[FEAT] 綠界電子發票設定整組選填一旦啟用即全欄必填
1. PaymentConfigController 的 store/update 新增 ecpay_invoice 群組條件式驗證:四欄全空時整組選填,只要填寫任一欄(store_id/hash_key/hash_iv/email)即透過 required_with 強制其餘全部必填。 2. email 欄位加上格式驗證,從源頭避免空值導致綠界開立失敗(綠界規定 CustomerPhone 與 CustomerEmail 至少擇一,本系統 email 為唯一來源)。 3. 新增三語系驗證訊息與表單提示 key(zh_TW/en/ja),維持字母排序與斜線不逸出。 4. create/edit 表單加入動態必填提示:填入任一欄時 label 顯示紅色星號,並於各欄下方顯示 @error 驗證訊息。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d3e455fc82
commit
e81756833c
@ -51,7 +51,7 @@ class PaymentConfigController extends AdminController
|
|||||||
'name' => 'required|string|max:255',
|
'name' => 'required|string|max:255',
|
||||||
'company_id' => 'nullable|exists:companies,id',
|
'company_id' => 'nullable|exists:companies,id',
|
||||||
'settings' => 'required|array',
|
'settings' => 'required|array',
|
||||||
]);
|
] + $this->ecpayInvoiceRules(), $this->ecpayInvoiceMessages());
|
||||||
|
|
||||||
PaymentConfig::create([
|
PaymentConfig::create([
|
||||||
'name' => $request->name,
|
'name' => $request->name,
|
||||||
@ -83,7 +83,7 @@ class PaymentConfigController extends AdminController
|
|||||||
'name' => 'required|string|max:255',
|
'name' => 'required|string|max:255',
|
||||||
'company_id' => 'nullable|exists:companies,id',
|
'company_id' => 'nullable|exists:companies,id',
|
||||||
'settings' => 'required|array',
|
'settings' => 'required|array',
|
||||||
]);
|
] + $this->ecpayInvoiceRules(), $this->ecpayInvoiceMessages());
|
||||||
|
|
||||||
$paymentConfig->update([
|
$paymentConfig->update([
|
||||||
'name' => $request->name,
|
'name' => $request->name,
|
||||||
@ -105,4 +105,30 @@ class PaymentConfigController extends AdminController
|
|||||||
return redirect()->route('admin.basic-settings.payment-configs.index')
|
return redirect()->route('admin.basic-settings.payment-configs.index')
|
||||||
->with('success', __('Payment Configuration deleted successfully.'));
|
->with('success', __('Payment Configuration deleted successfully.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 綠界電子發票群組驗證規則:整組選填,但只要填了任一欄,
|
||||||
|
* store_id / hash_key / hash_iv / email 即全部必填(綠界開立發票需金鑰齊備,
|
||||||
|
* 且 email 為「電話/信箱擇一」的唯一來源,缺一即無法開立)。
|
||||||
|
* 依賴 ConvertEmptyStringsToNull middleware:空欄送出為 null,required_with 不會誤觸發。
|
||||||
|
*/
|
||||||
|
private function ecpayInvoiceRules(): array
|
||||||
|
{
|
||||||
|
$g = 'settings.ecpay_invoice';
|
||||||
|
return [
|
||||||
|
"{$g}.store_id" => "nullable|string|max:20|required_with:{$g}.hash_key,{$g}.hash_iv,{$g}.email",
|
||||||
|
"{$g}.hash_key" => "nullable|string|max:64|required_with:{$g}.store_id,{$g}.hash_iv,{$g}.email",
|
||||||
|
"{$g}.hash_iv" => "nullable|string|max:64|required_with:{$g}.store_id,{$g}.hash_key,{$g}.email",
|
||||||
|
"{$g}.email" => "nullable|email|max:100|required_with:{$g}.store_id,{$g}.hash_key,{$g}.hash_iv",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 綠界電子發票群組的驗證訊息(三語系)。 */
|
||||||
|
private function ecpayInvoiceMessages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'settings.ecpay_invoice.*.required_with' => __('ECPay e-invoice is optional, but once any field is filled, Store ID / Hash Key / Hash IV / Email are all required.'),
|
||||||
|
'settings.ecpay_invoice.email.email' => __('Please enter a valid ECPay e-invoice notification email.'),
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -609,6 +609,7 @@
|
|||||||
"EASY_MERCHANT_ID": "EASY_MERCHANT_ID (EasyWallet)",
|
"EASY_MERCHANT_ID": "EASY_MERCHANT_ID (EasyWallet)",
|
||||||
"ECPay Invoice": "ECPay Invoice",
|
"ECPay Invoice": "ECPay Invoice",
|
||||||
"ECPay Invoice Settings Description": "ECPay Invoice Settings Description",
|
"ECPay Invoice Settings Description": "ECPay Invoice Settings Description",
|
||||||
|
"ECPay e-invoice is optional, but once any field is filled, Store ID / Hash Key / Hash IV / Email are all required.": "ECPay e-invoice is optional, but once any field is filled, Store ID / Hash Key / Hash IV / Email are all required.",
|
||||||
"ECPay has no such invoice; marked as pending re-issue": "ECPay has no such invoice; marked as pending re-issue",
|
"ECPay has no such invoice; marked as pending re-issue": "ECPay has no such invoice; marked as pending re-issue",
|
||||||
"ECPay query failed": "ECPay query failed",
|
"ECPay query failed": "ECPay query failed",
|
||||||
"ESUN": "ESUN",
|
"ESUN": "ESUN",
|
||||||
@ -1404,6 +1405,7 @@
|
|||||||
"Optional notes...": "Optional notes...",
|
"Optional notes...": "Optional notes...",
|
||||||
"Optional remarks": "Optional remarks",
|
"Optional remarks": "Optional remarks",
|
||||||
"Optional remarks...": "Optional remarks...",
|
"Optional remarks...": "Optional remarks...",
|
||||||
|
"Optional. If you fill in any field, all fields below become required.": "Optional. If you fill in any field, all fields below become required.",
|
||||||
"Or Choose Custom Date": "Or Choose Custom Date",
|
"Or Choose Custom Date": "Or Choose Custom Date",
|
||||||
"Order Details": "Order Details",
|
"Order Details": "Order Details",
|
||||||
"Order Info": "Order Info",
|
"Order Info": "Order Info",
|
||||||
@ -1536,6 +1538,7 @@
|
|||||||
"Please check the form for errors.": "Please check the form for errors.",
|
"Please check the form for errors.": "Please check the form for errors.",
|
||||||
"Please confirm the details below": "Please confirm the details below",
|
"Please confirm the details below": "Please confirm the details below",
|
||||||
"Please enter Webhook URL first.": "Please enter Webhook URL first.",
|
"Please enter Webhook URL first.": "Please enter Webhook URL first.",
|
||||||
|
"Please enter a valid ECPay e-invoice notification email.": "Please enter a valid ECPay e-invoice notification email.",
|
||||||
"Please enter address first": "Please enter address first",
|
"Please enter address first": "Please enter address first",
|
||||||
"Please enter configuration name": "Please enter configuration name",
|
"Please enter configuration name": "Please enter configuration name",
|
||||||
"Please enter machine flavor": "Please enter machine flavor",
|
"Please enter machine flavor": "Please enter machine flavor",
|
||||||
|
|||||||
@ -609,6 +609,7 @@
|
|||||||
"EASY_MERCHANT_ID": "EASY_MERCHANT_ID (悠遊付)",
|
"EASY_MERCHANT_ID": "EASY_MERCHANT_ID (悠遊付)",
|
||||||
"ECPay Invoice": "ECPay 電子領収書",
|
"ECPay Invoice": "ECPay 電子領収書",
|
||||||
"ECPay Invoice Settings Description": "ECPay 電子領収書設定",
|
"ECPay Invoice Settings Description": "ECPay 電子領収書設定",
|
||||||
|
"ECPay e-invoice is optional, but once any field is filled, Store ID / Hash Key / Hash IV / Email are all required.": "ECPay 電子インボイスは任意ですが、いずれかの項目を入力した場合は、店舗 ID / Hash Key / Hash IV / Email がすべて必須になります。",
|
||||||
"ECPay has no such invoice; marked as pending re-issue": "ECPay has no such invoice; marked as pending re-issue",
|
"ECPay has no such invoice; marked as pending re-issue": "ECPay has no such invoice; marked as pending re-issue",
|
||||||
"ECPay query failed": "ECPay query failed",
|
"ECPay query failed": "ECPay query failed",
|
||||||
"ESUN": "玉山銀行",
|
"ESUN": "玉山銀行",
|
||||||
@ -1404,6 +1405,7 @@
|
|||||||
"Optional notes...": "任意のメモ...",
|
"Optional notes...": "任意のメモ...",
|
||||||
"Optional remarks": "任意の備考",
|
"Optional remarks": "任意の備考",
|
||||||
"Optional remarks...": "任意の備考...",
|
"Optional remarks...": "任意の備考...",
|
||||||
|
"Optional. If you fill in any field, all fields below become required.": "任意。いずれかの項目を入力すると、以下のすべての項目が必須になります。",
|
||||||
"Or Choose Custom Date": "または日付を選択",
|
"Or Choose Custom Date": "または日付を選択",
|
||||||
"Order Details": "注文詳細",
|
"Order Details": "注文詳細",
|
||||||
"Order Info": "注文情報",
|
"Order Info": "注文情報",
|
||||||
@ -1536,6 +1538,7 @@
|
|||||||
"Please check the form for errors.": "フォームのエラーを確認してください。",
|
"Please check the form for errors.": "フォームのエラーを確認してください。",
|
||||||
"Please confirm the details below": "以下の詳細を確認してください",
|
"Please confirm the details below": "以下の詳細を確認してください",
|
||||||
"Please enter Webhook URL first.": "最初にWebhook URLを入力してください。",
|
"Please enter Webhook URL first.": "最初にWebhook URLを入力してください。",
|
||||||
|
"Please enter a valid ECPay e-invoice notification email.": "有効な ECPay 電子インボイス通知用メールアドレスを入力してください。",
|
||||||
"Please enter address first": "先に住所を入力してください",
|
"Please enter address first": "先に住所を入力してください",
|
||||||
"Please enter configuration name": "設定名を入力してください",
|
"Please enter configuration name": "設定名を入力してください",
|
||||||
"Please enter machine flavor": "機台フレーバーを入力してください",
|
"Please enter machine flavor": "機台フレーバーを入力してください",
|
||||||
|
|||||||
@ -609,6 +609,7 @@
|
|||||||
"EASY_MERCHANT_ID": "EASY_MERCHANT_ID (悠遊付)",
|
"EASY_MERCHANT_ID": "EASY_MERCHANT_ID (悠遊付)",
|
||||||
"ECPay Invoice": "綠界電子發票",
|
"ECPay Invoice": "綠界電子發票",
|
||||||
"ECPay Invoice Settings Description": "綠界科技電子發票設定",
|
"ECPay Invoice Settings Description": "綠界科技電子發票設定",
|
||||||
|
"ECPay e-invoice is optional, but once any field is filled, Store ID / Hash Key / Hash IV / Email are all required.": "綠界電子發票為選填,但只要填寫任一欄位,特店 ID / Hash Key / Hash IV / Email 即全部必填。",
|
||||||
"ECPay has no such invoice; marked as pending re-issue": "綠界查無此發票,已標記為待補開",
|
"ECPay has no such invoice; marked as pending re-issue": "綠界查無此發票,已標記為待補開",
|
||||||
"ECPay query failed": "綠界查詢失敗",
|
"ECPay query failed": "綠界查詢失敗",
|
||||||
"ESUN": "玉山銀行",
|
"ESUN": "玉山銀行",
|
||||||
@ -1404,6 +1405,7 @@
|
|||||||
"Optional notes...": "備註內容(選填)...",
|
"Optional notes...": "備註內容(選填)...",
|
||||||
"Optional remarks": "選填備註",
|
"Optional remarks": "選填備註",
|
||||||
"Optional remarks...": "選填備註...",
|
"Optional remarks...": "選填備註...",
|
||||||
|
"Optional. If you fill in any field, all fields below become required.": "選填。只要填寫任一欄位,以下欄位即全部必填。",
|
||||||
"Or Choose Custom Date": "或選擇自訂日期",
|
"Or Choose Custom Date": "或選擇自訂日期",
|
||||||
"Order Details": "訂單詳情",
|
"Order Details": "訂單詳情",
|
||||||
"Order Info": "單據資訊",
|
"Order Info": "單據資訊",
|
||||||
@ -1536,6 +1538,7 @@
|
|||||||
"Please check the form for errors.": "請檢查欄位內容是否正確。",
|
"Please check the form for errors.": "請檢查欄位內容是否正確。",
|
||||||
"Please confirm the details below": "請確認以下詳細資訊",
|
"Please confirm the details below": "請確認以下詳細資訊",
|
||||||
"Please enter Webhook URL first.": "請先輸入 Webhook 網址。",
|
"Please enter Webhook URL first.": "請先輸入 Webhook 網址。",
|
||||||
|
"Please enter a valid ECPay e-invoice notification email.": "請輸入有效的綠界電子發票通知 Email。",
|
||||||
"Please enter address first": "請先輸入地址",
|
"Please enter address first": "請先輸入地址",
|
||||||
"Please enter configuration name": "請輸入設定名稱",
|
"Please enter configuration name": "請輸入設定名稱",
|
||||||
"Please enter machine flavor": "請輸入機台風味",
|
"Please enter machine flavor": "請輸入機台風味",
|
||||||
|
|||||||
@ -98,22 +98,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-6">
|
<div class="space-y-6" x-data="{ vals: { store_id: '', hash_key: '', hash_iv: '', email: '' }, get anyFilled() { return Object.values(this.vals).some(v => (v || '').trim() !== '') } }">
|
||||||
|
<p class="text-[11px] font-bold text-slate-400 leading-relaxed -mt-2">{{ __('Optional. If you fill in any field, all fields below become required.') }}</p>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Invoice Store ID') }}</label>
|
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Invoice Store ID') }}<span x-show="anyFilled" class="text-rose-500 ml-0.5">*</span></label>
|
||||||
<input type="text" name="settings[ecpay_invoice][store_id]" class="luxury-input w-full">
|
<input type="text" name="settings[ecpay_invoice][store_id]" x-model="vals.store_id" class="luxury-input w-full">
|
||||||
|
@error('settings.ecpay_invoice.store_id')<p class="mt-1.5 text-[11px] font-bold text-rose-500">{{ $message }}</p>@enderror
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Invoice HashKey') }}</label>
|
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Invoice HashKey') }}<span x-show="anyFilled" class="text-rose-500 ml-0.5">*</span></label>
|
||||||
<input type="text" name="settings[ecpay_invoice][hash_key]" class="luxury-input w-full">
|
<input type="text" name="settings[ecpay_invoice][hash_key]" x-model="vals.hash_key" class="luxury-input w-full">
|
||||||
|
@error('settings.ecpay_invoice.hash_key')<p class="mt-1.5 text-[11px] font-bold text-rose-500">{{ $message }}</p>@enderror
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Invoice HashIV') }}</label>
|
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Invoice HashIV') }}<span x-show="anyFilled" class="text-rose-500 ml-0.5">*</span></label>
|
||||||
<input type="text" name="settings[ecpay_invoice][hash_iv]" class="luxury-input w-full">
|
<input type="text" name="settings[ecpay_invoice][hash_iv]" x-model="vals.hash_iv" class="luxury-input w-full">
|
||||||
|
@error('settings.ecpay_invoice.hash_iv')<p class="mt-1.5 text-[11px] font-bold text-rose-500">{{ $message }}</p>@enderror
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Invoice Notification Email') }}</label>
|
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Invoice Notification Email') }}<span x-show="anyFilled" class="text-rose-500 ml-0.5">*</span></label>
|
||||||
<input type="text" name="settings[ecpay_invoice][email]" class="luxury-input w-full">
|
<input type="text" name="settings[ecpay_invoice][email]" x-model="vals.email" class="luxury-input w-full">
|
||||||
|
@error('settings.ecpay_invoice.email')<p class="mt-1.5 text-[11px] font-bold text-rose-500">{{ $message }}</p>@enderror
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -102,22 +102,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-6">
|
<div class="space-y-6" x-data="{ vals: { store_id: @js($settings['ecpay_invoice']['store_id'] ?? ''), hash_key: @js($settings['ecpay_invoice']['hash_key'] ?? ''), hash_iv: @js($settings['ecpay_invoice']['hash_iv'] ?? ''), email: @js($settings['ecpay_invoice']['email'] ?? '') }, get anyFilled() { return Object.values(this.vals).some(v => (v || '').trim() !== '') } }">
|
||||||
|
<p class="text-[11px] font-bold text-slate-400 leading-relaxed -mt-2">{{ __('Optional. If you fill in any field, all fields below become required.') }}</p>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Invoice Store ID') }}</label>
|
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Invoice Store ID') }}<span x-show="anyFilled" class="text-rose-500 ml-0.5">*</span></label>
|
||||||
<input type="text" name="settings[ecpay_invoice][store_id]" value="{{ $settings['ecpay_invoice']['store_id'] ?? '' }}" class="luxury-input w-full">
|
<input type="text" name="settings[ecpay_invoice][store_id]" x-model="vals.store_id" value="{{ $settings['ecpay_invoice']['store_id'] ?? '' }}" class="luxury-input w-full">
|
||||||
|
@error('settings.ecpay_invoice.store_id')<p class="mt-1.5 text-[11px] font-bold text-rose-500">{{ $message }}</p>@enderror
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Invoice HashKey') }}</label>
|
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Invoice HashKey') }}<span x-show="anyFilled" class="text-rose-500 ml-0.5">*</span></label>
|
||||||
<input type="text" name="settings[ecpay_invoice][hash_key]" value="{{ $settings['ecpay_invoice']['hash_key'] ?? '' }}" class="luxury-input w-full">
|
<input type="text" name="settings[ecpay_invoice][hash_key]" x-model="vals.hash_key" value="{{ $settings['ecpay_invoice']['hash_key'] ?? '' }}" class="luxury-input w-full">
|
||||||
|
@error('settings.ecpay_invoice.hash_key')<p class="mt-1.5 text-[11px] font-bold text-rose-500">{{ $message }}</p>@enderror
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Invoice HashIV') }}</label>
|
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Invoice HashIV') }}<span x-show="anyFilled" class="text-rose-500 ml-0.5">*</span></label>
|
||||||
<input type="text" name="settings[ecpay_invoice][hash_iv]" value="{{ $settings['ecpay_invoice']['hash_iv'] ?? '' }}" class="luxury-input w-full">
|
<input type="text" name="settings[ecpay_invoice][hash_iv]" x-model="vals.hash_iv" value="{{ $settings['ecpay_invoice']['hash_iv'] ?? '' }}" class="luxury-input w-full">
|
||||||
|
@error('settings.ecpay_invoice.hash_iv')<p class="mt-1.5 text-[11px] font-bold text-rose-500">{{ $message }}</p>@enderror
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Invoice Notification Email') }}</label>
|
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Invoice Notification Email') }}<span x-show="anyFilled" class="text-rose-500 ml-0.5">*</span></label>
|
||||||
<input type="text" name="settings[ecpay_invoice][email]" value="{{ $settings['ecpay_invoice']['email'] ?? '' }}" class="luxury-input w-full">
|
<input type="text" name="settings[ecpay_invoice][email]" x-model="vals.email" value="{{ $settings['ecpay_invoice']['email'] ?? '' }}" class="luxury-input w-full">
|
||||||
|
@error('settings.ecpay_invoice.email')<p class="mt-1.5 text-[11px] font-bold text-rose-500">{{ $message }}</p>@enderror
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user