@moksa/plugin-ecpay 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -7,7 +7,7 @@
7
7
  /** @type {import("emdash").PluginDescriptor} */
8
8
  const descriptor = Object.freeze({
9
9
  "id": "moksa-ecpay",
10
- "version": "0.1.0",
10
+ "version": "0.1.1",
11
11
  "format": "standard",
12
12
  "entrypoint": "@moksa/plugin-ecpay/sandbox",
13
13
  "capabilities": [
@@ -18,6 +18,8 @@ const descriptor = Object.freeze({
18
18
  "payment.ecpay.com.tw",
19
19
  "ecpayment-stage.ecpay.com.tw",
20
20
  "ecpayment.ecpay.com.tw",
21
+ "ecpg-stage.ecpay.com.tw",
22
+ "ecpg.ecpay.com.tw",
21
23
  "logistics-stage.ecpay.com.tw",
22
24
  "logistics.ecpay.com.tw",
23
25
  "einvoice-stage.ecpay.com.tw",
@@ -76,6 +78,31 @@ const descriptor = Object.freeze({
76
78
  "nextAt"
77
79
  ]
78
80
  ]
81
+ },
82
+ "mandates": {
83
+ "indexes": [
84
+ "status",
85
+ "cardRef",
86
+ "orderNumber",
87
+ "updatedAt"
88
+ ]
89
+ },
90
+ "charges": {
91
+ "indexes": [
92
+ "state",
93
+ "mandateId",
94
+ "orderNumber",
95
+ "updatedAt",
96
+ [
97
+ "state",
98
+ "updatedAt"
99
+ ]
100
+ ]
101
+ },
102
+ "members": {
103
+ "indexes": [
104
+ "createdAt"
105
+ ]
79
106
  }
80
107
  },
81
108
  "hooks": [
@@ -137,6 +164,102 @@ const descriptor = Object.freeze({
137
164
  },
138
165
  "public": true
139
166
  },
167
+ {
168
+ "name": "bus/bind/step",
169
+ "methods": [
170
+ "POST"
171
+ ],
172
+ "request": {
173
+ "body": "text",
174
+ "maxBytes": 262144,
175
+ "headers": [
176
+ "x-moksa-bus-ts",
177
+ "x-moksa-bus-id",
178
+ "x-moksa-bus-sig"
179
+ ]
180
+ },
181
+ "public": true
182
+ },
183
+ {
184
+ "name": "bus/gw/bind-pay",
185
+ "methods": [
186
+ "POST"
187
+ ],
188
+ "request": {
189
+ "body": "text",
190
+ "maxBytes": 262144,
191
+ "headers": [
192
+ "x-moksa-bus-ts",
193
+ "x-moksa-bus-id",
194
+ "x-moksa-bus-sig"
195
+ ]
196
+ },
197
+ "public": true
198
+ },
199
+ {
200
+ "name": "bus/gw/bind-result",
201
+ "methods": [
202
+ "POST"
203
+ ],
204
+ "request": {
205
+ "body": "text",
206
+ "maxBytes": 262144,
207
+ "headers": [
208
+ "x-moksa-bus-ts",
209
+ "x-moksa-bus-id",
210
+ "x-moksa-bus-sig"
211
+ ]
212
+ },
213
+ "public": true
214
+ },
215
+ {
216
+ "name": "bus/gw/bind-notify",
217
+ "methods": [
218
+ "POST"
219
+ ],
220
+ "request": {
221
+ "body": "text",
222
+ "maxBytes": 262144,
223
+ "headers": [
224
+ "x-moksa-bus-ts",
225
+ "x-moksa-bus-id",
226
+ "x-moksa-bus-sig"
227
+ ]
228
+ },
229
+ "public": true
230
+ },
231
+ {
232
+ "name": "bus/recurring/charge",
233
+ "methods": [
234
+ "POST"
235
+ ],
236
+ "request": {
237
+ "body": "text",
238
+ "maxBytes": 262144,
239
+ "headers": [
240
+ "x-moksa-bus-ts",
241
+ "x-moksa-bus-id",
242
+ "x-moksa-bus-sig"
243
+ ]
244
+ },
245
+ "public": true
246
+ },
247
+ {
248
+ "name": "bus/recurring/action",
249
+ "methods": [
250
+ "POST"
251
+ ],
252
+ "request": {
253
+ "body": "text",
254
+ "maxBytes": 262144,
255
+ "headers": [
256
+ "x-moksa-bus-ts",
257
+ "x-moksa-bus-id",
258
+ "x-moksa-bus-sig"
259
+ ]
260
+ },
261
+ "public": true
262
+ },
140
263
  {
141
264
  "name": "bus/notify",
142
265
  "methods": [
@@ -329,6 +452,14 @@ const descriptor = Object.freeze({
329
452
  },
330
453
  "public": true
331
454
  },
455
+ {
456
+ "name": "recurring/list",
457
+ "methods": [
458
+ "GET",
459
+ "POST"
460
+ ],
461
+ "permission": "plugins:manage"
462
+ },
332
463
  {
333
464
  "name": "trades/list",
334
465
  "methods": [
@@ -471,6 +602,41 @@ const descriptor = Object.freeze({
471
602
  ],
472
603
  "mcp": {
473
604
  "tools": [
605
+ {
606
+ "name": "list_recurring_charges",
607
+ "description": "Subscription renewal charges made with ECPay saved cards (站內付 2.0 綁卡): state paid / failed / declined / uncertain, ECPay's code and message, the MerchantTradeNo, plus the saved cards (brand, last four digits, expiry; never the card token). Optional order number or state filter.",
608
+ "route": "recurring/list",
609
+ "permission": "plugins:manage",
610
+ "destructive": false,
611
+ "inputSchema": {
612
+ "$schema": "http://json-schema.org/draft-07/schema#",
613
+ "type": "object",
614
+ "properties": {
615
+ "orderNumber": {
616
+ "type": "string",
617
+ "minLength": 1,
618
+ "maxLength": 18,
619
+ "pattern": "^[A-Z0-9]+$"
620
+ },
621
+ "state": {
622
+ "enum": [
623
+ "calling",
624
+ "paid",
625
+ "failed",
626
+ "declined",
627
+ "uncertain"
628
+ ]
629
+ },
630
+ "limit": {
631
+ "type": "integer",
632
+ "minimum": 1,
633
+ "maximum": 100,
634
+ "default": 20
635
+ }
636
+ },
637
+ "additionalProperties": true
638
+ }
639
+ },
474
640
  {
475
641
  "name": "list_trades",
476
642
  "description": "List ECPay trades (one per payment attempt), newest first, optionally for one order number. Status: created, awaiting (ATM/CVS code issued), paid, failed.",
@@ -1054,6 +1220,12 @@ const descriptor = Object.freeze({
1054
1220
  "type": "boolean",
1055
1221
  "default": false
1056
1222
  },
1223
+ "methodCreditBind": {
1224
+ "label": "信用卡綁定:定期定額自動扣款(站內付 2.0,需向綠界申請綁卡)",
1225
+ "description": "只在訂閱商品結帳時出現。首期刷卡同時綁定卡片,之後每期由網站自動扣款;請在綠界廠商後台開啟每日自動關帳。測試環境用綠界公開測試帳號。",
1226
+ "type": "boolean",
1227
+ "default": false
1228
+ },
1057
1229
  "atmExpireDays": {
1058
1230
  "label": "ATM 繳費期限(天)",
1059
1231
  "type": "number",
@@ -1399,6 +1571,11 @@ const descriptor = Object.freeze({
1399
1571
  "type": "string",
1400
1572
  "default": ""
1401
1573
  },
1574
+ "vaultKey": {
1575
+ "label": "卡片憑證加密金鑰",
1576
+ "description": "第一次綁卡時自動產生,用來加密綠界綁卡代碼(BindCardID)。請勿修改或刪除,否則已綁定的卡片無法再扣款。",
1577
+ "type": "secret"
1578
+ },
1402
1579
  "busKey": {
1403
1580
  "label": "匯流排金鑰",
1404
1581
  "description": "由網站部署腳本寫入(scripts/provision-bus.mjs),請勿手動修改。",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "moksa-ecpay",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "declaredAccess": {
5
5
  "network": {
6
6
  "request": {
@@ -9,6 +9,8 @@
9
9
  "payment.ecpay.com.tw",
10
10
  "ecpayment-stage.ecpay.com.tw",
11
11
  "ecpayment.ecpay.com.tw",
12
+ "ecpg-stage.ecpay.com.tw",
13
+ "ecpg.ecpay.com.tw",
12
14
  "logistics-stage.ecpay.com.tw",
13
15
  "logistics.ecpay.com.tw",
14
16
  "einvoice-stage.ecpay.com.tw",
@@ -25,6 +27,8 @@
25
27
  "payment.ecpay.com.tw",
26
28
  "ecpayment-stage.ecpay.com.tw",
27
29
  "ecpayment.ecpay.com.tw",
30
+ "ecpg-stage.ecpay.com.tw",
31
+ "ecpg.ecpay.com.tw",
28
32
  "logistics-stage.ecpay.com.tw",
29
33
  "logistics.ecpay.com.tw",
30
34
  "einvoice-stage.ecpay.com.tw",
@@ -83,6 +87,31 @@
83
87
  "nextAt"
84
88
  ]
85
89
  ]
90
+ },
91
+ "mandates": {
92
+ "indexes": [
93
+ "status",
94
+ "cardRef",
95
+ "orderNumber",
96
+ "updatedAt"
97
+ ]
98
+ },
99
+ "charges": {
100
+ "indexes": [
101
+ "state",
102
+ "mandateId",
103
+ "orderNumber",
104
+ "updatedAt",
105
+ [
106
+ "state",
107
+ "updatedAt"
108
+ ]
109
+ ]
110
+ },
111
+ "members": {
112
+ "indexes": [
113
+ "createdAt"
114
+ ]
86
115
  }
87
116
  },
88
117
  "hooks": [
@@ -144,6 +173,102 @@
144
173
  },
145
174
  "public": true
146
175
  },
176
+ {
177
+ "name": "bus/bind/step",
178
+ "methods": [
179
+ "POST"
180
+ ],
181
+ "request": {
182
+ "body": "text",
183
+ "maxBytes": 262144,
184
+ "headers": [
185
+ "x-moksa-bus-ts",
186
+ "x-moksa-bus-id",
187
+ "x-moksa-bus-sig"
188
+ ]
189
+ },
190
+ "public": true
191
+ },
192
+ {
193
+ "name": "bus/gw/bind-pay",
194
+ "methods": [
195
+ "POST"
196
+ ],
197
+ "request": {
198
+ "body": "text",
199
+ "maxBytes": 262144,
200
+ "headers": [
201
+ "x-moksa-bus-ts",
202
+ "x-moksa-bus-id",
203
+ "x-moksa-bus-sig"
204
+ ]
205
+ },
206
+ "public": true
207
+ },
208
+ {
209
+ "name": "bus/gw/bind-result",
210
+ "methods": [
211
+ "POST"
212
+ ],
213
+ "request": {
214
+ "body": "text",
215
+ "maxBytes": 262144,
216
+ "headers": [
217
+ "x-moksa-bus-ts",
218
+ "x-moksa-bus-id",
219
+ "x-moksa-bus-sig"
220
+ ]
221
+ },
222
+ "public": true
223
+ },
224
+ {
225
+ "name": "bus/gw/bind-notify",
226
+ "methods": [
227
+ "POST"
228
+ ],
229
+ "request": {
230
+ "body": "text",
231
+ "maxBytes": 262144,
232
+ "headers": [
233
+ "x-moksa-bus-ts",
234
+ "x-moksa-bus-id",
235
+ "x-moksa-bus-sig"
236
+ ]
237
+ },
238
+ "public": true
239
+ },
240
+ {
241
+ "name": "bus/recurring/charge",
242
+ "methods": [
243
+ "POST"
244
+ ],
245
+ "request": {
246
+ "body": "text",
247
+ "maxBytes": 262144,
248
+ "headers": [
249
+ "x-moksa-bus-ts",
250
+ "x-moksa-bus-id",
251
+ "x-moksa-bus-sig"
252
+ ]
253
+ },
254
+ "public": true
255
+ },
256
+ {
257
+ "name": "bus/recurring/action",
258
+ "methods": [
259
+ "POST"
260
+ ],
261
+ "request": {
262
+ "body": "text",
263
+ "maxBytes": 262144,
264
+ "headers": [
265
+ "x-moksa-bus-ts",
266
+ "x-moksa-bus-id",
267
+ "x-moksa-bus-sig"
268
+ ]
269
+ },
270
+ "public": true
271
+ },
147
272
  {
148
273
  "name": "bus/notify",
149
274
  "methods": [
@@ -336,6 +461,14 @@
336
461
  },
337
462
  "public": true
338
463
  },
464
+ {
465
+ "name": "recurring/list",
466
+ "methods": [
467
+ "GET",
468
+ "POST"
469
+ ],
470
+ "permission": "plugins:manage"
471
+ },
339
472
  {
340
473
  "name": "trades/list",
341
474
  "methods": [
@@ -478,6 +611,41 @@
478
611
  ],
479
612
  "mcp": {
480
613
  "tools": [
614
+ {
615
+ "name": "list_recurring_charges",
616
+ "description": "Subscription renewal charges made with ECPay saved cards (站內付 2.0 綁卡): state paid / failed / declined / uncertain, ECPay's code and message, the MerchantTradeNo, plus the saved cards (brand, last four digits, expiry; never the card token). Optional order number or state filter.",
617
+ "route": "recurring/list",
618
+ "permission": "plugins:manage",
619
+ "destructive": false,
620
+ "inputSchema": {
621
+ "$schema": "http://json-schema.org/draft-07/schema#",
622
+ "type": "object",
623
+ "properties": {
624
+ "orderNumber": {
625
+ "type": "string",
626
+ "minLength": 1,
627
+ "maxLength": 18,
628
+ "pattern": "^[A-Z0-9]+$"
629
+ },
630
+ "state": {
631
+ "enum": [
632
+ "calling",
633
+ "paid",
634
+ "failed",
635
+ "declined",
636
+ "uncertain"
637
+ ]
638
+ },
639
+ "limit": {
640
+ "type": "integer",
641
+ "minimum": 1,
642
+ "maximum": 100,
643
+ "default": 20
644
+ }
645
+ },
646
+ "additionalProperties": true
647
+ }
648
+ },
481
649
  {
482
650
  "name": "list_trades",
483
651
  "description": "List ECPay trades (one per payment attempt), newest first, optionally for one order number. Status: created, awaiting (ATM/CVS code issued), paid, failed.",
@@ -1035,6 +1203,12 @@
1035
1203
  "type": "boolean",
1036
1204
  "default": false
1037
1205
  },
1206
+ "methodCreditBind": {
1207
+ "label": "信用卡綁定:定期定額自動扣款(站內付 2.0,需向綠界申請綁卡)",
1208
+ "description": "只在訂閱商品結帳時出現。首期刷卡同時綁定卡片,之後每期由網站自動扣款;請在綠界廠商後台開啟每日自動關帳。測試環境用綠界公開測試帳號。",
1209
+ "type": "boolean",
1210
+ "default": false
1211
+ },
1038
1212
  "atmExpireDays": {
1039
1213
  "label": "ATM 繳費期限(天)",
1040
1214
  "type": "number",
@@ -1380,6 +1554,11 @@
1380
1554
  "type": "string",
1381
1555
  "default": ""
1382
1556
  },
1557
+ "vaultKey": {
1558
+ "label": "卡片憑證加密金鑰",
1559
+ "description": "第一次綁卡時自動產生,用來加密綠界綁卡代碼(BindCardID)。請勿修改或刪除,否則已綁定的卡片無法再扣款。",
1560
+ "type": "secret"
1561
+ },
1383
1562
  "busKey": {
1384
1563
  "label": "匯流排金鑰",
1385
1564
  "description": "由網站部署腳本寫入(scripts/provision-bus.mjs),請勿手動修改。",
Binary file
package/dist/plugin.mjs CHANGED
@@ -1 +1 @@
1
- function e(e,t){let n=[],r=e.check(t,``,n);return n.length?{ok:!1,issues:n,error:n.map(e=>e.path?`${e.path}: ${e.message}`:e.message).join(`; `)}:{ok:!0,value:r}}function t(e,t,n){e.push({path:t,message:n})}function n(e){return e===null?`null`:Array.isArray(e)?`array`:typeof e}const r=/^[^\s@]+@[^\s@]+\.[^\s@]+$/;function i(e={}){return{check(i,a,o){if(typeof i!=`string`)return t(o,a,`expected string, got ${n(i)}`);let s=e.trim?i.trim():i;return e.min!==void 0&&s.length<e.min?t(o,a,`must be at least ${e.min} characters`):e.max!==void 0&&s.length>e.max?t(o,a,`must be at most ${e.max} characters`):e.pattern&&!e.pattern.test(s)?t(o,a,e.message??`has an invalid format`):e.format===`email`&&!r.test(s)?t(o,a,`must be an email address`):s},json(){let t={type:`string`};return e.min!==void 0&&(t.minLength=e.min),e.max!==void 0&&(t.maxLength=e.max),e.pattern&&(t.pattern=e.pattern.source),e.format&&(t.format=e.format),t}}}function a(e,r){return{check(i,a,o){let s=i;return r.coerce&&typeof s==`string`&&s.trim()!==``&&(s=Number(s)),typeof s!=`number`||!Number.isFinite(s)?t(o,a,`expected ${e?`integer`:`number`}, got ${n(i)}`):e&&!Number.isSafeInteger(s)?t(o,a,`must be an integer`):r.min!==void 0&&s<r.min?t(o,a,`must be >= ${r.min}`):r.max!==void 0&&s>r.max?t(o,a,`must be <= ${r.max}`):s},json(){let t={type:e?`integer`:`number`};return r.min!==void 0&&(t.minimum=r.min),r.max!==void 0&&(t.maximum=r.max),t}}}function o(e={}){return a(!0,e)}function s(e={}){return a(!1,e)}function c(){return{check:e=>e,json:()=>({})}}function l(){return{check:(e,r,i)=>typeof e==`boolean`?e:t(i,r,`expected boolean, got ${n(e)}`),json:()=>({type:`boolean`})}}function u(e){return{check:(n,r,i)=>e.includes(n)?n:t(i,r,`must be one of ${e.join(`, `)}`),json:()=>({enum:[...e]})}}function d(e){return{check:(n,r,i)=>n===e?e:t(i,r,`must be ${JSON.stringify(e)}`),json:()=>({const:e})}}function f(e,r={}){return{check(i,a,o){if(!Array.isArray(i))return t(o,a,`expected array, got ${n(i)}`);if(r.min!==void 0&&i.length<r.min)return t(o,a,`must have at least ${r.min} items`);if(r.max!==void 0&&i.length>r.max)return t(o,a,`must have at most ${r.max} items`);let s=o.length,c=i.map((t,n)=>e.check(t,`${a}[${n}]`,o));return o.length>s?void 0:c},json(){let t={type:`array`,items:e.json()};return r.min!==void 0&&(t.minItems=r.min),r.max!==void 0&&(t.maxItems=r.max),t}}}function p(e,r={}){return{check(i,a,o){if(typeof i!=`object`||!i||Array.isArray(i))return t(o,a,`expected object, got ${n(i)}`);let s=i,c=o.length,l={};for(let[n,r]of Object.entries(e)){let e=a?`${a}.${n}`:n;if(!(n in s)||s[n]===void 0){let i=r.optional?r.check(void 0,e,o):t(o,e,`is required`);i!==void 0&&(l[n]=i);continue}let i=r.check(s[n],e,o);i!==void 0&&(l[n]=i)}if(r.unknown===`reject`)for(let n of Object.keys(s))n in e||t(o,a?`${a}.${n}`:n,`is not allowed`);return o.length>c?void 0:l},json(){let t={},n=[];for(let[r,i]of Object.entries(e))t[r]=i.json(),i.optional||n.push(r);let i={type:`object`,properties:t,additionalProperties:r.unknown!==`reject`};return n.length&&(i.required=n),i}}}function m(e,r={}){return{check(i,a,o){if(typeof i!=`object`||!i||Array.isArray(i))return t(o,a,`expected object, got ${n(i)}`);let s=Object.entries(i);if(r.maxKeys!==void 0&&s.length>r.maxKeys)return t(o,a,`must have at most ${r.maxKeys} keys`);let c=o.length,l={};for(let[n,i]of s){let s=a?`${a}.${n}`:n;if(r.key&&!r.key.test(n)){t(o,s,`has an invalid key`);continue}let c=e.check(i,s,o);c!==void 0&&(l[n]=c)}return o.length>c?void 0:l},json(){let t={type:`object`,additionalProperties:e.json()};return r.maxKeys!==void 0&&(t.maxProperties=r.maxKeys),t}}}function h(e){return{optional:!0,mayBeAbsent:!0,check:(t,n,r)=>t===void 0?void 0:e.check(t,n,r),json:()=>e.json()}}function g(e,t){return{optional:!0,check:(n,r,i)=>n===void 0?t:e.check(n,r,i),json:()=>({...e.json(),default:t})}}function _(e){return{check:(t,n,r)=>t===null?null:e.check(t,n,r),json:()=>({anyOf:[e.json(),{type:`null`}]})}}const v=o({min:0,max:99999999}),y=i({min:1,max:20,pattern:/^[A-Z0-9]+$/}),b=i({min:10,max:40}),ee=[`member`,`mobile`,`certificate`,`paper`,`donate`,`company`],te=[`normal`,`refrigerated`,`frozen`],ne=p({name:i({max:60}),phone:i({max:30}),zip:h(i({max:10})),city:h(i({max:60})),line:h(i({max:200}))}),re=p({provider:i({min:1,max:40}),type:i({max:40}),id:i({min:1,max:40}),name:i({max:100}),address:i({max:200})}),ie=p({methodId:i({min:1,max:80}),label:i({max:100}),provider:_(i({max:64})),fee:v,address:h(ne),store:h(re),temperature:g(u(te),`normal`),collect:g(l(),!1),collectAmount:g(v,0)}),ae=p({kind:u(ee),carrierNum:h(i({max:64})),loveCode:h(i({max:10})),taxId:h(i({max:8,pattern:/^\d{8}$/})),companyTitle:h(i({max:100})),address:h(i({max:200})),provider:_(i({max:64}))}),oe=p({sku:i({min:1,max:64}),name:i({max:200}),quantity:o({min:1,max:9999}),unitPrice:v,amount:v,temperature:h(u(te)),weightKg:h(s({min:0,max:1e3}))}),se=p({id:i({min:1,max:120}),source:i({min:1,max:40}),provider:_(i({max:64})),label:i({max:100}),amount:o({min:-99999999,max:99999999}),holdId:_(i({max:80}))}),ce=p({number:y,status:i({max:20}),email:i({max:120}),customer:p({id:_(i({max:64})),name:i({max:60}),phone:i({max:30}),lineUserId:_(i({max:64}))}),lines:f(oe,{max:200}),subtotal:v,discountTotal:v,shippingFee:v,total:v,currency:d(`TWD`),shipping:_(ie),invoice:_(ae),payment:_(p({provider:i({max:40}),tradeNo:i({max:64}),amount:v,simulated:l()})),note:g(i({max:500}),``),createdAt:b,paidAt:_(b),adjustments:h(f(se,{max:30}))}),le=p({provider:i({min:1,max:64}),shipmentId:i({min:1,max:64}),carrier:i({max:40}),method:i({max:80}),trackingNo:_(i({max:64})),trackingUrl:_(i({max:300})),status:u([`created`,`in_progress`,`shipped`,`arrived`,`completed`,`store_closed`,`unclaimed`,`returned`,`failed`,`void`]),statusText:i({max:200}),isCollection:l(),occurredAt:b}),ue=p({provider:i({min:1,max:64}),status:u([`issued`,`failed`,`void`]),invoiceNo:_(i({max:20})),invoiceDate:_(i({max:20})),randomNumber:_(i({max:8})),kind:u(ee),amount:v,allowanceTotal:g(v,0),error:_(i({max:300})),occurredAt:b}),de=p({kind:d(`shipment`),reportId:i({min:1,max:200}),orderNumber:y,shipment:le}),fe=p({kind:d(`invoice`),reportId:i({min:1,max:200}),orderNumber:y,invoice:ue}),pe=p({kind:d(`payment`),reportId:i({min:1,max:200}),orderNumber:y,payment:p({eventId:i({min:1,max:120}),provider:i({min:1,max:40,pattern:/^[a-z0-9-]+$/}),orderNumber:y,amount:o({min:0}),status:u([`paid`,`failed`,`cancelled`]),tradeNo:i({max:64})}),reason:h(i({min:1,max:80}))}),me=p({kind:d(`refund`),reportId:i({min:1,max:200}),orderNumber:y,requestId:h(i({min:1,max:80})),status:u([`refunded`,`manual`,`failed`]),amount:v,provider:i({min:1,max:64}),message:g(i({max:300}),``),origin:h(d(`plugin`)),reason:h(i({max:200}))}),he=p({kind:d(`coupon`),reportId:i({min:1,max:200}),coupon:p({code:i({min:4,max:40,pattern:/^[A-Z0-9_-]+$/}),type:u([`percent`,`fixed_cart`]),amount:o({min:0,max:1e7}),freeShipping:g(l(),!1),description:g(i({max:200}),``),expiresAt:_(b),email:_(i({max:120})),customerId:_(i({max:64})),campaign:g(i({max:40}),``)})}),ge=p({kind:d(`points`),reportId:i({min:1,max:200}),credit:p({customerId:i({min:1,max:64}),email:i({min:3,max:120}),amount:o({min:1,max:99999999}),reason:i({min:1,max:200})})}),_e=p({kind:d(`order-action`),reportId:i({min:1,max:200}),orderNumber:y,action:u([`cancel`,`refund`]),amount:h(o({min:1,max:99999999})),reason:i({min:1,max:200})}),ve=p({kind:d(`order-request`),reportId:i({min:1,max:200}),email:i({min:3,max:120,format:`email`}),customer:p({name:i({min:1,max:60}),phone:i({max:30}),id:_(i({max:64}))}),lines:f(p({slug:i({min:1,max:200}),sku:i({min:1,max:64}),quantity:o({min:1,max:99})}),{min:1,max:20}),pricing:g(m(c(),{maxKeys:20}),{}),note:g(i({max:500}),``)}),ye=p({kind:d(`stock`),reportId:i({min:1,max:200}),sku:i({min:1,max:64}),stock:o({min:0,max:1e6}),reason:g(i({max:200}),``)}),be=p({kind:d(`points-grant`),reportId:i({min:1,max:200}),grant:p({customerId:i({min:1,max:64}),email:i({min:3,max:120}),points:o({min:1,max:1e6}),reason:i({min:1,max:200})})}),xe=p({kind:d(`line-push`),reportId:i({min:1,max:200}),push:p({customerId:h(_(i({max:64}))),userId:h(_(i({max:64,pattern:/^U[0-9a-f]{32}$/i}))),topic:i({min:1,max:40,pattern:/^[a-z0-9_]+$/}),text:i({min:1,max:5e3})})}),Se=`shipment, invoice, payment, refund, coupon, points, order-action, order-request, stock, points-grant or line-push`;function Ce(t){let n=t?.kind;return n===`shipment`?e(de,t):n===`invoice`?e(fe,t):n===`payment`?e(pe,t):n===`refund`?e(me,t):n===`coupon`?e(he,t):n===`points`?e(ge,t):n===`order-action`?e(_e,t):n===`order-request`?e(ve,t):n===`stock`?e(ye,t):n===`points-grant`?e(be,t):n===`line-push`?e(xe,t):{ok:!1,error:`kind: must be ${Se}`,issues:[{path:`kind`,message:`must be ${Se}`}]}}const we=p({method:u([`GET`,`POST`]),query:g(i({max:8192}),``),contentType:g(i({max:200}),``),body:g(i({max:2e5}),``),headers:g(m(i({max:1e3}),{maxKeys:10}),{}),origin:h(i({max:200}))});o({min:-99999999,max:99999999}),o({min:0,max:100}),o({min:1,max:9999}),o({min:60,max:3600});const x={events:`bus/events`,reportsDue:`bus/reports/due`,reportsAck:`bus/reports/ack`,notify:`bus/notify`,gateway:e=>`bus/gw/${e}`,customerSession:`bus/customers/session`},S={create:`shipments/create`,list:`shipments/list`,sync:`shipments/sync`,print:`shipments/print`,void:`shipments/void`,syncAll:`shipments/sync-all`},Te={issue:`invoices/issue`,get:`invoices/get`,void:`invoices/void`,allowance:`invoices/allowance`},Ee=new TextEncoder,De=new TextDecoder;function C(e){return typeof e==`string`?Ee.encode(e):e}function Oe(e){return De.decode(e)}function w(e){let t=``;for(let n of e)t+=n.toString(16).padStart(2,`0`);return t}function ke(e){let t=``;for(let n of e)t+=String.fromCharCode(n);return btoa(t)}function Ae(e){let t=e.replace(/-/g,`+`).replace(/_/g,`/`).replace(/\s+/g,``);if(!/^[A-Za-z0-9+/]*={0,2}$/.test(t))return null;try{let e=atob(t.padEnd(Math.ceil(t.length/4)*4,`=`)),n=new Uint8Array(e.length);for(let t=0;t<e.length;t++)n[t]=e.charCodeAt(t);return n}catch{return null}}function je(e){return ke(e).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=+$/,``)}function Me(...e){let t=new Uint8Array(e.reduce((e,t)=>e+t.length,0)),n=0;for(let r of e)t.set(r,n),n+=r.length;return t}function Ne(e,t){let n=C(e),r=C(t);if(n.length!==r.length)return!1;let i=0;for(let e=0;e<n.length;e++)i|=n[e]^r[e];return i===0}function Pe(e){return crypto.getRandomValues(new Uint8Array(e))}function T(e){return e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength)}function Fe(e){return e}async function Ie(e){return new Uint8Array(await crypto.subtle.digest(`SHA-256`,T(C(e))))}async function Le(e){return w(await Ie(e))}async function Re(e,t){let n=await crypto.subtle.importKey(`raw`,T(C(e)),{name:`HMAC`,hash:`SHA-256`},!1,[`sign`]);return new Uint8Array(await crypto.subtle.sign(`HMAC`,n,T(C(t))))}async function ze(e,t){return w(await Re(e,t))}const E={ts:`x-moksa-bus-ts`,id:`x-moksa-bus-id`,sig:`x-moksa-bus-sig`},Be=[E.ts,E.id,E.sig];function Ve(e,t,n,r){return`${e}\n${t}\n${n}\n${r}`}function He(e,t){if(typeof e.get==`function`)return e.get(t)??void 0;let n=e;return n[t]??Object.entries(n).find(([e])=>e.toLowerCase()===t)?.[1]}async function Ue(e,t,n,r,i=Date.now()){if(!e)return{ok:!1,reason:`not_configured`};let a=He(t,E.ts),o=He(t,E.id),s=He(t,E.sig);return!a||!o||!s||!/^\d{1,12}$/.test(a)||o.length>200?{ok:!1,reason:`missing_headers`}:Math.abs(Math.floor(i/1e3)-Number(a))>300?{ok:!1,reason:`stale`}:Ne(await ze(e,Ve(a,o,n,r)),s.toLowerCase())?{ok:!0,id:o}:{ok:!1,reason:`bad_signature`}}function We(e,t,n){return Fe({public:!0,methods:[`POST`],request:{body:`text`,headers:Be,maxBytes:256*1024},handler:async(r,i)=>{let a=r.input,o=await Ue(await i.settings.get(`busKey`),r.request.headers,e,a);if(!o.ok)return{ok:!1,error:`BUS_${o.reason.toUpperCase()}`};let s=t(a);return s.ok?n(s.value,i,o.id):s.reply}})}function D(t,n,r){return We(t,t=>{let r;try{r=JSON.parse(t)}catch{return{ok:!1,reply:{ok:!1,error:`INVALID_JSON`}}}let i=e(n,r);return i.ok?{ok:!0,value:i.value}:{ok:!1,reply:{ok:!1,error:`INVALID_INPUT`,detail:i.error}}},r)}function Ge(e,t){return We(e,e=>({ok:!0,value:e}),t)}function Ke(e){return Math.min(36e5,3e4*2**Math.max(0,e-1))}function qe(e){return e.storage.reports}async function Je(e,t,n=Ce){let r=n(t);if(!r.ok)throw Error(`invalid report: ${r.error}`);let i=new Date().toISOString(),a={id:t.reportId,report:r.value,status:`pending`,attempts:0,nextAt:i,createdAt:i};return(await qe(e).compareAndSet(a.id,null,a)).applied?a:await qe(e).get(a.id)??a}async function Ye(e,t,n=new Date){let r=qe(e),i=await r.query({where:{status:`pending`,nextAt:{lte:n.toISOString()}},orderBy:{nextAt:`asc`},limit:t}),a=[];for(let{id:e,data:t}of i.items){let i=await r.updateIf(e,{where:{status:`pending`,nextAt:t.nextAt},set:{nextAt:new Date(n.getTime()+Ke(t.attempts+1)).toISOString()},delta:{attempts:{inc:1}}});i.applied&&a.push(i.data)}return a}async function Xe(e,t,n,r){let i=qe(e);for(let e=0;e<8;e++){let e=await i.getVersioned(t);if(!e)return null;let a=n?{...e.value,status:`done`,doneAt:new Date().toISOString()}:{...e.value,lastError:(r??`failed`).slice(0,300)};if((await i.compareAndSet(t,e.revision,a)).applied)return a}return null}const Ze=p({limit:g(o({min:1,max:100}),20)}),Qe=p({id:i({min:1,max:200}),ok:l(),error:h(i({max:500}))});function $e(){return{[x.reportsDue]:D(x.reportsDue,Ze,async(e,t)=>({ok:!0,reports:(await Ye(t,e.limit)).map(e=>({id:e.id,attempts:e.attempts,report:e.report}))})),[x.reportsAck]:D(x.reportsAck,Qe,async(e,t)=>{let n=await Xe(t,e.id,e.ok,e.error);return n?{ok:!0,status:n.status}:{ok:!1,error:`NOT_FOUND`}})}}function O(e){return e.storage}function et(e,t=`0|Unknown`,n=404){return{ok:!0,outcome:e,reply:t,httpStatus:n,passThrough:!0}}function tt(e){return e.passThrough?{ok:!0,outcome:e.outcome,passThrough:!0,response:{kind:`redirect`,status:303,location:`/shop`}}:{ok:!0,outcome:e.outcome,response:{kind:`redirect`,status:303,location:e.orderNumber?`/shop/order/${e.orderNumber}`:`/shop`},...e.event?{payment:{...e.event}}:{},...e.paymentInfo?{paymentInfo:e.paymentInfo}:{},...e.paymentFailure?{paymentFailure:e.paymentFailure}:{},...e.orderNumber?{orderNumber:e.orderNumber}:{}}}async function nt(e,t,n,r,i=``){let{trades:a}=O(e),o=await a.query({where:{orderNumber:t},limit:100});if(o.items.some(e=>e.data.status===`paid`))throw Error(`ALREADY_PAID`);for(let e=o.items.length+1;e<1296;e++){let o={merchantTradeNo:`${i}${t}${e.toString(36).toUpperCase().padStart(2,`0`)}`,orderNumber:t,amount:n,method:r,status:`created`,createdAt:new Date().toISOString()};if((await a.compareAndSet(o.merchantTradeNo,null,o)).applied)return o}throw Error(`TOO_MANY_ATTEMPTS`)}async function rt(e,t,n){let{trades:r}=O(e);for(let e=0;e<8;e++){let e=await r.getVersioned(t);if(!e)return null;if(e.value.status===`paid`)return e.value;let i={...e.value,...n,updatedAt:new Date().toISOString()};if(n.status===`paid`&&!i.paidAt&&(i.paidAt=i.updatedAt),(await r.compareAndSet(t,e.revision,i)).applied)return i}return await r.get(t)??null}async function it(e,t){return(await O(e).trades.query({where:{orderNumber:t},limit:100})).items.map(e=>e.data).find(e=>e.status===`paid`)??null}async function at(e,t,n){await O(e).trades.updateIf(t,{where:{},set:{updatedAt:new Date().toISOString()},delta:{refunded:{inc:n}}})}async function ot(e,t,n){return t.ok?(await n(),{ok:!0,action:t.action??`refund`,message:t.message,merchantTradeNo:e}):{ok:!1,error:t.manual?`MANUAL_REFUND`:`REFUND_FAILED`,detail:t.message}}async function st(e,t,n,r){let{events:i,trades:a}=O(e);if(!(await i.compareAndSet(t.id,null,{id:t.id,type:t.type,createdAt:new Date().toISOString()})).applied)return{ok:!0,handled:!1,duplicate:!0};if(t.type===`order.refund_requested`){let n=r?await lt(e,t.payload,r):null;return{ok:!0,handled:!!n,duplicate:!1,...n?{reports:[n]}:{}}}if(t.type===`order.paid`){let e=String(t.payload.orderNumber??``),n=await a.query({where:{orderNumber:e},limit:100});for(let{id:e,data:t}of n.items)t.status===`paid`&&!t.settled&&await a.put(e,{...t,settled:!0})}let o=n?await n(e,t):null;return{ok:!0,handled:!0,duplicate:!1,...o?.reports.length?{reports:o.reports}:{}}}async function ct(e,t){let n={kind:`refund`,reportId:`${t.provider}:${t.refundId}`,orderNumber:t.orderNumber,status:`refunded`,amount:t.amount,provider:t.provider,message:t.message.slice(0,300),origin:`plugin`,...t.reason?{reason:t.reason.slice(0,200)}:{}};return await Je(e,n),n}async function lt(e,t,n){if(t.provider!==n.provider)return null;let r=String(t.orderNumber??``),i=String(t.requestId??``),a=Number(t.amount);if(!r||!i||!Number.isSafeInteger(a)||a<=0)return null;let o,s;try{let t=await n.refund(e,r,a);o=t.ok?`refunded`:t.error===`MANUAL_REFUND`?`manual`:`failed`,s=t.ok?t.message??``:t.detail??t.error??``}catch(e){o=`failed`,s=`退款時發生錯誤,請先到金流後台確認是否已退款:${e.message}`}let c={kind:`refund`,reportId:`${i}:refund`,orderNumber:r,requestId:i,status:o,amount:a,provider:n.provider,message:s.slice(0,300)};return await Je(e,c),c}function k(t,n,r=[`GET`,`POST`]){return{permission:`plugins:manage`,methods:r,handler:async(r,i)=>{let a=e(t,r.input??{});return a.ok?n(a.value,i):{ok:!1,error:`INVALID_INPUT`,detail:a.error}}}}function ut(e,t=`23:59:59`){if(!e)return null;let n=/^(\d{4})[-/]?(\d{2})[-/]?(\d{2})(?:[ T](\d{2}):?(\d{2})(?::?(\d{2}))?)?$/.exec(e.trim());if(!n)return null;let[r,i,a]=t.split(`:`),o=new Date(`${n[1]}-${n[2]}-${n[3]}T${n[4]??r}:${n[5]??i}:${n[6]??a}+08:00`);return Number.isNaN(o.getTime())?null:o.toISOString()}const dt=/^https?:\/\/[A-Za-z0-9.-]+(?::\d{1,5})?$/,ft=/^https?:\/\/[^\s"<>]+$/;function pt(e){return/^\/[0-9A-Z.+-]{7}$/.test(e)}function mt(e){return/^[A-Z]{2}[0-9]{14}$/.test(e)}function ht(e){return/^[0-9]{3,7}$/.test(e)}function gt(e){if(!/^\d{8}$/.test(e))return!1;let t=[1,2,1,2,1,2,4,1],n=0;for(let r=0;r<8;r++){let i=Number(e[r])*t[r];n+=Math.floor(i/10)+i%10}return n%5==0?!0:e[6]===`7`&&(n+1)%5==0}function _t(e){let t=[];return e.kind===`mobile`&&!pt((e.carrierNum??``).trim().toUpperCase())&&t.push(`手機條碼格式為「/」加 7 碼英數字。`),e.kind===`certificate`&&!mt((e.carrierNum??``).trim().toUpperCase())&&t.push(`自然人憑證條碼為 2 碼大寫英文加 14 碼數字。`),e.kind===`donate`&&!ht((e.loveCode??``).trim())&&t.push(`捐贈碼為 3 到 7 碼數字。`),e.kind===`company`&&(gt((e.taxId??``).trim())||t.push(`統一編號格式不正確。`),(e.companyTitle??``).trim()||t.push(`請填寫公司抬頭。`)),t}function vt(e,t,n=`件`){let r=[],i=0;for(let t of e.lines)i+=t.amount,r.push({name:t.name.slice(0,100)||t.sku,quantity:t.quantity,unit:n,unitPrice:t.unitPrice,amount:t.amount});if(e.shippingFee>0&&(i+=e.shippingFee,r.push({name:`運費`,quantity:1,unit:`式`,unitPrice:e.shippingFee,amount:e.shippingFee})),i!==t){let e=t-i;r.push({name:e<0?`折扣`:`其他`,quantity:1,unit:`式`,unitPrice:e,amount:e})}return r}function yt(e){let t=e.lines.reduce((e,t)=>e+t.amount,0),n=Math.min(e.discountTotal,t),r=0;return e.lines.map((i,a)=>{let o=a===e.lines.length-1?n-r:t>0?Math.floor(n*i.amount/t):0;return r+=o,{sku:i.sku,name:i.name,quantity:i.quantity,amount:i.amount-o,temperature:i.temperature,weightKg:i.weightKg}})}function bt(e){let t=e.shipping?.address;return(`${t?.city??``}${t?.line??``}`.trim()||e.shipping?.store?.address||e.invoice?.address?.trim()||`無`).slice(0,100)}function xt(e=new Date){return new Date(e.getTime()+8*36e5).toISOString().replace(`T`,` `).slice(0,19)}function St(e){let t=/^(\d{4})[-/](\d{2})[-/](\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2}))?)?/.exec(e.trim());return t?`${t[1]}-${t[2]}-${t[3]} ${t[4]??`00`}:${t[5]??`00`}:${t[6]??`00`}`:xt()}var A=class extends Error{constructor(e){super(e),this.name=`ProviderError`}};const j=()=>new Date().toISOString();function M(e){return e.storage}async function Ct(e,t){return M(e).orders.get(t)}async function wt(e,t,n={}){let{orders:r}=M(e),i=await r.get(t.number),a={...t,refundedTotal:i?.refundedTotal??0,lastError:i?.lastError??null,invoiceDueAt:i?.invoiceDueAt??null,...n,updatedAt:j()};return await r.put(t.number,a),a}async function N(e,t,n){let{orders:r}=M(e);await r.updateIf(t,{where:{},set:{lastError:n,updatedAt:j()}})}async function Tt(e,t,n){let r=`lock:${t}`,i=await e.kv.getVersioned(r);if(i&&Date.now()-i.value<6e4||!(await e.kv.compareAndSet(r,i?.revision??null,Date.now())).applied)return{busy:!0};try{return await n()}finally{await e.kv.delete(r)}}function Et(e){return!!(e&&typeof e==`object`&&e.busy===!0)}const Dt=new Set([`completed`,`returned`,`failed`,`void`,`unclaimed`]),Ot={created:0,in_progress:1,shipped:2,arrived:3,store_closed:3,unclaimed:4,completed:5,returned:5,failed:5,void:6};function kt(e){return{provider:e.provider,shipmentId:e.shipmentId,carrier:e.carrier,method:e.method,trackingNo:e.trackingNo,trackingUrl:e.trackingUrl,status:e.status,statusText:e.statusText,isCollection:e.isCollection,occurredAt:e.occurredAt}}function At(e){return{kind:`shipment`,reportId:`${e.shipmentId}:${e.status}:${e.occurredAt}`,orderNumber:e.orderNumber,shipment:kt(e)}}async function jt(e,t,n){let{shipments:r}=M(e);for(let i=0;i<8;i++){let i=await r.getVersioned(t);if(!i)return{result:`unknown`};let a=i.value,o={...a.details,...Object.fromEntries(Object.entries(n.details??{}).filter(([,e])=>e))},s=n.trackingNo||a.trackingNo,c=n.providerRef||a.providerRef,l=`duplicate`,u=a.status,d=a.statusText,f=a.statusCode,p=a.statusAt;if(n.status){let e=(n.statusCode??n.status)===(a.statusCode??a.status)&&(n.statusAt??null)===a.statusAt,t=!!(n.statusAt&&a.statusAt&&n.statusAt<a.statusAt),r=Dt.has(a.status)&&Ot[n.status]<Ot[a.status];e?l=`duplicate`:t||r||!a.active?l=`stale`:(l=`applied`,u=n.status,d=n.statusText??d,f=n.statusCode??n.status,p=n.statusAt??null)}let m=s!==a.trackingNo;if(l!==`applied`&&!m&&JSON.stringify(o)===JSON.stringify(a.details)&&c===a.providerRef)return{result:l,shipment:a,report:null};let h=l===`applied`||m,g={...a,status:u,statusText:d,statusCode:f,statusAt:p,trackingNo:s,providerRef:c,details:o,open:a.active&&!Dt.has(u),occurredAt:h?l===`applied`&&p?p:j():a.occurredAt,updatedAt:j()};if(!(await r.compareAndSet(t,i.revision,g)).applied)continue;let _=h?At(g):null;return _&&await Je(e,_),{result:h?l===`applied`?`applied`:`duplicate`:l,shipment:g,report:_}}return{result:`unknown`}}function Mt(e,t){return e.shipping?.provider===t.pluginId&&t.owns(e.shipping.methodId)}function Nt(e,t,n){let r=j();return{provider:e.pluginId,shipmentId:n.shipmentId,carrier:n.carrier,method:t.shipping.methodId,trackingNo:n.trackingNo,trackingUrl:n.trackingUrl??null,status:`created`,statusText:n.statusText??`已建立物流單`,isCollection:n.isCollection,occurredAt:r,orderNumber:t.number,createdAt:r,updatedAt:r,active:!0,open:!0,statusCode:null,statusAt:null,providerRef:n.providerRef,details:Object.fromEntries(Object.entries(n.details??{}).filter(([,e])=>e))}}async function Pt(e,t,n){let r=await Ct(e,n);if(!r)return{ok:!1,error:`NO_ORDER`,detail:`這個外掛沒有這筆訂單的資料(尚未收到訂單事件)。`};if(!Mt(r,t))return{ok:!1,error:`NOT_THIS_PROVIDER`,detail:`這筆訂單不是由這個物流外掛出貨。`};if(!r.paidAt&&!r.shipping?.collect)return{ok:!1,error:`NOT_PAID`,detail:`只有已付款或貨到付款的訂單可以建立物流單。`};if([`cancelled`,`refunded`,`failed`].includes(r.status))return{ok:!1,error:`ORDER_CLOSED`,detail:`訂單已取消或退款。`};let i=await Tt(e,`ship:${n}`,async()=>{let{shipments:i}=M(e),a=(await i.query({where:{orderNumber:n},orderBy:{updatedAt:`desc`},limit:50})).items.map(e=>e.data).filter(e=>e.active&&e.status!==`failed`&&e.status!==`void`),o={ok:!0,shipment:a[0],shipments:a,created:!1,report:null,reports:[]};if(a.length&&!t.multi)return o;let s,c;try{let n=await t.create(e,r,a);s=`shipments`in n?n.shipments:[n],c=`shipments`in n?n.errors:[]}catch(t){let r=t instanceof Error?t.message:String(t);return await N(e,n,`物流單建立失敗:${r}`),{ok:!1,error:`PROVIDER_ERROR`,detail:r}}if(!s.length)return c.length?(await N(e,n,`物流單建立失敗:${c.join(` / `)}`),{ok:!1,error:`PROVIDER_ERROR`,detail:c.join(` / `)}):a.length?o:{ok:!1,error:`NOTHING_TO_SHIP`,detail:`這筆訂單沒有需要建立的物流單。`};let l=[],u=[];for(let n of s){let a=Nt(t,r,n);await i.put(a.shipmentId,a);let o=At(a);await Je(e,o),l.push(a),u.push(o)}return await N(e,n,c.length?`部分物流單建立失敗:${c.join(` / `)}`:null),{ok:!0,shipment:l[0],shipments:[...a,...l],created:!0,report:u[0],reports:u,...c.length?{warning:c.join(` / `)}:{}}});return Et(i)?{ok:!1,error:`BUSY`,detail:`這筆訂單正在建立物流單,請稍後再試。`}:i}async function Ft(e,t,n){await e.kv.set(`map:${t}`,{...n,at:Date.now()})}async function It(e,t){if(!t)return null;let n=await e.kv.get(`map:${t}`);if(!n||(await e.kv.delete(`map:${t}`),Date.now()-Number(n.at)>18e5))return null;let{at:r,...i}=n;return i}async function Lt(e,t,n){let r=await M(e).shipments.get(n);if(!r)return{ok:!1,error:`NOT_FOUND`};let i;try{i=await t.sync(e,r)}catch(e){return{ok:!1,error:`PROVIDER_ERROR`,detail:e instanceof Error?e.message:String(e)}}let a=await jt(e,n,i);return a.result===`unknown`?{ok:!1,error:`NOT_FOUND`}:{ok:!0,shipment:a.shipment,result:a.result}}async function Rt(e,t,n){let{shipments:r}=M(e),i=await r.get(n);if(!i)return{ok:!1,error:`NOT_FOUND`};if(!i.active||i.status===`void`)return{ok:!0,shipment:i};if(Ot[i.status]>=Ot.shipped&&i.status!==`failed`)return{ok:!1,error:`ALREADY_SHIPPED`,detail:`物流單已出貨,無法作廢。`};if(t.cancel)try{await t.cancel(e,i)}catch(e){return{ok:!1,error:`PROVIDER_ERROR`,detail:e instanceof Error?e.message:String(e)}}let a=await jt(e,n,{status:`void`,statusText:t.cancel?`已取消物流單`:`已在商店作廢(物流商端請至其後台取消)`,statusCode:`void`});if(a.result===`unknown`)return{ok:!1,error:`NOT_FOUND`};let o={...a.shipment,active:!1,open:!1,updatedAt:j()};return await r.put(n,o),{ok:!0,shipment:o}}async function zt(e,t,n=20){let r=await M(e).shipments.query({where:{open:!0},orderBy:{updatedAt:`asc`},limit:n}),i=0;for(let{data:n}of r.items)!n.providerRef&&!n.trackingNo||(await Lt(e,t,n.shipmentId)).ok&&i++;return i}function Bt(e){return{provider:e.provider,status:e.status,invoiceNo:e.invoiceNo,invoiceDate:e.invoiceDate,randomNumber:e.randomNumber,kind:e.kind,amount:e.amount,allowanceTotal:e.allowanceTotal,error:e.error,occurredAt:e.occurredAt}}function Vt(e){return{kind:`invoice`,reportId:`${e.orderNumber}:invoice:${e.status}:${e.invoiceNo??`none`}:${e.allowanceTotal}:${e.occurredAt}`,orderNumber:e.orderNumber,invoice:Bt(e)}}async function Ht(e,t){await M(e).invoices.put(t.orderNumber,t);let n=Vt(t);return await Je(e,n),n}function Ut(e){return`${e}I${w(Pe(3)).toUpperCase()}`}async function Wt(e){return(await e.settings.get(`invoiceItemWord`)??``).trim().slice(0,2)||`件`}async function Gt(e,t,n){let r=await Ct(e,n);if(!r)return{ok:!1,error:`NO_ORDER`,detail:`這個外掛沒有這筆訂單的資料(尚未收到訂單事件)。`};if(r.invoice?.provider!==t.pluginId)return{ok:!1,error:`NOT_THIS_PROVIDER`,detail:`這筆訂單不是由這個發票外掛開立。`};if(!r.paidAt&&!r.shipping?.collect)return{ok:!1,error:`NOT_PAID`,detail:`訂單尚未付款,不能開立發票。`};if(!t.kinds.includes(r.invoice.kind))return{ok:!1,error:`UNSUPPORTED_KIND`,detail:`不支援的發票類型:${r.invoice.kind}`};let i=await Tt(e,`invoice:${n}`,async()=>{let{invoices:i}=M(e),a=await i.get(n);if(a?.status===`issued`)return{ok:!0,invoice:a,report:null};let o=r.total-r.refundedTotal;if(o<=0)return{ok:!1,error:`ZERO_AMOUNT`,detail:`訂單金額為 0,不需開立發票。`};let s=a?.status===`failed`&&a.relateNumber?a.relateNumber:t.relateNumber?await t.relateNumber(e,r):Ut(n),c={provider:t.pluginId,status:`issued`,invoiceNo:null,invoiceDate:null,randomNumber:null,kind:r.invoice.kind,amount:o,allowanceTotal:0,error:null,occurredAt:j(),orderNumber:n,relateNumber:s,updatedAt:j(),meta:{},allowances:[]};try{let i=await t.issue(e,r,{relateNumber:s,amount:o,lines:vt(r,o,await Wt(e)),discounted:yt(r),buyerAddress:bt(r)}),a={...c,invoiceNo:i.invoiceNo,invoiceDate:i.invoiceDate,randomNumber:i.randomNumber||null,meta:i.meta??{}},l=await Ht(e,a);return await N(e,n,null),{ok:!0,invoice:a,report:l}}catch(t){let r=(t instanceof Error?t.message:String(t)).slice(0,300),i={...c,status:`failed`,error:r},a=await Ht(e,i);return await N(e,n,`電子發票開立失敗:${r}`),{ok:!1,error:`PROVIDER_ERROR`,detail:r,invoice:i,report:a}}});return Et(i)?{ok:!1,error:`BUSY`,detail:`這筆訂單正在開立發票,請稍後再試。`}:i}async function Kt(e,t,n,r){let{invoices:i}=M(e),a=await i.get(n);if(!a||a.status!==`issued`||!a.invoiceNo)return{ok:!1,error:`NOT_ISSUED`,detail:`這筆訂單沒有已開立的發票。`};let o=await Tt(e,`invoice:${n}`,async()=>{let o=[...a.allowances];try{for(let[n,i]of o.entries())i.status===`issued`&&(await t.voidAllowance(e,a,i,r),o[n]={...i,status:`void`});await t.void(e,a,r)}catch(e){return await i.put(n,{...a,allowances:o,updatedAt:j()}),{ok:!1,error:`PROVIDER_ERROR`,detail:e instanceof Error?e.message:String(e)}}let s={...a,status:`void`,allowances:o,error:null,occurredAt:j(),updatedAt:j()};return{ok:!0,invoice:s,report:await Ht(e,s)}});return Et(o)?{ok:!1,error:`BUSY`}:o}async function qt(e,t,n,r,i){let{invoices:a}=M(e),[o,s]=await Promise.all([a.get(n),Ct(e,n)]);if(!o||o.status!==`issued`||!o.invoiceNo||!s)return{ok:!1,error:`NOT_ISSUED`,detail:`這筆訂單沒有已開立的發票。`};let c=o.amount-o.allowanceTotal;if(r<1||r>c)return{ok:!1,error:`INVALID_AMOUNT`,detail:`折讓金額需介於 1 到 ${c} 元。`};let l=await Tt(e,`invoice:${n}`,async()=>{let n;try{n=(await t.allowance(e,o,s,r,i)).allowanceNo}catch(e){return{ok:!1,error:`PROVIDER_ERROR`,detail:e instanceof Error?e.message:String(e)}}let a={...o,allowanceTotal:o.allowanceTotal+r,allowances:[...o.allowances,{allowanceNo:n,amount:r,status:`issued`,createdAt:j()}],occurredAt:j(),updatedAt:j()};return{ok:!0,invoice:a,report:await Ht(e,a)}});return Et(l)?{ok:!1,error:`BUSY`}:l}const Jt={member:`invoiceChannelMember`,mobile:`invoiceChannelMobile`,certificate:`invoiceChannelCert`,paper:`invoiceChannelPaper`,donate:`invoiceAllowDonate`,company:`invoiceAllowB2b`,defaultCarrier:`invoiceDefaultCarrier`,donateOrgs:`invoiceDonateOrgs`};async function Yt(e,t,n={}){let r=e=>n[e]??Jt[e],i=[];for(let n of[`member`,`mobile`,`certificate`,`paper`])t.includes(n)&&await P(e,r(n),!0)&&i.push(n);!i.length&&t.includes(`paper`)&&i.push(`paper`),t.includes(`donate`)&&await P(e,r(`donate`),!0)&&i.push(`donate`),t.includes(`company`)&&await P(e,r(`company`),!0)&&i.push(`company`);let a=String(await e.settings.get(r(`defaultCarrier`))||`member`).replace(/^cert$/,`certificate`);return{kinds:i,defaultKind:i.find(e=>e===a)??i[0],donateOrgs:String(await e.settings.get(r(`donateOrgs`))??``).split(/\r?\n|;/).map(e=>e.split(`|`).map(e=>e.trim())).filter(([e,t])=>e&&t&&/^\d{3,7}$/.test(t)).map(([e,t])=>({code:t,name:e}))}}async function P(e,t,n){let r=await e.settings.get(t);return r==null?n:r===!0}function Xt(e,t,n,r){return r===`completed`?e===`shipment.updated`&&t.shipment?.status===`completed`:e===`order.paid`&&!n||e===`order.created`&&n}async function Zt(e,t,n){let r=Math.max(0,Math.min(30,Math.floor(Number(await e.settings.get(`invoiceDelayDays`))||0)));return r>0?(await M(e).orders.updateIf(n,{where:{},set:{invoiceDueAt:new Date(Date.now()+r*864e5).toISOString(),updatedAt:j()}}),null):(await Gt(e,t,n)).report??null}async function Qt(e,t,n=20){let{orders:r}=M(e),i=await r.query({where:{invoiceDueAt:{lte:j()}},orderBy:{invoiceDueAt:`asc`},limit:n}),a=[];for(let{id:n}of i.items){await r.updateIf(n,{where:{},set:{invoiceDueAt:null,updatedAt:j()}});let i=await Gt(e,t,n);i.report&&a.push(i.report)}return a}async function $t(t,n,r){let i=e(ce,n.payload.order);if(!i.ok)return{handled:!1,reports:[]};let a=i.value,o=r.logistics&&Mt(a,r.logistics)?r.logistics:void 0,s=r.invoice&&a.invoice?.provider===r.invoice.pluginId?r.invoice:void 0;if(!o&&!s)return{handled:!1,reports:[]};let c=n.type===`order.refunded`?Math.max(0,Number(n.payload.refundedTotal)||0):void 0;await wt(t,a,c===void 0?{}:{refundedTotal:c});let l=[],u=e=>{e&&l.push(e)},d=!!a.shipping?.collect;if(o&&await P(t,`logisticsAutoShip`,!1)&&(!o.usable||await o.usable(t))&&(n.type===`order.created`&&d||n.type===`order.paid`&&!d)){let e=await Pt(t,o,a.number);e.ok&&l.push(...e.reports)}if(s){let e=(await t.settings.get(`invoiceIssueWhen`)??`paid`)===`completed`?`completed`:`paid`,r=n.type===`order.cancelled`||n.type===`order.refunded`&&n.payload.full===!0;if(await P(t,`invoiceAuto`,!0)&&Xt(n.type,n.payload,d,e))u(await Zt(t,s,a.number));else if(r&&await P(t,`invoiceAutoVoid`,!1)){await M(t).orders.updateIf(a.number,{where:{},set:{invoiceDueAt:null}});let e=n.type===`order.cancelled`?`訂單取消(自動作廢)`:`訂單退款(自動作廢)`,r=await Kt(t,s,a.number,e);r.ok&&u(r.report)}else if(n.type===`order.refunded`&&n.payload.full!==!0&&await P(t,`invoiceAutoAllowance`,!1)){let e=Math.floor(Number(n.payload.amount)||0);if(e>0){let n=await qt(t,s,a.number,e,`部分退款`);n.ok&&u(n.report)}}r&&await M(t).orders.updateIf(a.number,{where:{},set:{invoiceDueAt:null}})}return{handled:!0,reports:l}}const en=/^https?:\/\/[A-Za-z0-9.-]+(?::\d{1,5})?$/;async function tn(e){let t=(await e.settings.get(`siteUrl`)??``).trim().replace(/\/$/,``);return en.test(t)?t:await e.kv.get(`site:origin`)??null}async function nn(e,t){!t||!en.test(t)||await e.kv.get(`site:origin`)!==t&&await e.kv.set(`site:origin`,t)}const rn=p({temperatures:g(f(u([`normal`,`refrigerated`,`frozen`]),{max:200}),[])});async function an(e,t,n){let r=je(C(JSON.stringify(n))),i=await ze(await e.settings.get(`busKey`)??``,`store:${t}:${r}`);return`/shop/checkout?${new URLSearchParams({shipping:t,store:`${r}.${i}`})}`}const on=p({orderNumber:i({min:1,max:20,pattern:/^[A-Z0-9]+$/})}),sn=p({shipmentId:i({min:1,max:64})}),cn=p({orderNumber:h(i({max:20})),limit:g(o({min:1,max:100,coerce:!0}),20)}),ln=p({orderNumber:i({min:1,max:20,pattern:/^[A-Z0-9]+$/}),reason:g(i({min:1,max:50}),`訂單取消`)}),un=p({orderNumber:i({min:1,max:20,pattern:/^[A-Z0-9]+$/}),amount:o({min:1,max:99999999,coerce:!0}),reason:g(i({min:1,max:50}),`折讓`)});function F(e){return{...kt(e),orderNumber:e.orderNumber,active:e.active,statusCode:e.statusCode,providerRef:e.providerRef,details:e.details,createdAt:e.createdAt}}function dn(e){return{...Bt(e),orderNumber:e.orderNumber,relateNumber:e.relateNumber,allowances:e.allowances}}function fn(e){return{[S.create]:k(on,async(t,n)=>{let r=await Pt(n,e,t.orderNumber);return r.ok?{ok:!0,created:r.created,shipment:F(r.shipment),shipments:r.shipments.map(F),...r.warning?{warning:r.warning}:{}}:r},[`POST`]),[S.list]:k(cn,async(e,t)=>({ok:!0,shipments:(await M(t).shipments.query({where:e.orderNumber?{orderNumber:e.orderNumber}:void 0,orderBy:{updatedAt:`desc`},limit:e.limit})).items.map(e=>F(e.data))})),[S.sync]:k(sn,async(t,n)=>{let r=await Lt(n,e,t.shipmentId);return r.ok?{ok:!0,result:r.result,shipment:F(r.shipment)}:r}),[S.print]:k(sn,async(t,n)=>{let r=await M(n).shipments.get(t.shipmentId);if(!r)return{ok:!1,error:`NOT_FOUND`};try{return{ok:!0,...await e.print(n,r)}}catch(e){return{ok:!1,error:`PROVIDER_ERROR`,detail:e instanceof Error?e.message:String(e)}}}),[S.syncAll]:k(p({}),async(t,n)=>({ok:!0,synced:await zt(n,e,100)}),[`POST`]),[S.void]:k(sn,async(t,n)=>{let r=await Rt(n,e,t.shipmentId);return r.ok?{ok:!0,shipment:F(r.shipment)}:r},[`POST`])}}function pn(e){let t=e=>e.ok?{ok:!0,invoice:dn(e.invoice)}:{ok:!1,error:e.error,detail:e.detail,...e.invoice?{invoice:dn(e.invoice)}:{}};return{[Te.issue]:k(on,async(n,r)=>t(await Gt(r,e,n.orderNumber)),[`POST`]),[Te.get]:k(on,async(e,t)=>{let n=await M(t).invoices.get(e.orderNumber);return n?{ok:!0,invoice:dn(n)}:{ok:!1,error:`NOT_FOUND`}}),[Te.void]:k(ln,async(n,r)=>t(await Kt(r,e,n.orderNumber,n.reason)),[`POST`]),[Te.allowance]:k(un,async(n,r)=>t(await qt(r,e,n.orderNumber,n.amount,n.reason)),[`POST`]),"invoices/due":k(p({}),async(t,n)=>({ok:!0,reports:await Qt(n,e)}),[`POST`]),"invoices/query":k(on,async(t,n)=>{let r=await M(n).invoices.get(t.orderNumber);if(!r?.relateNumber||!r.invoiceNo)return{ok:!1,error:`NOT_ISSUED`};if(!e.query)return{ok:!1,error:`UNSUPPORTED`};try{return{ok:!0,result:await e.query(n,r)}}catch(e){return{ok:!1,error:`PROVIDER_ERROR`,detail:e instanceof Error?e.message:String(e)}}})}}const mn=p({kind:u([`member`,`mobile`,`certificate`,`paper`,`donate`,`company`]),carrierNum:h(i({max:64})),loveCode:h(i({max:10})),taxId:h(i({max:8})),companyTitle:h(i({max:100}))});function hn(e,t,n){return{"bus/invoice/options":D(`bus/invoice/options`,p({}),async(r,i)=>{if(!await n(i))return{ok:!0,provider:e.pluginId,label:t,kinds:[]};let a=e.checkoutOptions?await e.checkoutOptions(i):{kinds:[...e.kinds]};return{ok:!0,provider:e.pluginId,label:t,...a}}),"bus/invoice/verify":D(`bus/invoice/verify`,mn,async(t,n)=>{if(!(e.checkoutOptions?(await e.checkoutOptions(n)).kinds:e.kinds).includes(t.kind))return{ok:!0,valid:!1,errors:[`不支援的發票類型:${t.kind}`]};let r=_t(t);if(r.length)return{ok:!0,valid:!1,errors:r};if(e.verifyOnline&&await P(n,`invoiceVerifyOnline`,!0))try{let r=await e.verifyOnline(n,{kind:t.kind,carrierNum:t.carrierNum?.trim().toUpperCase(),loveCode:t.loveCode?.trim()});if(r)return{ok:!0,valid:!1,errors:[r]}}catch{}return{ok:!0,valid:!0,errors:[]}})}}const gn={created:`已建立`,in_progress:`處理中`,shipped:`已出貨`,arrived:`已到店`,completed:`已完成`,store_closed:`門市異常`,unclaimed:`逾期未取`,returned:`已退回`,failed:`失敗`,void:`已作廢`},_n={issued:`已開立`,failed:`開立失敗`,void:`已作廢`};async function vn(e,t,n,r){let{shipments:i,orders:a}=M(e),[o,s,c]=await Promise.all([i.query({orderBy:{updatedAt:`desc`},limit:30}),i.count({open:!0}),a.query({orderBy:{updatedAt:`desc`},limit:50})]),l=c.items.map(e=>e.data).filter(e=>e.lastError?.startsWith(`物流`));return{blocks:[{type:`header`,text:t},{type:`stats`,items:[...n,{label:`未完成的物流單`,value:String(s)}]},...r?[{type:`context`,text:r}]:[],...l.length?[{type:`table`,columns:[{key:`order`,label:`訂單`},{key:`error`,label:`最近的自動建單錯誤`}],rows:l.slice(0,10).map(e=>({order:e.number,error:e.lastError??``})),page_action_id:`browse_errors`}]:[],{type:`table`,columns:[{key:`shipmentId`,label:`物流單`},{key:`orderNumber`,label:`訂單`},{key:`carrier`,label:`物流`},{key:`trackingNo`,label:`追蹤號碼`},{key:`status`,label:`狀態`},{key:`updatedAt`,label:`更新時間`}],rows:o.items.map(({data:e})=>({shipmentId:e.shipmentId,orderNumber:e.orderNumber,carrier:`${e.carrier}${e.isCollection?`(代收)`:``}`,trackingNo:e.trackingNo??``,status:`${gn[e.status]??e.status}${e.statusText?`:${e.statusText}`:``}`,updatedAt:e.updatedAt.slice(0,16).replace(`T`,` `)})),page_action_id:`browse_shipments`,empty_text:`尚無物流單。`}]}}async function yn(e,t,n){let{invoices:r}=M(e),[i,a,o]=await Promise.all([r.query({orderBy:{updatedAt:`desc`},limit:30}),r.count({status:`issued`}),r.count({status:`failed`})]);return{blocks:[{type:`header`,text:t},{type:`stats`,items:[...n,{label:`已開立`,value:String(a)},{label:`開立失敗`,value:String(o)}]},{type:`table`,columns:[{key:`orderNumber`,label:`訂單`},{key:`invoiceNo`,label:`發票號碼`},{key:`amount`,label:`金額`},{key:`status`,label:`狀態`},{key:`date`,label:`開立時間`}],rows:i.items.map(({data:e})=>({orderNumber:e.orderNumber,invoiceNo:e.invoiceNo??``,amount:`NT$${e.amount}${e.allowanceTotal?`(折讓 NT$${e.allowanceTotal})`:``}`,status:`${_n[e.status]??e.status}${e.error?`:${e.error}`:``}`,date:e.invoiceDate??``})),page_action_id:`browse_invoices`,empty_text:`尚無發票。`}]}}const bn={import:`migrate/import`,kv:`migrate/kv`,stats:`migrate/stats`};function xn(e,t=[]){let n=p({collection:u(e),mode:g(u([`create`,`replace`]),`create`),items:f(p({id:i({min:1,max:300}),data:m(c(),{maxKeys:200})}),{min:1,max:100})}),r=p({mode:g(u([`create`,`replace`]),`create`),items:f(p({key:i({min:1,max:300}),value:c()}),{min:1,max:100})}),a=(e,t)=>e.storage[t];return{[bn.import]:k(n,async(e,t)=>{let n=a(t,e.collection),r=0,i=0;for(let t of e.items)e.mode===`replace`?(await n.put(t.id,t.data),r++):(await n.compareAndSet(t.id,null,t.data)).applied?r++:i++;return{ok:!0,created:r,skipped:i}},[`POST`]),[bn.kv]:k(r,async(e,n)=>{let r=0,i=0;for(let n of e.items)if(!t.some(e=>n.key.startsWith(e)))return{ok:!1,error:`INVALID_INPUT`,detail:`key ${n.key}`};for(let t of e.items)e.mode===`replace`?(await n.kv.set(t.key,t.value),r++):(await n.kv.compareAndSet(t.key,null,t.value)).applied?r++:i++;return{ok:!0,created:r,skipped:i}},[`POST`]),[bn.stats]:k(p({}),async(t,n)=>({ok:!0,counts:Object.fromEntries(await Promise.all(e.map(async e=>[e,await a(n,e).count()])))}),[`GET`,`POST`])}}function Sn(e){if(![16,24,32].includes(e.length))throw Error(`AES key must be 16, 24 or 32 bytes, got ${e.length}`)}function Cn(e,t,n){return Sn(e),crypto.subtle.importKey(`raw`,T(e),{name:t},!1,n)}function wn(e,t,n=16){if(t===`none`){if(e.length%n!==0)throw Error(`Data length must be a multiple of the block size without padding`);return e}if(t===`zero`){let t=e.length%n;return t===0&&e.length>0?e:Me(e,new Uint8Array(n-t))}let r=n-e.length%n;return Me(e,new Uint8Array(r).fill(r))}function Tn(e,t){if(t===`none`)return e;if(t===`zero`){let t=e.length;for(;t>0&&e[t-1]===0;)t--;return e.slice(0,t)}let n=e[e.length-1];if(n===void 0||n===0||n>e.length)return null;for(let t=e.length-n;t<e.length;t++)if(e[t]!==n)return null;return e.slice(0,e.length-n)}async function En(e,t,n){return new Uint8Array(await crypto.subtle.encrypt({name:`AES-CBC`,iv:T(t)},e,T(n))).slice(0,n.length)}async function Dn(e,t,n){let r=n.slice(n.length-16),i=new Uint8Array(await crypto.subtle.encrypt({name:`AES-CBC`,iv:T(r)},e,new ArrayBuffer(0)));return new Uint8Array(await crypto.subtle.decrypt({name:`AES-CBC`,iv:T(t)},e,T(Me(n,i))))}async function On(e,t,n,r=`pkcs7`){let i=C(t);if(i.length!==16)throw Error(`AES-CBC IV must be 16 bytes`);return En(await Cn(C(e),`AES-CBC`,[`encrypt`]),i,wn(C(n),r))}async function kn(e,t,n,r=`pkcs7`){let i=C(t);if(i.length!==16)throw Error(`AES-CBC IV must be 16 bytes`);return n.length===0||n.length%16!=0?null:Tn(await Dn(await Cn(C(e),`AES-CBC`,[`encrypt`,`decrypt`]),i,n),r)}function An(e){return Object.fromEntries(new URLSearchParams(e))}function jn(e){return encodeURIComponent(e).replace(/[!'()*~]/g,e=>`%`+e.charCodeAt(0).toString(16).toUpperCase()).replace(/%20/g,`+`)}const I=`methodLabels`,Mn={name:100,desc:300};function Nn(e,t){return String(e??``).trim().slice(0,Mn[t])}async function L(e,t){let n=await e.kv.get(I);if(n)return n;let r=new Set(t),i=[...await e.settings.list(`name`),...await e.settings.list(`desc`)].filter(e=>r.has(e.key.slice(4))),a={};for(let{key:e,value:t}of i){let n=e.startsWith(`name`)?`name`:`desc`,r=Nn(t,n);r&&(a[e.slice(4)]={...a[e.slice(4)],[n]:r})}let o=await e.kv.compareAndSet(I,null,a);for(let{key:t}of i)await e.settings.delete(t);return o.applied?a:await e.kv.get(I)??a}function Pn(e,t,n){let r=e[t]??{};return{code:t,label:r.name||n,...r.desc?{description:r.desc}:{}}}const Fn=`label:`;function In(e,t,n,r){return{blocks:[{type:`header`,text:`${e}:付款方式名稱與說明`},{type:`section`,text:`結帳頁顯示的付款方式名稱與說明。名稱留空時用預設名稱,說明留空不顯示。付款方式的開關在外掛設定。`},...t.map(e=>({type:`form`,block_id:`label-${e.code}`,fields:[{type:`text_input`,action_id:`name`,label:`「${e.label}」顯示名稱`,placeholder:e.label,initial_value:n[e.code]?.name??``},{type:`text_input`,action_id:`desc`,label:`「${e.label}」說明`,multiline:!0,initial_value:n[e.code]?.desc??``}],submit:{label:`儲存「${e.label}」`,action_id:`${Fn}${e.code}`}}))],...r?{toast:r}:{}}}async function Ln(e,t,n,r,i){if(!r.startsWith(Fn))return null;let a=n.find(e=>e.code===r.slice(6)),o=n.map(e=>e.code);if(!a)return In(t,n,await L(e,o),{message:`沒有這個付款方式`,type:`error`});await L(e,o);let s={name:Nn(i.name,`name`),desc:Nn(i.desc,`desc`)};for(let r=0;r<5;r++){let r=await e.kv.getVersioned(I),i={...r?.value};if(s.name||s.desc?i[a.code]={...s.name?{name:s.name}:{},...s.desc?{desc:s.desc}:{}}:delete i[a.code],(await e.kv.compareAndSet(I,r?.revision??null,i)).applied)return In(t,n,i,{message:`已儲存「${a.label}」`,type:`success`})}return In(t,n,await L(e,o),{message:`儲存失敗,請再試一次`,type:`error`})}async function R(e,t){return await e.kv.get(t.key)??{}}const Rn=(e,t)=>e.fieldsFor?.(t)??e.fields;function z(e,t,n,r){return{blocks:[{type:`header`,text:e.heading},{type:`section`,text:e.intro},...t.map(t=>({type:`form`,block_id:`${e.action}${t.code}`,fields:Rn(e,t).map(e=>({type:`text_input`,action_id:e.id,label:e.label(t),...e.kind===`multiline`?{multiline:!0}:{},...e.placeholder?{placeholder:e.placeholder(t)}:{},initial_value:n[t.code]?.[e.id]===void 0?``:String(n[t.code][e.id])})),submit:{label:`儲存「${t.label}」`,action_id:`${e.action}${t.code}`}}))],...r?{toast:r}:{}}}function zn(e,t){let n=String(t??``).trim();if(n)return e.kind===`amount`?/^\d{1,8}$/.test(n)?Number(n):null:n.slice(0,e.max??300)}async function Bn(e,t,n,r,i){if(!r.startsWith(t.action))return null;let a=n.find(e=>e.code===r.slice(t.action.length));if(!a)return z(t,n,await R(e,t),{message:`沒有這個${t.noun}`,type:`error`});let o={};for(let r of Rn(t,a)){let s=zn(r,i[r.id]);if(s===null)return z(t,n,await R(e,t),{message:`「${r.label(a)}」請填 0 以上的整數,或留空`,type:`error`});s!==void 0&&(o[r.id]=s)}for(let r=0;r<5;r++){let r=await e.kv.getVersioned(t.key),i={...r?.value};if(Object.keys(o).length?i[a.code]=o:delete i[a.code],(await e.kv.compareAndSet(t.key,r?.revision??null,i)).applied)return z(t,n,i,{message:`已儲存「${a.label}」`,type:`success`})}return z(t,n,await R(e,t),{message:`儲存失敗,請再試一次`,type:`error`})}function Vn(e){let t=e??{};return t.type!==`form_submit`||typeof t.action_id!=`string`?null:{actionId:t.action_id,values:t.values&&typeof t.values==`object`?t.values:{}}}const Hn=[7,12,17,22,5,9,14,20,4,11,16,23,6,10,15,21],Un=Array.from({length:64},(e,t)=>Math.floor(Math.abs(Math.sin(t+1))*2**32)>>>0);function Wn(e,t){return(e<<t|e>>>32-t)>>>0}function Gn(e){let t=C(e),n=t.length*8,r=new Uint8Array((t.length+8>>6)+1<<6);r.set(t),r[t.length]=128;let i=new DataView(r.buffer);i.setUint32(r.length-8,n>>>0,!0),i.setUint32(r.length-4,Math.floor(n/2**32),!0);let a=1732584193,o=4023233417,s=2562383102,c=271733878,l=Array(16);for(let e=0;e<r.length;e+=64){for(let t=0;t<16;t++)l[t]=i.getUint32(e+t*4,!0);let t=a,n=o,r=s,u=c;for(let e=0;e<64;e++){let i,a;e<16?(i=n&r|~n&u,a=e):e<32?(i=u&n|~u&r,a=(5*e+1)%16):e<48?(i=n^r^u,a=(3*e+5)%16):(i=r^(n|~u),a=7*e%16);let o=t+i+Un[e]+l[a]>>>0;t=u,u=r,r=n,n=n+Wn(o,Hn[(e>>4)*4+e%4])>>>0}a=a+t>>>0,o=o+n>>>0,s=s+r>>>0,c=c+u>>>0}let u=new Uint8Array(16),d=new DataView(u.buffer);return d.setUint32(0,a,!0),d.setUint32(4,o,!0),d.setUint32(8,s,!0),d.setUint32(12,c,!0),u}function Kn(e){return w(Gn(e))}const qn={merchantId:`3002607`,hashKey:`pwFHCqoQZGmho4w6`,hashIv:`EkRm7iFT261dpevs`},Jn={stage:`payment-stage.ecpay.com.tw`,production:`payment.ecpay.com.tw`},Yn={"%2d":`-`,"%5f":`_`,"%2e":`.`,"%21":`!`,"%2a":`*`,"%28":`(`,"%29":`)`};function Xn(e,t,n){return jn(`HashKey=${t}&${Object.keys(e).filter(e=>e!==`CheckMacValue`).sort((e,t)=>{let n=e.toLowerCase(),r=t.toLowerCase();return n<r?-1:n>r?1:0}).map(t=>`${t}=${e[t]}`).join(`&`)}&HashIV=${n}`).toLowerCase().replace(/%2d|%5f|%2e|%21|%2a|%28|%29/g,e=>Yn[e])}async function B(e,t,n,r=`sha256`){let i=Xn(e,t,n);return(r===`md5`?Kn(i):await Le(i)).toUpperCase()}async function Zn(e,t,n,r=`sha256`){let i=e.CheckMacValue;return!i||!t||!n?!1:Ne(i.toUpperCase(),await B(e,t,n,r))}function Qn(e=new Date){let t=new Date(e.getTime()+8*36e5).toISOString();return`${t.slice(0,4)}/${t.slice(5,7)}/${t.slice(8,10)} ${t.slice(11,19)}`}function $n(e){let t=e.map(e=>e.replace(/#/g,`#`)).join(`#`);return t.length>400?`${t.slice(0,397)}...`:t}const er=`moksa-ecpay`,tr={normal:1,refrigerated:2,frozen:3},nr={normal:`常溫`,refrigerated:`冷藏`,frozen:`冷凍`},rr=[`normal`,`refrigerated`,`frozen`],ir={C2C:{merchantId:`2000933`,hashKey:`XBERn1YOvpM9nfZc`,hashIv:`h1ONHk4P4yqbl5LK`},B2C:{merchantId:`2000132`,hashKey:`5294y06JbISpM5x9`,hashIv:`v77hoKGq4kWxNNIS`}},ar={stage:`logistics-stage.ecpay.com.tw`,production:`logistics.ecpay.com.tw`},or=rr,V=[`normal`],H=[{id:`ecpay-cvs-unimart`,setting:`cvsUnimart`,label:`7-ELEVEN 超商取貨`,kind:`cvs`,subtype:{C2C:`UNIMARTC2C`,B2C:`UNIMART`},carrier:`7-ELEVEN`,temps:{C2C:V,B2C:[`normal`,`frozen`]},enabledByDefault:!0},{id:`ecpay-cvs-fami`,setting:`cvsFami`,label:`全家超商取貨`,kind:`cvs`,subtype:{C2C:`FAMIC2C`,B2C:`FAMI`},carrier:`全家`,temps:{C2C:V,B2C:V},enabledByDefault:!0},{id:`ecpay-cvs-hilife`,setting:`cvsHilife`,label:`萊爾富超商取貨`,kind:`cvs`,subtype:{C2C:`HILIFEC2C`,B2C:`HILIFE`},carrier:`萊爾富`,temps:{C2C:V,B2C:V},enabledByDefault:!1},{id:`ecpay-cvs-okmart`,setting:`cvsOkmart`,label:`OK 超商取貨`,kind:`cvs`,subtype:{C2C:`OKMARTC2C`,B2C:`OKMART`},carrier:`OK`,temps:{C2C:V,B2C:V},enabledByDefault:!1},{id:`ecpay-cvs-unimart-freeze`,setting:`cvsUnimartFreeze`,label:`7-ELEVEN 冷凍取貨`,kind:`cvs`,subtype:{C2C:`UNIMARTFREEZE`,B2C:`UNIMARTFREEZE`},carrier:`7-ELEVEN 冷凍`,temps:{C2C:[`frozen`],B2C:[`frozen`]},enabledByDefault:!1},{id:`ecpay-home-tcat`,setting:`homeTcat`,label:`黑貓宅急便`,kind:`home`,subtype:{C2C:`TCAT`,B2C:`TCAT`},carrier:`黑貓宅急便`,temps:{C2C:or,B2C:or},enabledByDefault:!0},{id:`ecpay-home-post`,setting:`homePost`,label:`中華郵政`,kind:`home`,subtype:{C2C:`POST`,B2C:`POST`},carrier:`中華郵政`,temps:{C2C:V,B2C:V},enabledByDefault:!1}];function sr(e){return H.find(t=>t.id===e)}function U(e){return e.endsWith(`C2C`)?`C2C`:`B2C`}function cr(e,t){return e===`UNIMART`&&t===`frozen`?`UNIMARTFREEZE`:e}function lr(e){let t=e.trim(),n=t.startsWith(`1|`);if(!/^[01]\|/.test(t))return{ok:!1,message:t.slice(0,200),fields:{}};let r=t.slice(2);if(!n)return{ok:!1,message:r,fields:{}};let i=Object.fromEntries(new URLSearchParams(r));return{ok:n,message:i.RtnMsg??``,fields:i}}async function ur(e,t,n){let r=e.trim();if(/^[01]\|/.test(r))return lr(r);if(!r.includes(`CheckMacValue=`))return{ok:!1,message:r.slice(0,200),fields:{}};let i=Object.fromEntries(new URLSearchParams(r));return await Zn(i,t,n,`md5`)?{ok:!0,message:``,fields:i}:{ok:!1,message:`CheckMacValue mismatch`,fields:{}}}const dr={shipped:[`2030`,`2068`,`3001`,`3006`,`3024`,`3032`,`3119`,`3301`,`3312`],arrived:[`2063`,`2073`,`3018`,`3029`,`3314`],completed:[`2067`,`3022`,`3003`,`3307`,`3308`,`3309`],store_closed:[`2016`,`2026`,`2037`,`2049`,`2050`,`2101`,`2104`],unclaimed:`2065.2074.2076.2078.2079.2080.2081.2082.2083.2084.2085.2086.2087.2088.2089.2092.2093.2072.2044.2070.3020.3025.3019.3031.3023.3310`.split(`.`),failed:[`2033`,`2034`,`2042`,`3303`,`3305`,`3306`,`4004`,`5001`,`5002`]};function fr(e){for(let[t,n]of Object.entries(dr))if(n.includes(e))return t;return`in_progress`}function pr(e){let t=e.replace(/[\^'`!@#$%&*+\\"<>|_[\]]/gu,` `).replace(/\s+/gu,` `).trim(),n=0,r=``;for(let e of t){let t=/[ᄀ-ᅟ⺀-꓏가-힣豈-﫿︰-﹏＀-⦆¢-₩]|[\u{20000}-\u{3fffd}]/u.test(e)?2:1;if(n+t>50)break;n+=t,r+=e}return r.trim()||`網路商品一批`}async function W(e,t){return String(await e.settings.get(t)??``).trim()}async function mr(e,t,n){let r=await e.settings.get(t);if(r==null||r===``)return n;let i=Number(r);return Number.isFinite(i)&&i>=0?i:n}async function hr(e,t,n){let r=await e.settings.get(t);return r==null?n:r===!0}async function gr(e){return await W(e,`logisticsEnvironment`)!==`production`}async function G(e,t){let n=await gr(e),r=t===`C2C`?`C2c`:`B2c`,i=await W(e,`logistics${r}MerchantId`),a=await e.settings.get(`logistics${r}HashKey`)??``,o=await e.settings.get(`logistics${r}HashIv`)??``;return i&&a&&o?{merchantId:i,hashKey:a,hashIv:o,stage:n}:n?{...ir[t],stage:n}:{merchantId:i,hashKey:a,hashIv:o,stage:n}}async function _r(e,t){let n=await G(e,t);return!!(n.merchantId&&n.hashKey&&n.hashIv)}function vr(e){return`https://${e?ar.stage:ar.production}`}async function K(e){return hr(e,`logisticsEnabled`,!1)}async function q(e){return await W(e,`logisticsContract`)===`B2C`?`B2C`:`C2C`}async function yr(e,t){return await hr(e,t.setting,t.enabledByDefault)?t.id===`ecpay-cvs-unimart-freeze`?await q(e)===`B2C`:t.id===`ecpay-cvs-okmart`?await q(e)===`C2C`:!0:!1}async function br(e,t){return t.temps[await q(e)]}const xr=H.map(e=>({code:e.id,label:e.label})),Sr=e=>H.some(t=>t.id===e&&[...t.temps.C2C,...t.temps.B2C].some(e=>e!==`normal`)),Cr=e=>H.some(t=>t.id===e&&![...t.temps.C2C,...t.temps.B2C].includes(`normal`)),wr=[{id:`name`,label:e=>`「${e.label}」顯示名稱`,kind:`text`,max:100,placeholder:e=>e.label},{id:`desc`,label:e=>`「${e.label}」說明`,kind:`multiline`,max:300},{id:`fee`,label:e=>`「${e.label}」運費(常溫)`,kind:`amount`,placeholder:e=>e.code.includes(`-cvs-`)?`留空:用外掛設定的超商取貨運費`:`留空:用外掛設定的宅配運費`},{id:`coldFee`,label:e=>`「${e.label}」低溫運費(購物車含冷藏或冷凍時)`,kind:`amount`,placeholder:()=>`留空:用外掛設定的低溫配送運費`},{id:`freeMin`,label:e=>`「${e.label}」免運門檻`,kind:`amount`,placeholder:()=>`留空:用商店的全站免運門檻;0 為一律免運`},{id:`spec`,label:e=>`「${e.label}」包裹尺寸(1=60 公分、2=90 公分、3=120 公分)`,kind:`amount`,placeholder:()=>`留空:1(60 公分)`}],J={key:`shippingMethods`,heading:`綠界物流:物流方式設定`,intro:`結帳頁顯示的物流方式名稱、說明、運費與免運門檻。留空的欄位沿用外掛設定的運費與商店的全站免運門檻。物流方式的開關在外掛設定。`,noun:`物流方式`,action:`ship:`,fields:wr,fieldsFor:e=>wr.filter(t=>(t.id!==`coldFee`||Sr(e.code))&&(t.id!==`fee`||!Cr(e.code))&&(t.id!==`spec`||e.code===`ecpay-home-tcat`))};async function Tr(e,t){if(!await K(e))return[];let n=[...new Set((t.length?t:[`normal`]).map(e=>e===`refrigerated`||e===`frozen`?e:`normal`))],r=n.some(e=>e!==`normal`),i=await R(e,J),a=[];for(let t of H){if(!await yr(e,t))continue;let o=await br(e,t);if(!n.every(e=>o.includes(e)))continue;let s=i[t.id]??{},c=e=>typeof s[e]==`number`?s[e]:void 0,l=c(r?`coldFee`:`fee`)??await mr(e,r?`coldFee`:t.kind===`cvs`?`cvsFee`:`homeFee`,r?160:t.kind===`cvs`?60:100),u=c(`freeMin`);a.push({id:t.id,label:typeof s.name==`string`&&s.name?s.name:t.label,...typeof s.desc==`string`&&s.desc?{description:s.desc}:{},...u===void 0?{}:{freeMin:u},carrier:t.carrier,requires:t.kind===`cvs`?`store`:`address`,fee:Math.floor(l),collect:t.id!==`ecpay-home-post`,maxAmount:null,...t.kind===`cvs`?{mapUrl:`/api/shop/ecpay/map?shipping=${encodeURIComponent(t.id)}&sbx=1`}:{}})}return a}const Y=(e,t)=>({ok:!0,response:{kind:`text`,status:e,body:t}});function Er(e){return`${e}L${w(Pe(3)).toUpperCase()}`.slice(0,20)}async function Dr(e,t){let n=new URLSearchParams(t.query);if(n.get(`sbx`)!==`1`)return{...Y(404,`not ours`),passThrough:!0};let r=sr(n.get(`shipping`)??``);if(!r||r.kind!==`cvs`||!await K(e)||!await yr(e,r))return Y(400,`不支援的運送方式。`);let i=await tn(e);if(!i)return Y(503,`尚未設定網站網址。`);let a=r.subtype[await q(e)],o=await G(e,U(a)),s=Er(`MAP`);await Ft(e,s,{methodId:r.id});let c={MerchantID:o.merchantId,MerchantTradeNo:s,LogisticsType:`CVS`,LogisticsSubType:a,IsCollection:n.get(`cod`)===`1`?`Y`:`N`,ServerReplyURL:`${i}/api/shop/ecpay/map-reply`,ExtraData:``,Device:n.get(`mobile`)===`1`?`1`:`0`};return c.CheckMacValue=await B(c,o.hashKey,o.hashIv,`md5`),{ok:!0,response:{kind:`form`,title:`正在開啟門市地圖…`,action:`${vr(o.stage)}/Express/map`,params:c}}}async function Or(e,t){let n=An(t.body),r=await It(e,n.MerchantTradeNo??``);if(!r)return{ok:!0,passThrough:!0,response:{kind:`text`,status:403,body:`Unknown trade`}};let i=sr(r.methodId??``),a=(n.CVSStoreID??``).trim();if(!i||!a||a.length>20)return{ok:!0,response:{kind:`redirect`,status:303,location:`/shop/checkout?store_error=1`}};let o={provider:er,type:n.LogisticsSubType||i.subtype[await q(e)],id:a,name:(n.CVSStoreName??``).slice(0,100),address:(n.CVSAddress??``).slice(0,200)};return{ok:!0,response:{kind:`redirect`,status:303,location:await an(e,i.id,o)}}}function kr(e,t){let n=t.includes(`normal`)?`normal`:t[0]??`normal`,r=new Map;for(let i of yt(e)){let a=i.temperature??e.shipping?.temperature??`normal`;t.includes(a)||(a=n);let o=r.get(a)??{temp:a,amount:0,names:[],weightKg:0};o.amount+=i.amount,o.names.push(i.name),o.weightKg+=(i.weightKg??0)*i.quantity,r.set(a,o)}let i=rr.filter(e=>r.has(e)).map(e=>r.get(e));return i[0]&&e.shippingFee>0&&(i[0].amount+=e.shippingFee),i}async function Ar(e,t){return(await W(e,`senderZipcode`)).replace(/\D/g,``)||(/^\s*(\d{3,6})/.exec(t)?.[1]??``)}function jr(e,t){let n=0,r=``;for(let i of e){let e=/[ᄀ-ᅟ⺀-꓏가-힣豈-﫿︰-﹏＀-⦆¢-₩]|[\u{20000}-\u{3fffd}]/u.test(i)?2:1;if(n+e>t)break;n+=e,r+=i}return r}async function Mr(e,t,n,r,i,a=new Date){let o=t.shipping,s=![`TCAT`,`POST`].includes(i),c=o.collect&&n.amount>0,l=s&&!c?Math.min(2e4,Math.max(1,n.amount)):Math.max(1,n.amount),u=await G(e,U(i)),d=await tn(e);if(!d)throw new A(`尚未設定網站網址(siteUrl),綠界無法回傳物流狀態。`);let f=await W(e,`senderAddress`),p={MerchantID:u.merchantId,MerchantTradeNo:r,MerchantTradeDate:Qn(a),LogisticsType:s?`CVS`:`HOME`,LogisticsSubType:i,GoodsAmount:String(l),IsCollection:c?`Y`:`N`,CollectionAmount:String(c?l:0),GoodsName:pr(n.names.join(`#`)),SenderName:jr(await W(e,`senderName`),10),SenderPhone:await W(e,`senderPhone`),SenderCellPhone:await W(e,`senderCellphone`),ReceiverName:[...(o.address?.name||t.customer.name).trim()].slice(0,10).join(``),ReceiverPhone:``,ReceiverCellPhone:t.customer.phone.replace(/\D/g,``),ReceiverEmail:t.email,TradeDesc:``,ServerReplyURL:`${d}/api/shop/ecpay/logistics-notify`,ClientReplyURL:``,LogisticsC2CReplyURL:``,Remark:``,PlatformID:``};if(s){if(!o.store?.id)throw new A(`訂單沒有取貨門市。`);p.ReceiverStoreID=o.store.id,p.ReturnStoreID=o.store.id}else{if(!o.address?.line)throw new A(`訂單沒有收件地址。`);p.SenderZipCode=await Ar(e,f),p.SenderAddress=f,p.ReceiverZipCode=o.address.zip??``,p.ReceiverAddress=`${o.address.city??``}${o.address.line}`.trim(),p.Temperature=`000${tr[n.temp]}`,p.Distance=`00`;let t=Number((await R(e,J))[o.methodId]?.spec??1);p.Specification=`000${t>=1&&t<=3?t:1}`,p.ScheduledPickupTime=`4`,p.ScheduledDeliveryTime=`4`,p.GoodsWeight=String(n.weightKg>0?Math.min(20,Math.max(1,Math.ceil(n.weightKg))):1)}return p.CheckMacValue=await B(p,u.hashKey,u.hashIv,`md5`),{payload:p,cred:u}}async function Nr(e,t,n){if(!e.http)throw new A(`沒有對外連線權限。`);let r;try{r=await e.http.fetch(t,{method:`POST`,headers:{"content-type":`application/x-www-form-urlencoded`},body:new URLSearchParams(n).toString()})}catch(e){throw new A(`無法連線綠界物流:${e instanceof Error?e.message:String(e)}`)}return r.text()}function Pr(e){return e.CVSPaymentNo?`${e.CVSPaymentNo}${e.CVSValidationNo??``}`:e.BookingNote||e.AllPayLogisticsID||null}const Fr=new Set([`300`,`2001`]);async function Ir(e,t,n){let r=t.shipping?sr(t.shipping.methodId):void 0;if(!t.shipping||!r)throw new A(`這筆訂單不是綠界物流的運送方式。`);let i=r.subtype[await q(e)],a=kr(t,await br(e,r));if(!a.length)throw new A(`這筆訂單沒有可出貨的商品。`);let o=new Set(n.map(e=>e.details.溫層)),s=a.length>1,c=[],l=[];for(let n of a){let a=nr[n.temp];if(o.has(a))continue;let u=cr(i,n.temp),d=U(u);if(!await _r(e,d)){l.push(`${a}(${d} 帳號)尚未設定金鑰`);continue}let f=s?`${Er(t.number).slice(0,18)}T${tr[n.temp]}`:Er(t.number),{payload:p,cred:m}=await Mr(e,t,n,f,u),h;try{h=await Nr(e,`${vr(m.stage)}/Express/Create`,p)}catch(e){l.push(`${a}物流單建立失敗:${e instanceof Error?e.message:String(e)}`);continue}let g=lr(h),_=g.fields;if(!g.ok||!Fr.has(_.RtnCode??``)){l.push(`${a}物流單建立失敗:${g.ok?`${_.RtnMsg??``}(狀態碼 ${_.RtnCode??`?`})`:g.message}`);continue}c.push({shipmentId:f,carrier:u===`UNIMARTFREEZE`?`7-ELEVEN 冷凍`:r.carrier,trackingNo:Pr(_),providerRef:_.AllPayLogisticsID||null,isCollection:t.shipping.collect,statusText:_.RtnMsg||`已建立物流單`,details:{子類型:u,溫層:a,綠界物流編號:_.AllPayLogisticsID??``,寄貨編號:_.CVSPaymentNo??``,驗證碼:_.CVSValidationNo??``,託運單號:_.BookingNote??``,金額:`NT$${p.GoodsAmount}${t.shipping.collect?`(代收)`:``}`}})}if(!c.length&&l.length)throw new A(l.join(` / `));return{shipments:c,errors:l}}async function Lr(e,t){let n=await G(e,U(t.details.子類型??`UNIMARTC2C`)),r={MerchantID:n.merchantId,AllPayLogisticsID:t.providerRef??``,TimeStamp:String(Math.floor(Date.now()/1e3))};r.CheckMacValue=await B(r,n.hashKey,n.hashIv,`md5`);let i=await ur(await Nr(e,`${vr(n.stage)}/Helper/QueryLogisticsTradeInfo/V2`,r),n.hashKey,n.hashIv),a=i.fields.LogisticsStatus;if(!i.ok||!a)throw new A(`查詢物流狀態失敗:${i.message||`no LogisticsStatus in response`}`);return{status:fr(a),statusText:i.fields.LogisticsStatusMsg||i.fields.RtnMsg||`狀態 ${a}`,statusCode:a}}function Rr(e){return jn(e).replace(/%2[Aa]/g,`*`).replace(/%21/g,`!`).replace(/%28/g,`(`).replace(/%29/g,`)`)}const zr=new Set([`UNIMARTC2C`,`UNIMART`,`UNIMARTFREEZE`,`POST`]);function Br(e,t){return t===`A6`&&zr.has(e)?2:1}async function Vr(e){return await W(e,`labelMode`)===`2`?`A6`:`A4`}async function Hr(e,t){let n=t.details.子類型??`UNIMARTC2C`;return Wr(e,n,[t.providerRef??``],Br(n,await Vr(e)))}async function Ur(e,t,n){let r=n??await Vr(e),i=new Map,a=[];for(let e of t){if(!e.active||!e.providerRef){a.push(`${e.shipmentId}:${e.active?`沒有綠界物流編號`:`物流單已作廢`}`);continue}let t=e.details.子類型??`UNIMARTC2C`;i.set(t,[...i.get(t)??[],e])}let o=[];for(let[t,n]of i){let i=Br(t,r);try{let{html:r}=await Wr(e,t,n.map(e=>e.providerRef),i);o.push({subtype:t,mode:i===2?`A6`:`A4`,shipmentIds:n.map(e=>e.shipmentId),html:r})}catch(e){a.push(`${t}:${e instanceof Error?e.message:String(e)}`)}}return{labels:o,errors:a}}async function Wr(e,t,n,r){if(!e.http)throw new A(`沒有對外連線權限。`);let i=await G(e,U(t)),a=JSON.stringify({MerchantID:i.merchantId,LogisticsID:n,LogisticsSubType:t,PrintMode:r}),o=JSON.stringify({MerchantID:i.merchantId,RqHeader:{Timestamp:Math.floor(Date.now()/1e3),Revision:`1.0.0`},Data:ke(await On(i.hashKey,i.hashIv,Rr(a),`pkcs7`))}),s;try{s=await e.http.fetch(`${vr(i.stage)}/Express/v2/PrintTradeDocument`,{method:`POST`,headers:{"content-type":`application/json`},body:o})}catch(e){throw new A(`無法連線綠界物流:${e instanceof Error?e.message:String(e)}`)}let c=await s.text();if(s.status!==200)throw new A(`ECPay HTTP ${s.status}: ${c.slice(0,200)}`);return{html:c}}const Gr={pluginId:er,owns:e=>!!sr(e),multi:!0,usable:K,create:Ir,sync:Lr,print:Hr};async function Kr(e,t){let n=An(t.body),r=await M(e).shipments.get(n.MerchantTradeNo??``);if(!r)return{...Y(404,`Order not found`),passThrough:!0};let i=await G(e,U(r.details.子類型||n.LogisticsSubType||`UNIMARTC2C`));if(!i.hashKey||!i.hashIv||(n.MerchantID??``)!==i.merchantId||!await Zn(n,i.hashKey,i.hashIv,`md5`))return Y(400,`CheckMacValue mismatch`);let a=n.RtnCode??``,o=await jt(e,r.shipmentId,{status:fr(a),statusText:n.RtnMsg||`狀態 ${a}`,statusCode:a,trackingNo:Pr(n),providerRef:n.AllPayLogisticsID||null});return{ok:!0,outcome:o.result,response:{kind:`text`,status:200,body:`1|OK`},reports:o.result!==`unknown`&&o.report?[o.report]:[]}}const qr={merchantId:`2000132`,hashKey:`ejCk326UnaZWKisg`,hashIv:`q9jcZX8Ib9LM8wYk`},Jr={stage:`einvoice-stage.ecpay.com.tw`,production:`einvoice.ecpay.com.tw`};async function Yr(e,t,n){return ke(await On(t,n,jn(JSON.stringify(e)),`pkcs7`))}async function Xr(e,t,n){let r=Ae(e);if(!r)return null;let i=await kn(t,n,r,`pkcs7`);if(!i)return null;try{return JSON.parse(decodeURIComponent(Oe(i).replace(/\+/g,` `)))}catch{return null}}async function Zr(e){let t=String(await e.settings.get(`invoiceMerchantId`)??``).trim(),n=await e.settings.get(`invoiceHashKey`)??``,r=await e.settings.get(`invoiceHashIv`)??``;return t&&n&&r?{merchantId:t,hashKey:n,hashIv:r,stage:await e.settings.get(`invoiceEnvironment`)!==`production`}:{...qr,stage:!0}}async function Qr(e){return await e.settings.get(`invoiceEnabled`)===!0}async function X(e,t,n){if(!e.http)throw new A(`沒有對外連線權限。`);let r=await Zr(e),i={MerchantID:r.merchantId,RqHeader:{Timestamp:Math.floor(Date.now()/1e3),Revision:`3.0.0`},Data:await Yr({MerchantID:r.merchantId,...n},r.hashKey,r.hashIv)},a;try{a=await e.http.fetch(`https://${r.stage?Jr.stage:Jr.production}/B2CInvoice/${t}`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify(i)})}catch(e){throw new A(`無法連線綠界電子發票:${e instanceof Error?e.message:String(e)}`)}let o=await a.json().catch(()=>null);if(!o||Number(o.TransCode)!==1||!o.Data)throw new A(`綠界發票 API 傳輸失敗:${o?.TransMsg??`HTTP ${a.status}`}`);let s=await Xr(o.Data,r.hashKey,r.hashIv);if(!s)throw new A(`綠界發票回應無法解密,請確認 HashKey/HashIV。`);return s}function $r(e,t){if(Number(e.RtnCode)!==1)throw new A(`${t}:${e.RtnMsg}(${e.RtnCode})`)}function ei(e,t,n){let r=[],i=0,a=(e,t,n,i,a)=>r.push({ItemSeq:r.length+1,ItemName:[...e].slice(0,100).join(``),ItemCount:t,ItemWord:n,ItemPrice:i,ItemTaxType:`1`,ItemAmount:a});for(let e of t)i+=e.amount,a(e.name,e.quantity,`批`,e.quantity>0?Math.round(e.amount/e.quantity):0,e.amount);return e.shippingFee>0&&(i+=e.shippingFee,a(e.shipping?.label||`運費`,1,`式`,e.shippingFee,e.shippingFee)),i!==n&&a(`其他`,1,`式`,n-i,n-i),r}function ti(e,t){return e===`company`||e===`donate`||e===`paper`?{CarrierType:``,CarrierNum:``}:e===`mobile`?{CarrierType:`3`,CarrierNum:t??``}:e===`certificate`?{CarrierType:`2`,CarrierNum:t??``}:{CarrierType:`1`,CarrierNum:``}}function ni(e,t){let n=e.invoice,r=n.kind===`company`&&!!n.taxId,i=n.kind===`donate`&&!!n.loveCode,a=r||n.kind===`paper`;if(a&&t.buyerAddress===`無`)throw new A(`列印發票(紙本或統編)需要買受人地址,這筆訂單沒有收件或門市地址。請補上地址後重新開立。`);let o=t.buyerAddress===`無`?``:t.buyerAddress;return{RelateNumber:t.relateNumber,CustomerID:``,CustomerIdentifier:r?n.taxId??``:``,CustomerName:[...(r?n.companyTitle:e.customer.name)||`消費者`].slice(0,60).join(``),CustomerAddr:[...o].slice(0,100).join(``),CustomerPhone:e.customer.phone,CustomerEmail:e.email,Print:a?`1`:`0`,Donation:i?`1`:`0`,LoveCode:i?n.loveCode??``:``,...ti(r||i?`company`:n.kind,n.carrierNum),TaxType:`1`,SalesAmount:t.amount,InvoiceRemark:``,Items:ei(e,t.discounted,t.amount),InvType:`07`,vat:`1`}}const ri=e=>(e.meta.invoiceDate??e.invoiceDate??``).slice(0,10);async function ii(e,t){let n=String(await e.settings.get(`invoicePrefix`)??``).replace(/[^A-Za-z0-9]/g,``).slice(0,5);return`${n?`${n}INV`:``}${t.number}R${w(Pe(4))}`.slice(0,30)}const Z={pluginId:er,kinds:[`member`,`mobile`,`certificate`,`paper`,`donate`,`company`],checkoutOptions:e=>Yt(e,Z.kinds),relateNumber:ii,async issue(e,t,n){let r=await X(e,`Issue`,ni(t,n));$r(r,`電子發票開立失敗`);let i=String(r.InvoiceDate??``);return{invoiceNo:String(r.InvoiceNo),invoiceDate:St(i),randomNumber:String(r.RandomNumber??``),meta:{invoiceDate:i}}},async void(e,t,n){$r(await X(e,`Invalid`,{InvoiceNo:t.invoiceNo,InvoiceDate:ri(t),Reason:n.slice(0,20)}),`作廢失敗`)},async allowance(e,t,n,r){let i=await X(e,`Allowance`,{InvoiceNo:t.invoiceNo,InvoiceDate:ri(t),AllowanceNotify:`E`,CustomerName:n.customer.name,NotifyMail:n.email,AllowanceAmount:r,Items:[{ItemSeq:1,ItemName:[...n.lines[0]?.name||`其他`].slice(0,100).join(``),ItemCount:1,ItemWord:`式`,ItemPrice:r,ItemTaxType:`1`,ItemAmount:r}]});return $r(i,`折讓開立失敗`),{allowanceNo:String(i.IA_Allow_No??i.AllowanceNo??``)}},async voidAllowance(e,t,n,r){$r(await X(e,`AllowanceInvalid`,{InvoiceNo:t.invoiceNo,AllowanceNo:n.allowanceNo,Reason:r.slice(0,20)}),`折讓作廢失敗`)},async query(e,t){return X(e,`GetIssue`,{RelateNumber:t.relateNumber})},async verifyOnline(e,t){if(t.kind===`donate`){let n=await X(e,`CheckLoveCode`,{LoveCode:t.loveCode});if(Number(n.RtnCode)===1&&n.IsExist!==`Y`)return`查無此捐贈碼。`}else if(t.kind===`mobile`){let n=await X(e,`CheckBarcode`,{BarCode:t.carrierNum});if(Number(n.RtnCode)===1&&n.IsExist!==`Y`)return`查無此手機條碼,請確認後再輸入。`}return null}},Q={Credit:`信用卡`,Credit3:`信用卡分 3 期`,Credit6:`信用卡分 6 期`,Credit12:`信用卡分 12 期`,Credit18:`信用卡分 18 期`,Credit24:`信用卡分 24 期`,Credit30N:`信用卡分 30 期(永豐)`,UnionPay:`銀聯卡`,ATM:`ATM 轉帳`,CVS:`超商代碼繳費`,BARCODE:`超商條碼繳費`,WebATM:`網路 ATM`,ApplePay:`Apple Pay`,TWQR:`TWQR 行動支付`,BNPL:`無卡分期(裕富/中租)`,WeiXin:`微信支付`,Jkopay:`街口支付`,iPASS:`一卡通 iPASS MONEY`,ALL:`綠界付款頁自選`},ai=Object.keys(Q),oi={Credit:{choose:`Credit`,card:!0},Credit3:{choose:`Credit`,installment:3,card:!0},Credit6:{choose:`Credit`,installment:6,card:!0},Credit12:{choose:`Credit`,installment:12,card:!0},Credit18:{choose:`Credit`,installment:18,card:!0},Credit24:{choose:`Credit`,installment:24,card:!0},Credit30N:{choose:`Credit`,installment:`30N`,card:!0},UnionPay:{choose:`Credit`,unionPay:!0,card:!0},ATM:{choose:`ATM`,offline:!0,max:49999},CVS:{choose:`CVS`,offline:!0,max:2e4},BARCODE:{choose:`BARCODE`,offline:!0,max:2e4},WebATM:{choose:`WebATM`},ApplePay:{choose:`ApplePay`,card:!0},TWQR:{choose:`TWQR`,min:6,max:49999},BNPL:{choose:`BNPL`,min:50,max:5e5},WeiXin:{choose:`WeiXin`,min:6,max:5e5},Jkopay:{choose:`DigitalPayment`,sub:`Jkopay`},iPASS:{choose:`DigitalPayment`,sub:`iPASS`},ALL:{choose:`ALL`}};function si(e){if(!e)return``;let t={Credit_CreditCard:`信用卡`,Flexible_Installment:`信用卡分期(永豐 30 期)`,BARCODE_BARCODE:`超商條碼`,CVS_CVS:`超商代碼`,TWQR_OPAY:`TWQR 行動支付`,WeiXin_OPAY:`微信支付`,BNPL_URICH:`無卡分期(裕富)`,BNPL_ZINGALA:`無卡分期(中租)`,DigitalPayment_Jkopay:`街口支付`,DigitalPayment_IPASS:`一卡通 iPASS MONEY`,ApplePay:`Apple Pay`};if(t[e])return t[e];let n=[[`Credit_`,`信用卡`],[`WebATM_`,`網路 ATM`],[`ATM_`,`ATM 轉帳`],[`CVS_`,`超商代碼`],[`BNPL_`,`無卡分期`],[`DigitalPayment_`,`電子支付`],[`ApplePay`,`Apple Pay`]].find(([t])=>e.startsWith(t));return n?`${n[1]}(${e})`:e}const ci=`/api/shop/ecpay/notify`,li=new Set([`2`,`10100073`]);async function $(e){let t=await e.settings.get(`environment`)===`production`?`production`:`stage`,n=(await e.settings.get(`merchantId`)??``).trim(),r=await e.settings.get(`hashKey`)??``,i=await e.settings.get(`hashIv`)??``;return n&&r&&i?{environment:t,merchantId:n,hashKey:r,hashIv:i}:t===`stage`?{environment:t,...qn}:null}async function ui(e,t){let n=[];for(let r of ai){let i=await e.settings.get(`method${r}`);if(!(i===!0||i==null&&r===`Credit`))continue;let a=oi[r];if(!(t!=null&&(a.min!=null&&t<a.min||a.max!=null&&t>a.max))){if(r===`Credit30N`){let n=await di(e);if(t==null?n===1/0:!(t>n))continue}n.push(r)}}return n}async function di(e){let t=Number(await e.settings.get(`credit30NMinAmount`));return Number.isFinite(t)&&t>0?t:1/0}const fi=ai.map(e=>({code:e,label:Q[e]}));async function pi(e,t,n,r){let i=Math.floor(Number(await e.settings.get(t)));return Number.isFinite(i)&&i>=1?Math.min(i,r):n}async function mi(e){return String(await e.settings.get(`orderPrefix`)??``).replace(/[^A-Za-z0-9]/g,``).slice(0,5)}async function hi(e,t,n){let r=oi[n.method],i=String(await e.settings.get(`itemName`)??``).trim(),a={MerchantID:t.merchantId,MerchantTradeNo:n.merchantTradeNo,MerchantTradeDate:Qn(n.now),PaymentType:`aio`,TotalAmount:String(Math.round(n.amount)),TradeDesc:`線上購物`,ItemName:$n(i?[i]:n.itemNames),ReturnURL:`${n.origin}${ci}`,ChoosePayment:r.choose,EncryptType:`1`,ClientBackURL:n.orderUrl,NeedExtraPaidInfo:`Y`};return r.sub&&(a.ChooseSubPayment=r.sub),r.installment&&(a.CreditInstallment=String(r.installment)),r.unionPay&&(a.UnionPay=`1`),n.method===`Credit`&&await e.settings.get(`creditRedeem`)===!0&&(a.Redeem=`Y`),r.card||(a.PaymentInfoURL=`${n.origin}${ci}`),(n.method===`ATM`||n.method===`ALL`)&&(a.ExpireDate=String(await pi(e,`atmExpireDays`,3,60))),n.method===`CVS`&&(a.StoreExpireDate=String(Math.min(43200,await pi(e,`cvsExpireDays`,7,30)*1440))),n.method===`BARCODE`&&(a.StoreExpireDate=String(await pi(e,`barcodeExpireDays`,7,30))),r.offline||(a.OrderResultURL=`${n.origin}/api/shop/gw/ecpay/return`),a.CheckMacValue=await B(a,t.hashKey,t.hashIv),{action:`https://${Jn[t.environment]}/Cashier/AioCheckOut/V5`,params:a}}const gi=[`BankCode`,`vAccount`,`PaymentNo`,`ExpireDate`,`Barcode1`,`Barcode2`,`Barcode3`,`BNPLTradeNo`,`BNPLInstallment`],_i=[`card4no`,`card6no`,`gwsr`,`auth_code`,`stage`,`process_date`];async function vi(e,t){let n=An(t),r=await $(e);if(!r)return et(`not_configured`,`0|Not configured`,503);if(!await Zn(n,r.hashKey,r.hashIv))return et(`bad_checkmac`,`0|CheckMacValue Error`,400);if(n.MerchantID!==r.merchantId)return et(`bad_merchant`,`0|MerchantID Error`,400);let i=n.MerchantTradeNo??``,a=await O(e).trades.get(i);if(!a)return et(`unknown_trade`,`0|Unknown MerchantTradeNo`,404);let o={ok:!0,reply:`1|OK`,httpStatus:200,orderNumber:a.orderNumber},s=n.SimulatePaid===`1`;if(s&&r.environment===`production`)return{...o,outcome:`ignored_simulated`};let c=n.RtnCode??``,l=n.TradeNo??``;if(c===`1`){let t=Number(n.TradeAmt)===a.amount,r={};for(let e of _i)n[e]&&(r[e]=n[e]);return await rt(e,i,{status:t?`paid`:`failed`,tradeNo:l,paymentType:n.PaymentType,resultCode:c,resultMessage:t?n.RtnMsg:`amount_mismatch:${n.TradeAmt}`,simulated:s,...Object.keys(r).length?{info:r}:{}}),{...o,outcome:t?`paid`:`amount_mismatch`,event:{eventId:`ecpay:${l||i}:paid`,provider:`ecpay`,orderNumber:a.orderNumber,amount:Number(n.TradeAmt),status:`paid`,tradeNo:l,simulated:s}}}if(li.has(c)){let t={PaymentType:n.PaymentType??``};for(let e of gi)n[e]&&(t[e]=n[e]);if((await rt(e,i,{status:`awaiting`,tradeNo:l,paymentType:n.PaymentType,resultCode:c,resultMessage:n.RtnMsg,info:t}))?.status===`paid`)return{...o,outcome:`already_paid`};let r=(n.PaymentType??``).startsWith(`BNPL`)||a.method===`BNPL`;return{...o,outcome:`payment_info`,paymentInfo:{eventId:`ecpay:${l||i}:info`,provider:`ecpay`,orderNumber:a.orderNumber,merchantTradeNo:i,tradeNo:l,info:t,expiresAt:r?new Date(Date.now()+6048e5).toISOString():ut(n.ExpireDate)}}}return(await rt(e,i,{status:`failed`,tradeNo:l,paymentType:n.PaymentType,resultCode:c,resultMessage:n.RtnMsg}))?.status===`paid`?{...o,outcome:`already_paid`}:{...o,outcome:`failed`,event:{eventId:`ecpay:${l||i}:rtn${c}`,provider:`ecpay`,orderNumber:a.orderNumber,amount:Number(n.TradeAmt)||0,status:`failed`,tradeNo:l,simulated:s}}}async function yi(e,t,n){if(!e.http)throw Error(`NO_NETWORK`);let r=await e.http.fetch(t,{method:`POST`,headers:{"content-type":`application/x-www-form-urlencoded`},body:new URLSearchParams(n).toString()});return{status:r.status,reply:An((await r.text()).trim())}}async function bi(e,t,n){let r={MerchantID:t.merchantId,MerchantTradeNo:n,TimeStamp:String(Math.floor(Date.now()/1e3))};r.CheckMacValue=await B(r,t.hashKey,t.hashIv);let{status:i,reply:a}=await yi(e,`https://${Jn[t.environment]}/Cashier/QueryTradeInfo/V5`,r),o=await Zn(a,t.hashKey,t.hashIv);return{ok:i>=200&&i<300,status:i,verified:o,tradeStatus:a.TradeStatus??null,tradeAmt:a.TradeAmt??null,merchantTradeNo:a.MerchantTradeNo??null,paymentType:a.PaymentType??null}}const xi=e=>/^(Credit_|ApplePay)/.test(e.paymentType??``)||!!(oi[e.method]?.card&&!e.paymentType),Si=e=>!!oi[e.method]?.installment||Number(e.info?.stage??0)>0||e.paymentType===`Flexible_Installment`;async function Ci(e,t,n,r,i){let a={MerchantID:t.merchantId,MerchantTradeNo:n.merchantTradeNo,TradeNo:n.tradeNo??``,Action:r,TotalAmount:String(i)};a.CheckMacValue=await B(a,t.hashKey,t.hashIv);let{status:o,reply:s}=await yi(e,`https://${Jn[t.environment]}/CreditDetail/DoAction`,a);return{ok:s.RtnCode===`1`,code:s.RtnCode??``,message:s.RtnMsg??`HTTP ${o}`}}async function wi(e,t,n){if(!xi(t))return{ok:!1,manual:!0,message:`這筆以 ${si(t.paymentType)||`非信用卡`} 付款,綠界沒有退款 API,請在綠界(或該支付的)廠商後台退款,或以轉帳等方式人工退款。`};let r=t.amount-(t.refunded??0);if(n>r)return{ok:!1,message:`退款金額超過可退餘額 NT$${r}。`};if(Si(t)&&n!==r)return{ok:!1,message:`分期付款的交易只能全額退刷(NT$${r})。`};let i=await $(e);if(!i)return{ok:!1,message:`綠界金流未設定。`};let a=await Ci(e,i,t,`R`,n);if(a.ok)return{ok:!0,action:`refund`,message:`綠界信用卡退刷成功(NT$${n})`};if(n===r){let r=await Ci(e,i,t,`N`,n);return r.ok?{ok:!0,action:`release`,message:`綠界信用卡授權已取消(尚未請款)`}:{ok:!1,message:`綠界退刷失敗:${a.message};取消授權也失敗:${r.message}`}}return{ok:!1,message:`綠界退刷失敗:${a.message}(${a.code})。尚未請款的交易只能全額取消授權。`}}async function Ti(e,t){if(t.status!==`paid`||!xi(t))return{ok:!1,message:`只有已付款的信用卡交易可以請款。`};let n=await $(e);if(!n)return{ok:!1,message:`綠界金流未設定。`};let r=t.amount-(t.refunded??0),i=await Ci(e,n,t,`C`,r);return i.ok?{ok:!0,message:`綠界信用卡請款(關帳)成功(NT$${r})`}:{ok:!1,code:i.code,message:`綠界請款失敗:${i.message}(${i.code})`}}const Ei={stage:`ecpayment-stage.ecpay.com.tw`,production:`ecpayment.ecpay.com.tw`},Di={Authorized:`已授權(尚未請款)`,"To be captured":`請款中`,Captured:`已請款`,Refunded:`已退刷`,Cancelled:`已取消授權`,Failed:`失敗`};async function Oi(e,t){if(!xi(t))return{ok:!1,error:`NOT_CARD`,message:`這筆不是信用卡交易。`};let n=await $(e);if(!n)return{ok:!1,error:`NOT_CONFIGURED`,message:`綠界金流未設定。`};if(!e.http)return{ok:!1,error:`NO_NETWORK`,message:`沒有網路權限。`};let r={MerchantID:n.merchantId,RqHeader:{Timestamp:Math.floor(Date.now()/1e3)},Data:await Yr({MerchantID:n.merchantId,MerchantTradeNo:t.merchantTradeNo},n.hashKey,n.hashIv)},i=await e.http.fetch(`https://${Ei[n.environment]}/1.0.0/CreditDetail/QueryTrade`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify(r)}),a={};try{a=await i.json()}catch{return{ok:!1,error:`BAD_REPLY`,message:`綠界回應無法解析(HTTP ${i.status})`}}if(Number(a.TransCode)!==1)return{ok:!1,error:`ECPAY`,message:`綠界查詢失敗:${String(a.TransMsg??`HTTP ${i.status}`)}`};let o=await Xr(String(a.Data??``),n.hashKey,n.hashIv),s=o?.RtnValue;if(!s)return{ok:!1,error:`ECPAY`,message:`綠界查詢失敗:${o?.RtnMsg||`沒有資料`}`};let c=String(s.Status??``);return{ok:!0,status:c,statusLabel:Di[c]??c,amount:Number(s.Amount)||0,captured:Number(s.ClsAmt)||0,authorizedAt:String(s.AuthTime??``),history:(o?.CloseData??[]).map(e=>({status:String(e.Status??``),amount:Number(e.Amount)||0,at:String(e.DateTime??``)}))}}const ki=`logistics-reconcile`,Ai=p({shipmentIds:f(i({min:1,max:64}),{min:1,max:50}),mode:h(u([`A4`,`A6`]))}),ji=i({min:1,max:18,pattern:/^[A-Z0-9]+$/}),Mi=i({min:1,max:20,pattern:/^[A-Za-z0-9]+$/}),Ni=p({amount:h(o({min:0}))}),Pi=p({orderNumber:ji,amount:o({min:1,max:99999999}),itemNames:f(i({min:1,max:200}),{min:1,max:100}),method:g(u(ai),`Credit`),origin:i({max:200,pattern:dt}),orderUrl:i({min:8,max:400,pattern:ft}),email:h(i({max:120}))}),Fi=p({id:i({min:1,max:200}),type:i({min:1,max:60}),payload:m(c(),{maxKeys:50})}),Ii=p({orderNumber:ji,amount:o({min:1,max:99999999})}),Li=p({orderNumber:h(ji),limit:g(o({min:1,max:100,coerce:!0}),20)}),Ri=p({merchantTradeNo:Mi}),zi=p({merchantTradeNo:Mi,amount:o({min:1,max:99999999,coerce:!0})}),Bi={created:`待付款`,awaiting:`待繳費`,paid:`已付款`,failed:`未成功`};async function Vi(e){let{trades:t}=O(e),n=await $(e),[r,i,a]=await Promise.all([t.query({orderBy:{createdAt:`desc`},limit:30}),t.count({status:`paid`}),ui(e)]);return{blocks:[{type:`header`,text:`綠界金流`},{type:`stats`,items:[{label:`環境`,value:n?n.environment===`production`?`正式`:`測試`:`未設定`},{label:`特店編號`,value:n?.merchantId??`—`},{label:`開放的付款方式`,value:a.map(e=>Q[e]).join(`、`)||`無`},{label:`已付款交易`,value:String(i)}]},{type:`table`,columns:[{key:`merchantTradeNo`,label:`綠界訂單編號`},{key:`orderNumber`,label:`訂單`},{key:`method`,label:`付款方式`},{key:`amount`,label:`金額`},{key:`status`,label:`狀態`},{key:`tradeNo`,label:`綠界交易序號`}],rows:r.items.map(({data:e})=>({merchantTradeNo:e.merchantTradeNo,orderNumber:e.orderNumber,method:[Q[e.method]??e.method??``,e.paymentType?si(e.paymentType):``,e.info?.card4no?`末四碼 ${e.info.card4no}`:``].filter(Boolean).join(`/`),amount:`NT$${e.amount}${e.refunded?`(已退 NT$${e.refunded})`:``}`,status:e.status===`paid`&&e.simulated?`已付款(模擬)`:Bi[e.status]??e.status,tradeNo:e.tradeNo??``})),page_action_id:`browse_trades`,empty_text:`尚無交易。`}]}}async function Hi(e,t,n){let r=Vn(n);if(r){let t=await Ln(e,`綠界金流`,fi,r.actionId,r.values)??await Bn(e,J,xr,r.actionId,r.values);if(t)return t}if(t===`/payment-labels`)return In(`綠界金流`,fi,await L(e,ai));if(t===`/shipping-methods`)return z(J,xr,await R(e,J));if(t===`/shipments`){let t=await G(e,`C2C`);return vn(e,`綠界物流`,[{label:`物流`,value:await K(e)?`已啟用`:`未啟用`},{label:`環境`,value:t.stage?`測試`:`正式`},{label:`合約`,value:await e.settings.get(`logisticsContract`)===`B2C`?`B2C 大宗寄倉`:`C2C 店到店`}])}if(t===`/invoices`){let t=await Zr(e);return yn(e,`綠界電子發票`,[{label:`電子發票`,value:await Qr(e)?`已啟用`:`未啟用`},{label:`環境`,value:t.stage?`測試`:`正式`},{label:`特店編號`,value:t.merchantId}])}return Vi(e)}async function Ui(e,t,n){let r=await it(e,t);return r?ot(r.merchantTradeNo,await wi(e,r,n),()=>at(e,r.merchantTradeNo,n)):{ok:!1,error:`NOT_PAID`,detail:`這筆訂單沒有已付款的綠界交易。`}}const Wi={hooks:{"plugin:activate":async(e,t)=>{await t.cron?.schedule(ki,{schedule:`*/30 * * * *`})},cron:async(e,t)=>{if(e.name!==ki)return;let n=await Qt(t,Z);if(n.length&&t.log.info(`issued delayed ECPay invoices`,{n:n.length}),!await K(t))return;let r=await zt(t,Gr);r&&t.log.info(`synced ECPay shipments`,{n:r})}},routes:{...xn([`trades`,`orders`,`shipments`,`invoices`]),"bus/methods":D(`bus/methods`,Ni,async(e,t)=>{if(!await $(t))return{ok:!0,provider:`ecpay`,methods:[]};let n=await ui(t,e.amount),r=await L(t,ai);return{ok:!0,provider:`ecpay`,label:`綠界金流`,methods:n.map(e=>Pn(r,e,Q[e]))}}),"bus/checkout":D(`bus/checkout`,Pi,async(e,t)=>{let n=await $(t);if(!n)return{ok:!1,error:`NOT_CONFIGURED`};if(!(await ui(t,e.amount)).includes(e.method))return{ok:!1,error:`METHOD_DISABLED`};let r=await mi(t);if(r.length+e.orderNumber.length+2>20)return{ok:!1,error:`TRADE_NO_TOO_LONG`,detail:`訂單編號加上前綴超過綠界的 20 字上限,請縮短「訂單編號前綴」。`};let i;try{i=await nt(t,e.orderNumber,e.amount,e.method,r)}catch(e){return{ok:!1,error:e.message}}return{ok:!0,...await hi(t,n,{merchantTradeNo:i.merchantTradeNo,amount:e.amount,itemNames:e.itemNames,method:e.method,origin:e.origin,orderUrl:e.orderUrl}),merchantTradeNo:i.merchantTradeNo}}),"bus/notify":Ge(`bus/notify`,async(e,t)=>vi(t,e)),[x.gateway(`return`)]:D(x.gateway(`return`),we,async(e,t)=>tt(await vi(t,e.body))),"bus/events":D(`bus/events`,Fi,async(e,t)=>st(t,e,(e,t)=>$t(e,t,{logistics:Gr,invoice:Z}),{provider:`ecpay`,refund:Ui})),...$e(),"bus/shipping/methods":D(`bus/shipping/methods`,rn,async(e,t)=>({ok:!0,provider:`moksa-ecpay`,methods:await Tr(t,e.temperatures)})),...hn(Z,`綠界電子發票`,Qr),[x.gateway(`logistics-map`)]:D(x.gateway(`logistics-map`),we,async(e,t)=>(await nn(t,e.origin),Dr(t,e))),[x.gateway(`logistics-map-reply`)]:D(x.gateway(`logistics-map-reply`),we,async(e,t)=>Or(t,e)),[x.gateway(`logistics-notify`)]:D(x.gateway(`logistics-notify`),we,async(e,t)=>(await nn(t,e.origin),Kr(t,e))),"bus/refund":D(`bus/refund`,Ii,async(e,t)=>Ui(t,e.orderNumber,e.amount)),"trades/list":k(Li,async(e,t)=>({ok:!0,trades:(await O(t).trades.query({where:e.orderNumber?{orderNumber:e.orderNumber}:void 0,orderBy:{createdAt:`desc`},limit:e.limit})).items.map(e=>e.data)})),"trades/query":k(Ri,async(e,t)=>{let n=await $(t);return n?t.http?bi(t,n,e.merchantTradeNo):{ok:!1,error:`NO_NETWORK`}:{ok:!1,error:`NOT_CONFIGURED`}}),"trades/refund":k(zi,async(e,t)=>{let n=await O(t).trades.get(e.merchantTradeNo);if(!n||n.status!==`paid`)return{ok:!1,error:`NOT_PAID`};let r=await Ui(t,n.orderNumber,e.amount);if(!r.ok)return r;let i=await ct(t,{provider:`ecpay`,refundId:`${n.merchantTradeNo}:${(n.refunded??0)+e.amount}`,orderNumber:n.orderNumber,amount:e.amount,message:r.message});return{...r,reports:[i]}},[`POST`]),"trades/capture":k(Ri,async(e,t)=>{let n=await O(t).trades.get(e.merchantTradeNo);if(!n)return{ok:!1,error:`NOT_FOUND`};let r=await Ti(t,n);return r.ok?{ok:!0,message:r.message}:{ok:!1,error:`CAPTURE_FAILED`,detail:r.message}},[`POST`]),"trades/card-status":k(Ri,async(e,t)=>{let n=await O(t).trades.get(e.merchantTradeNo);if(!n)return{ok:!1,error:`NOT_FOUND`};let r=await Oi(t,n);return r.ok?r:{ok:!1,error:r.error,detail:r.message}}),...fn(Gr),"shipments/print-batch":k(Ai,async(e,t)=>{let n=await Promise.all([...new Set(e.shipmentIds)].map(async e=>[e,await M(t).shipments.get(e)])),r=n.filter(([,e])=>!e).map(([e])=>`${e}:找不到物流單`),i=await Ur(t,n.flatMap(([,e])=>e?[e]:[]),e.mode),a=[...r,...i.errors];return i.labels.length?{ok:!0,labels:i.labels,errors:a}:{ok:!1,error:`NOTHING_PRINTED`,detail:a.join(` / `)}}),...pn(Z),admin:{permission:`plugins:manage`,handler:async(e,t)=>Hi(t,String(e.input?.page??``),e.input)}}};export{Wi as default};
1
+ function e(e,t){let n=[],r=e.check(t,``,n);return n.length?{ok:!1,issues:n,error:n.map(e=>e.path?`${e.path}: ${e.message}`:e.message).join(`; `)}:{ok:!0,value:r}}function t(e,t,n){e.push({path:t,message:n})}function n(e){return e===null?`null`:Array.isArray(e)?`array`:typeof e}const r=/^[^\s@]+@[^\s@]+\.[^\s@]+$/;function i(e={}){return{check(i,a,o){if(typeof i!=`string`)return t(o,a,`expected string, got ${n(i)}`);let s=e.trim?i.trim():i;return e.min!==void 0&&s.length<e.min?t(o,a,`must be at least ${e.min} characters`):e.max!==void 0&&s.length>e.max?t(o,a,`must be at most ${e.max} characters`):e.pattern&&!e.pattern.test(s)?t(o,a,e.message??`has an invalid format`):e.format===`email`&&!r.test(s)?t(o,a,`must be an email address`):s},json(){let t={type:`string`};return e.min!==void 0&&(t.minLength=e.min),e.max!==void 0&&(t.maxLength=e.max),e.pattern&&(t.pattern=e.pattern.source),e.format&&(t.format=e.format),t}}}function a(e,r){return{check(i,a,o){let s=i;return r.coerce&&typeof s==`string`&&s.trim()!==``&&(s=Number(s)),typeof s!=`number`||!Number.isFinite(s)?t(o,a,`expected ${e?`integer`:`number`}, got ${n(i)}`):e&&!Number.isSafeInteger(s)?t(o,a,`must be an integer`):r.min!==void 0&&s<r.min?t(o,a,`must be >= ${r.min}`):r.max!==void 0&&s>r.max?t(o,a,`must be <= ${r.max}`):s},json(){let t={type:e?`integer`:`number`};return r.min!==void 0&&(t.minimum=r.min),r.max!==void 0&&(t.maximum=r.max),t}}}function o(e={}){return a(!0,e)}function s(e={}){return a(!1,e)}function c(){return{check:e=>e,json:()=>({})}}function l(){return{check:(e,r,i)=>typeof e==`boolean`?e:t(i,r,`expected boolean, got ${n(e)}`),json:()=>({type:`boolean`})}}function u(e){return{check:(n,r,i)=>e.includes(n)?n:t(i,r,`must be one of ${e.join(`, `)}`),json:()=>({enum:[...e]})}}function d(e){return{check:(n,r,i)=>n===e?e:t(i,r,`must be ${JSON.stringify(e)}`),json:()=>({const:e})}}function f(e,r={}){return{check(i,a,o){if(!Array.isArray(i))return t(o,a,`expected array, got ${n(i)}`);if(r.min!==void 0&&i.length<r.min)return t(o,a,`must have at least ${r.min} items`);if(r.max!==void 0&&i.length>r.max)return t(o,a,`must have at most ${r.max} items`);let s=o.length,c=i.map((t,n)=>e.check(t,`${a}[${n}]`,o));return o.length>s?void 0:c},json(){let t={type:`array`,items:e.json()};return r.min!==void 0&&(t.minItems=r.min),r.max!==void 0&&(t.maxItems=r.max),t}}}function p(e,r={}){return{check(i,a,o){if(typeof i!=`object`||!i||Array.isArray(i))return t(o,a,`expected object, got ${n(i)}`);let s=i,c=o.length,l={};for(let[n,r]of Object.entries(e)){let e=a?`${a}.${n}`:n;if(!(n in s)||s[n]===void 0){let i=r.optional?r.check(void 0,e,o):t(o,e,`is required`);i!==void 0&&(l[n]=i);continue}let i=r.check(s[n],e,o);i!==void 0&&(l[n]=i)}if(r.unknown===`reject`)for(let n of Object.keys(s))n in e||t(o,a?`${a}.${n}`:n,`is not allowed`);return o.length>c?void 0:l},json(){let t={},n=[];for(let[r,i]of Object.entries(e))t[r]=i.json(),i.optional||n.push(r);let i={type:`object`,properties:t,additionalProperties:r.unknown!==`reject`};return n.length&&(i.required=n),i}}}function m(e,r={}){return{check(i,a,o){if(typeof i!=`object`||!i||Array.isArray(i))return t(o,a,`expected object, got ${n(i)}`);let s=Object.entries(i);if(r.maxKeys!==void 0&&s.length>r.maxKeys)return t(o,a,`must have at most ${r.maxKeys} keys`);let c=o.length,l={};for(let[n,i]of s){let s=a?`${a}.${n}`:n;if(r.key&&!r.key.test(n)){t(o,s,`has an invalid key`);continue}let c=e.check(i,s,o);c!==void 0&&(l[n]=c)}return o.length>c?void 0:l},json(){let t={type:`object`,additionalProperties:e.json()};return r.maxKeys!==void 0&&(t.maxProperties=r.maxKeys),t}}}function h(e){return{optional:!0,mayBeAbsent:!0,check:(t,n,r)=>t===void 0?void 0:e.check(t,n,r),json:()=>e.json()}}function g(e,t){return{optional:!0,check:(n,r,i)=>n===void 0?t:e.check(n,r,i),json:()=>({...e.json(),default:t})}}function _(e){return{check:(t,n,r)=>t===null?null:e.check(t,n,r),json:()=>({anyOf:[e.json(),{type:`null`}]})}}const v=o({min:0,max:99999999}),y=i({min:1,max:20,pattern:/^[A-Z0-9]+$/}),ee=i({min:10,max:40}),te=[`member`,`mobile`,`certificate`,`paper`,`donate`,`company`],ne=[`normal`,`refrigerated`,`frozen`],re=p({name:i({max:60}),phone:i({max:30}),zip:h(i({max:10})),city:h(i({max:60})),line:h(i({max:200}))}),ie=p({provider:i({min:1,max:40}),type:i({max:40}),id:i({min:1,max:40}),name:i({max:100}),address:i({max:200})}),ae=p({methodId:i({min:1,max:80}),label:i({max:100}),provider:_(i({max:64})),fee:v,address:h(re),store:h(ie),temperature:g(u(ne),`normal`),collect:g(l(),!1),collectAmount:g(v,0)}),oe=p({kind:u(te),carrierNum:h(i({max:64})),loveCode:h(i({max:10})),taxId:h(i({max:8,pattern:/^\d{8}$/})),companyTitle:h(i({max:100})),address:h(i({max:200})),provider:_(i({max:64}))}),se=p({sku:i({min:1,max:64}),name:i({max:200}),quantity:o({min:1,max:9999}),unitPrice:v,amount:v,temperature:h(u(ne)),weightKg:h(s({min:0,max:1e3}))}),ce=p({id:i({min:1,max:120}),source:i({min:1,max:40}),provider:_(i({max:64})),label:i({max:100}),amount:o({min:-99999999,max:99999999}),holdId:_(i({max:80}))}),le=p({number:y,status:i({max:20}),email:i({max:120}),customer:p({id:_(i({max:64})),name:i({max:60}),phone:i({max:30}),lineUserId:_(i({max:64}))}),lines:f(se,{max:200}),subtotal:v,discountTotal:v,shippingFee:v,total:v,currency:d(`TWD`),shipping:_(ae),invoice:_(oe),payment:_(p({provider:i({max:40}),tradeNo:i({max:64}),amount:v,simulated:l()})),note:g(i({max:500}),``),createdAt:ee,paidAt:_(ee),adjustments:h(f(ce,{max:30}))}),ue=p({provider:i({min:1,max:64}),shipmentId:i({min:1,max:64}),carrier:i({max:40}),method:i({max:80}),trackingNo:_(i({max:64})),trackingUrl:_(i({max:300})),status:u([`created`,`in_progress`,`shipped`,`arrived`,`completed`,`store_closed`,`unclaimed`,`returned`,`failed`,`void`]),statusText:i({max:200}),isCollection:l(),occurredAt:ee}),de=p({provider:i({min:1,max:64}),status:u([`issued`,`failed`,`void`]),invoiceNo:_(i({max:20})),invoiceDate:_(i({max:20})),randomNumber:_(i({max:8})),kind:u(te),amount:v,allowanceTotal:g(v,0),error:_(i({max:300})),occurredAt:ee}),fe=p({kind:d(`shipment`),reportId:i({min:1,max:200}),orderNumber:y,shipment:ue}),pe=p({kind:d(`invoice`),reportId:i({min:1,max:200}),orderNumber:y,invoice:de}),me=p({kind:d(`payment`),reportId:i({min:1,max:200}),orderNumber:y,payment:p({eventId:i({min:1,max:120}),provider:i({min:1,max:40,pattern:/^[a-z0-9-]+$/}),orderNumber:y,amount:o({min:0}),status:u([`paid`,`failed`,`cancelled`]),tradeNo:i({max:64})}),reason:h(i({min:1,max:80}))}),he=p({kind:d(`refund`),reportId:i({min:1,max:200}),orderNumber:y,requestId:h(i({min:1,max:80})),status:u([`refunded`,`manual`,`failed`]),amount:v,provider:i({min:1,max:64}),message:g(i({max:300}),``),origin:h(d(`plugin`)),reason:h(i({max:200}))}),ge=p({kind:d(`coupon`),reportId:i({min:1,max:200}),coupon:p({code:i({min:4,max:40,pattern:/^[A-Z0-9_-]+$/}),type:u([`percent`,`fixed_cart`]),amount:o({min:0,max:1e7}),freeShipping:g(l(),!1),description:g(i({max:200}),``),expiresAt:_(ee),email:_(i({max:120})),customerId:_(i({max:64})),campaign:g(i({max:40}),``)})}),_e=p({kind:d(`points`),reportId:i({min:1,max:200}),credit:p({customerId:i({min:1,max:64}),email:i({min:3,max:120}),amount:o({min:1,max:99999999}),reason:i({min:1,max:200})})}),ve=p({kind:d(`order-action`),reportId:i({min:1,max:200}),orderNumber:y,action:u([`cancel`,`refund`]),amount:h(o({min:1,max:99999999})),reason:i({min:1,max:200})}),ye=p({kind:d(`order-request`),reportId:i({min:1,max:200}),email:i({min:3,max:120,format:`email`}),customer:p({name:i({min:1,max:60}),phone:i({max:30}),id:_(i({max:64}))}),lines:f(p({slug:i({min:1,max:200}),sku:i({min:1,max:64}),quantity:o({min:1,max:99})}),{min:1,max:20}),pricing:g(m(c(),{maxKeys:20}),{}),note:g(i({max:500}),``)}),be=p({kind:d(`stock`),reportId:i({min:1,max:200}),sku:i({min:1,max:64}),stock:o({min:0,max:1e6}),reason:g(i({max:200}),``)}),xe=p({kind:d(`points-grant`),reportId:i({min:1,max:200}),grant:p({customerId:i({min:1,max:64}),email:i({min:3,max:120}),points:o({min:1,max:1e6}),reason:i({min:1,max:200})})}),Se=p({kind:d(`line-push`),reportId:i({min:1,max:200}),push:p({customerId:h(_(i({max:64}))),userId:h(_(i({max:64,pattern:/^U[0-9a-f]{32}$/i}))),topic:i({min:1,max:40,pattern:/^[a-z0-9_]+$/}),text:i({min:1,max:5e3})})}),Ce=`shipment, invoice, payment, refund, coupon, points, order-action, order-request, stock, points-grant or line-push`;function we(t){let n=t?.kind;return n===`shipment`?e(fe,t):n===`invoice`?e(pe,t):n===`payment`?e(me,t):n===`refund`?e(he,t):n===`coupon`?e(ge,t):n===`points`?e(_e,t):n===`order-action`?e(ve,t):n===`order-request`?e(ye,t):n===`stock`?e(be,t):n===`points-grant`?e(xe,t):n===`line-push`?e(Se,t):{ok:!1,error:`kind: must be ${Ce}`,issues:[{path:`kind`,message:`must be ${Ce}`}]}}const b=p({method:u([`GET`,`POST`]),query:g(i({max:8192}),``),contentType:g(i({max:200}),``),body:g(i({max:2e5}),``),headers:g(m(i({max:1e3}),{maxKeys:10}),{}),origin:h(i({max:200}))});o({min:-99999999,max:99999999}),o({min:0,max:100}),o({min:1,max:9999}),o({min:60,max:3600});const x={events:`bus/events`,reportsDue:`bus/reports/due`,reportsAck:`bus/reports/ack`,notify:`bus/notify`,gateway:e=>`bus/gw/${e}`,customerSession:`bus/customers/session`},S={create:`shipments/create`,list:`shipments/list`,sync:`shipments/sync`,print:`shipments/print`,void:`shipments/void`,syncAll:`shipments/sync-all`},Te={issue:`invoices/issue`,get:`invoices/get`,void:`invoices/void`,allowance:`invoices/allowance`},Ee=new TextEncoder,De=new TextDecoder;function C(e){return typeof e==`string`?Ee.encode(e):e}function Oe(e){return De.decode(e)}function w(e){let t=``;for(let n of e)t+=n.toString(16).padStart(2,`0`);return t}function T(e){let t=``;for(let n of e)t+=String.fromCharCode(n);return btoa(t)}function ke(e){let t=e.replace(/-/g,`+`).replace(/_/g,`/`).replace(/\s+/g,``);if(!/^[A-Za-z0-9+/]*={0,2}$/.test(t))return null;try{let e=atob(t.padEnd(Math.ceil(t.length/4)*4,`=`)),n=new Uint8Array(e.length);for(let t=0;t<e.length;t++)n[t]=e.charCodeAt(t);return n}catch{return null}}function Ae(e){return T(e).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=+$/,``)}function je(...e){let t=new Uint8Array(e.reduce((e,t)=>e+t.length,0)),n=0;for(let r of e)t.set(r,n),n+=r.length;return t}function Me(e,t){let n=C(e),r=C(t);if(n.length!==r.length)return!1;let i=0;for(let e=0;e<n.length;e++)i|=n[e]^r[e];return i===0}function E(e){return crypto.getRandomValues(new Uint8Array(e))}function D(e){return e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength)}function Ne(e){return e}async function Pe(e){return new Uint8Array(await crypto.subtle.digest(`SHA-256`,D(C(e))))}async function Fe(e){return w(await Pe(e))}async function Ie(e,t){let n=await crypto.subtle.importKey(`raw`,D(C(e)),{name:`HMAC`,hash:`SHA-256`},!1,[`sign`]);return new Uint8Array(await crypto.subtle.sign(`HMAC`,n,D(C(t))))}async function Le(e,t){return w(await Ie(e,t))}const O={ts:`x-moksa-bus-ts`,id:`x-moksa-bus-id`,sig:`x-moksa-bus-sig`},Re=[O.ts,O.id,O.sig];function ze(e,t,n,r){return`${e}\n${t}\n${n}\n${r}`}function Be(e,t){if(typeof e.get==`function`)return e.get(t)??void 0;let n=e;return n[t]??Object.entries(n).find(([e])=>e.toLowerCase()===t)?.[1]}async function Ve(e,t,n,r,i=Date.now()){if(!e)return{ok:!1,reason:`not_configured`};let a=Be(t,O.ts),o=Be(t,O.id),s=Be(t,O.sig);return!a||!o||!s||!/^\d{1,12}$/.test(a)||o.length>200?{ok:!1,reason:`missing_headers`}:Math.abs(Math.floor(i/1e3)-Number(a))>300?{ok:!1,reason:`stale`}:Me(await Le(e,ze(a,o,n,r)),s.toLowerCase())?{ok:!0,id:o}:{ok:!1,reason:`bad_signature`}}function He(e,t,n,r={}){return Ne({public:!0,methods:[`POST`],request:{body:`text`,headers:Re,maxBytes:r.maxBytes??256*1024},handler:async(r,i)=>{let a=r.input,o=await Ve(await i.settings.get(`busKey`),r.request.headers,e,a);if(!o.ok)return{ok:!1,error:`BUS_${o.reason.toUpperCase()}`};let s=t(a);return s.ok?n(s.value,i,o.id):s.reply}})}function k(t,n,r,i){return He(t,t=>{let r;try{r=JSON.parse(t)}catch{return{ok:!1,reply:{ok:!1,error:`INVALID_JSON`}}}let i=e(n,r);return i.ok?{ok:!0,value:i.value}:{ok:!1,reply:{ok:!1,error:`INVALID_INPUT`,detail:i.error}}},r,i)}function Ue(e,t){return He(e,e=>({ok:!0,value:e}),t)}function We(e){return Math.min(36e5,3e4*2**Math.max(0,e-1))}function Ge(e){return e.storage.reports}async function A(e,t,n=we){let r=n(t);if(!r.ok)throw Error(`invalid report: ${r.error}`);let i=new Date().toISOString(),a={id:t.reportId,report:r.value,status:`pending`,attempts:0,nextAt:i,createdAt:i};return(await Ge(e).compareAndSet(a.id,null,a)).applied?a:await Ge(e).get(a.id)??a}async function Ke(e,t,n=new Date){let r=Ge(e),i=await r.query({where:{status:`pending`,nextAt:{lte:n.toISOString()}},orderBy:{nextAt:`asc`},limit:t}),a=[];for(let{id:e,data:t}of i.items){let i=await r.updateIf(e,{where:{status:`pending`,nextAt:t.nextAt},set:{nextAt:new Date(n.getTime()+We(t.attempts+1)).toISOString()},delta:{attempts:{inc:1}}});i.applied&&a.push(i.data)}return a}async function qe(e,t,n,r){let i=Ge(e);for(let e=0;e<8;e++){let e=await i.getVersioned(t);if(!e)return null;let a=n?{...e.value,status:`done`,doneAt:new Date().toISOString()}:{...e.value,lastError:(r??`failed`).slice(0,300)};if((await i.compareAndSet(t,e.revision,a)).applied)return a}return null}const Je=p({limit:g(o({min:1,max:100}),20)}),Ye=p({id:i({min:1,max:200}),ok:l(),error:h(i({max:500}))});function Xe(){return{[x.reportsDue]:k(x.reportsDue,Je,async(e,t)=>({ok:!0,reports:(await Ke(t,e.limit)).map(e=>({id:e.id,attempts:e.attempts,report:e.report}))})),[x.reportsAck]:k(x.reportsAck,Ye,async(e,t)=>{let n=await qe(t,e.id,e.ok,e.error);return n?{ok:!0,status:n.status}:{ok:!1,error:`NOT_FOUND`}})}}function j(e){return e.storage}function Ze(e,t=`0|Unknown`,n=404){return{ok:!0,outcome:e,reply:t,httpStatus:n,passThrough:!0}}function Qe(e){return e.passThrough?{ok:!0,outcome:e.outcome,passThrough:!0,response:{kind:`redirect`,status:303,location:`/shop`}}:{ok:!0,outcome:e.outcome,response:{kind:`redirect`,status:303,location:e.orderNumber?`/shop/order/${e.orderNumber}`:`/shop`},...e.event?{payment:{...e.event}}:{},...e.paymentInfo?{paymentInfo:e.paymentInfo}:{},...e.paymentFailure?{paymentFailure:e.paymentFailure}:{},...e.orderNumber?{orderNumber:e.orderNumber}:{}}}async function $e(e,t,n,r,i=``){let{trades:a}=j(e),o=await a.query({where:{orderNumber:t},limit:100});if(o.items.some(e=>e.data.status===`paid`))throw Error(`ALREADY_PAID`);for(let e=o.items.length+1;e<1296;e++){let o={merchantTradeNo:`${i}${t}${e.toString(36).toUpperCase().padStart(2,`0`)}`,orderNumber:t,amount:n,method:r,status:`created`,createdAt:new Date().toISOString()};if((await a.compareAndSet(o.merchantTradeNo,null,o)).applied)return o}throw Error(`TOO_MANY_ATTEMPTS`)}async function M(e,t,n){let{trades:r}=j(e);for(let e=0;e<8;e++){let e=await r.getVersioned(t);if(!e)return null;if(e.value.status===`paid`)return e.value;let i={...e.value,...n,updatedAt:new Date().toISOString()};if(n.status===`paid`&&!i.paidAt&&(i.paidAt=i.updatedAt),(await r.compareAndSet(t,e.revision,i)).applied)return i}return await r.get(t)??null}async function et(e,t){return(await j(e).trades.query({where:{orderNumber:t},limit:100})).items.map(e=>e.data).find(e=>e.status===`paid`)??null}async function tt(e,t,n){await j(e).trades.updateIf(t,{where:{},set:{updatedAt:new Date().toISOString()},delta:{refunded:{inc:n}}})}async function nt(e,t,n){return t.ok?(await n(),{ok:!0,action:t.action??`refund`,message:t.message,merchantTradeNo:e}):{ok:!1,error:t.manual?`MANUAL_REFUND`:`REFUND_FAILED`,detail:t.message}}async function rt(e,t,n,r){let{events:i,trades:a}=j(e);if(!(await i.compareAndSet(t.id,null,{id:t.id,type:t.type,createdAt:new Date().toISOString()})).applied)return{ok:!0,handled:!1,duplicate:!0};if(t.type===`order.refund_requested`){let n=r?await at(e,t.payload,r):null;return{ok:!0,handled:!!n,duplicate:!1,...n?{reports:[n]}:{}}}if(t.type===`order.paid`){let e=String(t.payload.orderNumber??``),n=await a.query({where:{orderNumber:e},limit:100});for(let{id:e,data:t}of n.items)t.status===`paid`&&!t.settled&&await a.put(e,{...t,settled:!0})}let o=n?await n(e,t):null;return{ok:!0,handled:!0,duplicate:!1,...o?.reports.length?{reports:o.reports}:{}}}async function it(e,t){let n={kind:`refund`,reportId:`${t.provider}:${t.refundId}`,orderNumber:t.orderNumber,status:`refunded`,amount:t.amount,provider:t.provider,message:t.message.slice(0,300),origin:`plugin`,...t.reason?{reason:t.reason.slice(0,200)}:{}};return await A(e,n),n}async function at(e,t,n){if(t.provider!==n.provider)return null;let r=String(t.orderNumber??``),i=String(t.requestId??``),a=Number(t.amount);if(!r||!i||!Number.isSafeInteger(a)||a<=0)return null;let o,s;try{let t=await n.refund(e,r,a);o=t.ok?`refunded`:t.error===`MANUAL_REFUND`?`manual`:`failed`,s=t.ok?t.message??``:t.detail??t.error??``}catch(e){o=`failed`,s=`退款時發生錯誤,請先到金流後台確認是否已退款:${e.message}`}let c={kind:`refund`,reportId:`${i}:refund`,orderNumber:r,requestId:i,status:o,amount:a,provider:n.provider,message:s.slice(0,300)};return await A(e,c),c}function N(t,n,r=[`GET`,`POST`]){return{permission:`plugins:manage`,methods:r,handler:async(r,i)=>{let a=e(t,r.input??{});return a.ok?n(a.value,i):{ok:!1,error:`INVALID_INPUT`,detail:a.error}}}}function ot(e,t=`23:59:59`){if(!e)return null;let n=/^(\d{4})[-/]?(\d{2})[-/]?(\d{2})(?:[ T](\d{2}):?(\d{2})(?::?(\d{2}))?)?$/.exec(e.trim());if(!n)return null;let[r,i,a]=t.split(`:`),o=new Date(`${n[1]}-${n[2]}-${n[3]}T${n[4]??r}:${n[5]??i}:${n[6]??a}+08:00`);return Number.isNaN(o.getTime())?null:o.toISOString()}const st=/^https?:\/\/[A-Za-z0-9.-]+(?::\d{1,5})?$/,ct=/^https?:\/\/[^\s"<>]+$/;function lt(e){return/^\/[0-9A-Z.+-]{7}$/.test(e)}function ut(e){return/^[A-Z]{2}[0-9]{14}$/.test(e)}function dt(e){return/^[0-9]{3,7}$/.test(e)}function ft(e){if(!/^\d{8}$/.test(e))return!1;let t=[1,2,1,2,1,2,4,1],n=0;for(let r=0;r<8;r++){let i=Number(e[r])*t[r];n+=Math.floor(i/10)+i%10}return n%5==0?!0:e[6]===`7`&&(n+1)%5==0}function pt(e){let t=[];return e.kind===`mobile`&&!lt((e.carrierNum??``).trim().toUpperCase())&&t.push(`手機條碼格式為「/」加 7 碼英數字。`),e.kind===`certificate`&&!ut((e.carrierNum??``).trim().toUpperCase())&&t.push(`自然人憑證條碼為 2 碼大寫英文加 14 碼數字。`),e.kind===`donate`&&!dt((e.loveCode??``).trim())&&t.push(`捐贈碼為 3 到 7 碼數字。`),e.kind===`company`&&(ft((e.taxId??``).trim())||t.push(`統一編號格式不正確。`),(e.companyTitle??``).trim()||t.push(`請填寫公司抬頭。`)),t}function mt(e,t,n=`件`){let r=[],i=0;for(let t of e.lines)i+=t.amount,r.push({name:t.name.slice(0,100)||t.sku,quantity:t.quantity,unit:n,unitPrice:t.unitPrice,amount:t.amount});if(e.shippingFee>0&&(i+=e.shippingFee,r.push({name:`運費`,quantity:1,unit:`式`,unitPrice:e.shippingFee,amount:e.shippingFee})),i!==t){let e=t-i;r.push({name:e<0?`折扣`:`其他`,quantity:1,unit:`式`,unitPrice:e,amount:e})}return r}function ht(e){let t=e.lines.reduce((e,t)=>e+t.amount,0),n=Math.min(e.discountTotal,t),r=0;return e.lines.map((i,a)=>{let o=a===e.lines.length-1?n-r:t>0?Math.floor(n*i.amount/t):0;return r+=o,{sku:i.sku,name:i.name,quantity:i.quantity,amount:i.amount-o,temperature:i.temperature,weightKg:i.weightKg}})}function gt(e){let t=e.shipping?.address;return(`${t?.city??``}${t?.line??``}`.trim()||e.shipping?.store?.address||e.invoice?.address?.trim()||`無`).slice(0,100)}function _t(e=new Date){return new Date(e.getTime()+8*36e5).toISOString().replace(`T`,` `).slice(0,19)}function vt(e){let t=/^(\d{4})[-/](\d{2})[-/](\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2}))?)?/.exec(e.trim());return t?`${t[1]}-${t[2]}-${t[3]} ${t[4]??`00`}:${t[5]??`00`}:${t[6]??`00`}`:_t()}var P=class extends Error{constructor(e){super(e),this.name=`ProviderError`}};const F=()=>new Date().toISOString();function I(e){return e.storage}async function yt(e,t){return I(e).orders.get(t)}async function bt(e,t,n={}){let{orders:r}=I(e),i=await r.get(t.number),a={...t,refundedTotal:i?.refundedTotal??0,lastError:i?.lastError??null,invoiceDueAt:i?.invoiceDueAt??null,...n,updatedAt:F()};return await r.put(t.number,a),a}async function xt(e,t,n){let{orders:r}=I(e);await r.updateIf(t,{where:{},set:{lastError:n,updatedAt:F()}})}async function St(e,t,n){let r=`lock:${t}`,i=await e.kv.getVersioned(r);if(i&&Date.now()-i.value<6e4||!(await e.kv.compareAndSet(r,i?.revision??null,Date.now())).applied)return{busy:!0};try{return await n()}finally{await e.kv.delete(r)}}function Ct(e){return!!(e&&typeof e==`object`&&e.busy===!0)}const wt=new Set([`completed`,`returned`,`failed`,`void`,`unclaimed`]),Tt={created:0,in_progress:1,shipped:2,arrived:3,store_closed:3,unclaimed:4,completed:5,returned:5,failed:5,void:6};function Et(e){return{provider:e.provider,shipmentId:e.shipmentId,carrier:e.carrier,method:e.method,trackingNo:e.trackingNo,trackingUrl:e.trackingUrl,status:e.status,statusText:e.statusText,isCollection:e.isCollection,occurredAt:e.occurredAt}}function Dt(e){return{kind:`shipment`,reportId:`${e.shipmentId}:${e.status}:${e.occurredAt}`,orderNumber:e.orderNumber,shipment:Et(e)}}async function Ot(e,t,n){let{shipments:r}=I(e);for(let i=0;i<8;i++){let i=await r.getVersioned(t);if(!i)return{result:`unknown`};let a=i.value,o={...a.details,...Object.fromEntries(Object.entries(n.details??{}).filter(([,e])=>e))},s=n.trackingNo||a.trackingNo,c=n.providerRef||a.providerRef,l=`duplicate`,u=a.status,d=a.statusText,f=a.statusCode,p=a.statusAt;if(n.status){let e=(n.statusCode??n.status)===(a.statusCode??a.status)&&(n.statusAt??null)===a.statusAt,t=!!(n.statusAt&&a.statusAt&&n.statusAt<a.statusAt),r=wt.has(a.status)&&Tt[n.status]<Tt[a.status];e?l=`duplicate`:t||r||!a.active?l=`stale`:(l=`applied`,u=n.status,d=n.statusText??d,f=n.statusCode??n.status,p=n.statusAt??null)}let m=s!==a.trackingNo;if(l!==`applied`&&!m&&JSON.stringify(o)===JSON.stringify(a.details)&&c===a.providerRef)return{result:l,shipment:a,report:null};let h=l===`applied`||m,g={...a,status:u,statusText:d,statusCode:f,statusAt:p,trackingNo:s,providerRef:c,details:o,open:a.active&&!wt.has(u),occurredAt:h?l===`applied`&&p?p:F():a.occurredAt,updatedAt:F()};if(!(await r.compareAndSet(t,i.revision,g)).applied)continue;let _=h?Dt(g):null;return _&&await A(e,_),{result:h?l===`applied`?`applied`:`duplicate`:l,shipment:g,report:_}}return{result:`unknown`}}function kt(e,t){return e.shipping?.provider===t.pluginId&&t.owns(e.shipping.methodId)}function At(e,t,n){let r=F();return{provider:e.pluginId,shipmentId:n.shipmentId,carrier:n.carrier,method:t.shipping.methodId,trackingNo:n.trackingNo,trackingUrl:n.trackingUrl??null,status:`created`,statusText:n.statusText??`已建立物流單`,isCollection:n.isCollection,occurredAt:r,orderNumber:t.number,createdAt:r,updatedAt:r,active:!0,open:!0,statusCode:null,statusAt:null,providerRef:n.providerRef,details:Object.fromEntries(Object.entries(n.details??{}).filter(([,e])=>e))}}async function jt(e,t,n){let r=await yt(e,n);if(!r)return{ok:!1,error:`NO_ORDER`,detail:`這個外掛沒有這筆訂單的資料(尚未收到訂單事件)。`};if(!kt(r,t))return{ok:!1,error:`NOT_THIS_PROVIDER`,detail:`這筆訂單不是由這個物流外掛出貨。`};if(!r.paidAt&&!r.shipping?.collect)return{ok:!1,error:`NOT_PAID`,detail:`只有已付款或貨到付款的訂單可以建立物流單。`};if([`cancelled`,`refunded`,`failed`].includes(r.status))return{ok:!1,error:`ORDER_CLOSED`,detail:`訂單已取消或退款。`};let i=await St(e,`ship:${n}`,async()=>{let{shipments:i}=I(e),a=(await i.query({where:{orderNumber:n},orderBy:{updatedAt:`desc`},limit:50})).items.map(e=>e.data).filter(e=>e.active&&e.status!==`failed`&&e.status!==`void`),o={ok:!0,shipment:a[0],shipments:a,created:!1,report:null,reports:[]};if(a.length&&!t.multi)return o;let s,c;try{let n=await t.create(e,r,a);s=`shipments`in n?n.shipments:[n],c=`shipments`in n?n.errors:[]}catch(t){let r=t instanceof Error?t.message:String(t);return await xt(e,n,`物流單建立失敗:${r}`),{ok:!1,error:`PROVIDER_ERROR`,detail:r}}if(!s.length)return c.length?(await xt(e,n,`物流單建立失敗:${c.join(` / `)}`),{ok:!1,error:`PROVIDER_ERROR`,detail:c.join(` / `)}):a.length?o:{ok:!1,error:`NOTHING_TO_SHIP`,detail:`這筆訂單沒有需要建立的物流單。`};let l=[],u=[];for(let n of s){let a=At(t,r,n);await i.put(a.shipmentId,a);let o=Dt(a);await A(e,o),l.push(a),u.push(o)}return await xt(e,n,c.length?`部分物流單建立失敗:${c.join(` / `)}`:null),{ok:!0,shipment:l[0],shipments:[...a,...l],created:!0,report:u[0],reports:u,...c.length?{warning:c.join(` / `)}:{}}});return Ct(i)?{ok:!1,error:`BUSY`,detail:`這筆訂單正在建立物流單,請稍後再試。`}:i}async function Mt(e,t,n){await e.kv.set(`map:${t}`,{...n,at:Date.now()})}async function Nt(e,t){if(!t)return null;let n=await e.kv.get(`map:${t}`);if(!n||(await e.kv.delete(`map:${t}`),Date.now()-Number(n.at)>18e5))return null;let{at:r,...i}=n;return i}async function Pt(e,t,n){let r=await I(e).shipments.get(n);if(!r)return{ok:!1,error:`NOT_FOUND`};let i;try{i=await t.sync(e,r)}catch(e){return{ok:!1,error:`PROVIDER_ERROR`,detail:e instanceof Error?e.message:String(e)}}let a=await Ot(e,n,i);return a.result===`unknown`?{ok:!1,error:`NOT_FOUND`}:{ok:!0,shipment:a.shipment,result:a.result}}async function Ft(e,t,n){let{shipments:r}=I(e),i=await r.get(n);if(!i)return{ok:!1,error:`NOT_FOUND`};if(!i.active||i.status===`void`)return{ok:!0,shipment:i};if(Tt[i.status]>=Tt.shipped&&i.status!==`failed`)return{ok:!1,error:`ALREADY_SHIPPED`,detail:`物流單已出貨,無法作廢。`};if(t.cancel)try{await t.cancel(e,i)}catch(e){return{ok:!1,error:`PROVIDER_ERROR`,detail:e instanceof Error?e.message:String(e)}}let a=await Ot(e,n,{status:`void`,statusText:t.cancel?`已取消物流單`:`已在商店作廢(物流商端請至其後台取消)`,statusCode:`void`});if(a.result===`unknown`)return{ok:!1,error:`NOT_FOUND`};let o={...a.shipment,active:!1,open:!1,updatedAt:F()};return await r.put(n,o),{ok:!0,shipment:o}}async function It(e,t,n=20){let r=await I(e).shipments.query({where:{open:!0},orderBy:{updatedAt:`asc`},limit:n}),i=0;for(let{data:n}of r.items)!n.providerRef&&!n.trackingNo||(await Pt(e,t,n.shipmentId)).ok&&i++;return i}function Lt(e){return{provider:e.provider,status:e.status,invoiceNo:e.invoiceNo,invoiceDate:e.invoiceDate,randomNumber:e.randomNumber,kind:e.kind,amount:e.amount,allowanceTotal:e.allowanceTotal,error:e.error,occurredAt:e.occurredAt}}function Rt(e){return{kind:`invoice`,reportId:`${e.orderNumber}:invoice:${e.status}:${e.invoiceNo??`none`}:${e.allowanceTotal}:${e.occurredAt}`,orderNumber:e.orderNumber,invoice:Lt(e)}}async function zt(e,t){await I(e).invoices.put(t.orderNumber,t);let n=Rt(t);return await A(e,n),n}function Bt(e){return`${e}I${w(E(3)).toUpperCase()}`}async function Vt(e){return(await e.settings.get(`invoiceItemWord`)??``).trim().slice(0,2)||`件`}async function Ht(e,t,n){let r=await yt(e,n);if(!r)return{ok:!1,error:`NO_ORDER`,detail:`這個外掛沒有這筆訂單的資料(尚未收到訂單事件)。`};if(r.invoice?.provider!==t.pluginId)return{ok:!1,error:`NOT_THIS_PROVIDER`,detail:`這筆訂單不是由這個發票外掛開立。`};if(!r.paidAt&&!r.shipping?.collect)return{ok:!1,error:`NOT_PAID`,detail:`訂單尚未付款,不能開立發票。`};if(!t.kinds.includes(r.invoice.kind))return{ok:!1,error:`UNSUPPORTED_KIND`,detail:`不支援的發票類型:${r.invoice.kind}`};let i=await St(e,`invoice:${n}`,async()=>{let{invoices:i}=I(e),a=await i.get(n);if(a?.status===`issued`)return{ok:!0,invoice:a,report:null};let o=r.total-r.refundedTotal;if(o<=0)return{ok:!1,error:`ZERO_AMOUNT`,detail:`訂單金額為 0,不需開立發票。`};let s=a?.status===`failed`&&a.relateNumber?a.relateNumber:t.relateNumber?await t.relateNumber(e,r):Bt(n),c={provider:t.pluginId,status:`issued`,invoiceNo:null,invoiceDate:null,randomNumber:null,kind:r.invoice.kind,amount:o,allowanceTotal:0,error:null,occurredAt:F(),orderNumber:n,relateNumber:s,updatedAt:F(),meta:{},allowances:[]};try{let i=await t.issue(e,r,{relateNumber:s,amount:o,lines:mt(r,o,await Vt(e)),discounted:ht(r),buyerAddress:gt(r)}),a={...c,invoiceNo:i.invoiceNo,invoiceDate:i.invoiceDate,randomNumber:i.randomNumber||null,meta:i.meta??{}},l=await zt(e,a);return await xt(e,n,null),{ok:!0,invoice:a,report:l}}catch(t){let r=(t instanceof Error?t.message:String(t)).slice(0,300),i={...c,status:`failed`,error:r},a=await zt(e,i);return await xt(e,n,`電子發票開立失敗:${r}`),{ok:!1,error:`PROVIDER_ERROR`,detail:r,invoice:i,report:a}}});return Ct(i)?{ok:!1,error:`BUSY`,detail:`這筆訂單正在開立發票,請稍後再試。`}:i}async function Ut(e,t,n,r){let{invoices:i}=I(e),a=await i.get(n);if(!a||a.status!==`issued`||!a.invoiceNo)return{ok:!1,error:`NOT_ISSUED`,detail:`這筆訂單沒有已開立的發票。`};let o=await St(e,`invoice:${n}`,async()=>{let o=[...a.allowances];try{for(let[n,i]of o.entries())i.status===`issued`&&(await t.voidAllowance(e,a,i,r),o[n]={...i,status:`void`});await t.void(e,a,r)}catch(e){return await i.put(n,{...a,allowances:o,updatedAt:F()}),{ok:!1,error:`PROVIDER_ERROR`,detail:e instanceof Error?e.message:String(e)}}let s={...a,status:`void`,allowances:o,error:null,occurredAt:F(),updatedAt:F()};return{ok:!0,invoice:s,report:await zt(e,s)}});return Ct(o)?{ok:!1,error:`BUSY`}:o}async function Wt(e,t,n,r,i){let{invoices:a}=I(e),[o,s]=await Promise.all([a.get(n),yt(e,n)]);if(!o||o.status!==`issued`||!o.invoiceNo||!s)return{ok:!1,error:`NOT_ISSUED`,detail:`這筆訂單沒有已開立的發票。`};let c=o.amount-o.allowanceTotal;if(r<1||r>c)return{ok:!1,error:`INVALID_AMOUNT`,detail:`折讓金額需介於 1 到 ${c} 元。`};let l=await St(e,`invoice:${n}`,async()=>{let n;try{n=(await t.allowance(e,o,s,r,i)).allowanceNo}catch(e){return{ok:!1,error:`PROVIDER_ERROR`,detail:e instanceof Error?e.message:String(e)}}let a={...o,allowanceTotal:o.allowanceTotal+r,allowances:[...o.allowances,{allowanceNo:n,amount:r,status:`issued`,createdAt:F()}],occurredAt:F(),updatedAt:F()};return{ok:!0,invoice:a,report:await zt(e,a)}});return Ct(l)?{ok:!1,error:`BUSY`}:l}const Gt={member:`invoiceChannelMember`,mobile:`invoiceChannelMobile`,certificate:`invoiceChannelCert`,paper:`invoiceChannelPaper`,donate:`invoiceAllowDonate`,company:`invoiceAllowB2b`,defaultCarrier:`invoiceDefaultCarrier`,donateOrgs:`invoiceDonateOrgs`};async function Kt(e,t,n={}){let r=e=>n[e]??Gt[e],i=[];for(let n of[`member`,`mobile`,`certificate`,`paper`])t.includes(n)&&await L(e,r(n),!0)&&i.push(n);!i.length&&t.includes(`paper`)&&i.push(`paper`),t.includes(`donate`)&&await L(e,r(`donate`),!0)&&i.push(`donate`),t.includes(`company`)&&await L(e,r(`company`),!0)&&i.push(`company`);let a=String(await e.settings.get(r(`defaultCarrier`))||`member`).replace(/^cert$/,`certificate`);return{kinds:i,defaultKind:i.find(e=>e===a)??i[0],donateOrgs:String(await e.settings.get(r(`donateOrgs`))??``).split(/\r?\n|;/).map(e=>e.split(`|`).map(e=>e.trim())).filter(([e,t])=>e&&t&&/^\d{3,7}$/.test(t)).map(([e,t])=>({code:t,name:e}))}}async function L(e,t,n){let r=await e.settings.get(t);return r==null?n:r===!0}function qt(e,t,n,r){return r===`completed`?e===`shipment.updated`&&t.shipment?.status===`completed`:e===`order.paid`&&!n||e===`order.created`&&n}async function Jt(e,t,n){let r=Math.max(0,Math.min(30,Math.floor(Number(await e.settings.get(`invoiceDelayDays`))||0)));return r>0?(await I(e).orders.updateIf(n,{where:{},set:{invoiceDueAt:new Date(Date.now()+r*864e5).toISOString(),updatedAt:F()}}),null):(await Ht(e,t,n)).report??null}async function Yt(e,t,n=20){let{orders:r}=I(e),i=await r.query({where:{invoiceDueAt:{lte:F()}},orderBy:{invoiceDueAt:`asc`},limit:n}),a=[];for(let{id:n}of i.items){await r.updateIf(n,{where:{},set:{invoiceDueAt:null,updatedAt:F()}});let i=await Ht(e,t,n);i.report&&a.push(i.report)}return a}async function Xt(t,n,r){let i=e(le,n.payload.order);if(!i.ok)return{handled:!1,reports:[]};let a=i.value,o=r.logistics&&kt(a,r.logistics)?r.logistics:void 0,s=r.invoice&&a.invoice?.provider===r.invoice.pluginId?r.invoice:void 0;if(!o&&!s)return{handled:!1,reports:[]};let c=n.type===`order.refunded`?Math.max(0,Number(n.payload.refundedTotal)||0):void 0;await bt(t,a,c===void 0?{}:{refundedTotal:c});let l=[],u=e=>{e&&l.push(e)},d=!!a.shipping?.collect;if(o&&await L(t,`logisticsAutoShip`,!1)&&(!o.usable||await o.usable(t))&&(n.type===`order.created`&&d||n.type===`order.paid`&&!d)){let e=await jt(t,o,a.number);e.ok&&l.push(...e.reports)}if(s){let e=(await t.settings.get(`invoiceIssueWhen`)??`paid`)===`completed`?`completed`:`paid`,r=n.type===`order.cancelled`||n.type===`order.refunded`&&n.payload.full===!0;if(await L(t,`invoiceAuto`,!0)&&qt(n.type,n.payload,d,e))u(await Jt(t,s,a.number));else if(r&&await L(t,`invoiceAutoVoid`,!1)){await I(t).orders.updateIf(a.number,{where:{},set:{invoiceDueAt:null}});let e=n.type===`order.cancelled`?`訂單取消(自動作廢)`:`訂單退款(自動作廢)`,r=await Ut(t,s,a.number,e);r.ok&&u(r.report)}else if(n.type===`order.refunded`&&n.payload.full!==!0&&await L(t,`invoiceAutoAllowance`,!1)){let e=Math.floor(Number(n.payload.amount)||0);if(e>0){let n=await Wt(t,s,a.number,e,`部分退款`);n.ok&&u(n.report)}}r&&await I(t).orders.updateIf(a.number,{where:{},set:{invoiceDueAt:null}})}return{handled:!0,reports:l}}const Zt=/^https?:\/\/[A-Za-z0-9.-]+(?::\d{1,5})?$/;async function Qt(e){let t=(await e.settings.get(`siteUrl`)??``).trim().replace(/\/$/,``);return Zt.test(t)?t:await e.kv.get(`site:origin`)??null}async function $t(e,t){!t||!Zt.test(t)||await e.kv.get(`site:origin`)!==t&&await e.kv.set(`site:origin`,t)}const en=p({temperatures:g(f(u([`normal`,`refrigerated`,`frozen`]),{max:200}),[])});async function tn(e,t,n){let r=Ae(C(JSON.stringify(n))),i=await Le(await e.settings.get(`busKey`)??``,`store:${t}:${r}`);return`/shop/checkout?${new URLSearchParams({shipping:t,store:`${r}.${i}`})}`}const nn=p({orderNumber:i({min:1,max:20,pattern:/^[A-Z0-9]+$/})}),rn=p({shipmentId:i({min:1,max:64})}),an=p({orderNumber:h(i({max:20})),limit:g(o({min:1,max:100,coerce:!0}),20)}),on=p({orderNumber:i({min:1,max:20,pattern:/^[A-Z0-9]+$/}),reason:g(i({min:1,max:50}),`訂單取消`)}),sn=p({orderNumber:i({min:1,max:20,pattern:/^[A-Z0-9]+$/}),amount:o({min:1,max:99999999,coerce:!0}),reason:g(i({min:1,max:50}),`折讓`)});function cn(e){return{...Et(e),orderNumber:e.orderNumber,active:e.active,statusCode:e.statusCode,providerRef:e.providerRef,details:e.details,createdAt:e.createdAt}}function ln(e){return{...Lt(e),orderNumber:e.orderNumber,relateNumber:e.relateNumber,allowances:e.allowances}}function un(e){return{[S.create]:N(nn,async(t,n)=>{let r=await jt(n,e,t.orderNumber);return r.ok?{ok:!0,created:r.created,shipment:cn(r.shipment),shipments:r.shipments.map(cn),...r.warning?{warning:r.warning}:{}}:r},[`POST`]),[S.list]:N(an,async(e,t)=>({ok:!0,shipments:(await I(t).shipments.query({where:e.orderNumber?{orderNumber:e.orderNumber}:void 0,orderBy:{updatedAt:`desc`},limit:e.limit})).items.map(e=>cn(e.data))})),[S.sync]:N(rn,async(t,n)=>{let r=await Pt(n,e,t.shipmentId);return r.ok?{ok:!0,result:r.result,shipment:cn(r.shipment)}:r}),[S.print]:N(rn,async(t,n)=>{let r=await I(n).shipments.get(t.shipmentId);if(!r)return{ok:!1,error:`NOT_FOUND`};try{return{ok:!0,...await e.print(n,r)}}catch(e){return{ok:!1,error:`PROVIDER_ERROR`,detail:e instanceof Error?e.message:String(e)}}}),[S.syncAll]:N(p({}),async(t,n)=>({ok:!0,synced:await It(n,e,100)}),[`POST`]),[S.void]:N(rn,async(t,n)=>{let r=await Ft(n,e,t.shipmentId);return r.ok?{ok:!0,shipment:cn(r.shipment)}:r},[`POST`])}}function dn(e){let t=e=>e.ok?{ok:!0,invoice:ln(e.invoice)}:{ok:!1,error:e.error,detail:e.detail,...e.invoice?{invoice:ln(e.invoice)}:{}};return{[Te.issue]:N(nn,async(n,r)=>t(await Ht(r,e,n.orderNumber)),[`POST`]),[Te.get]:N(nn,async(e,t)=>{let n=await I(t).invoices.get(e.orderNumber);return n?{ok:!0,invoice:ln(n)}:{ok:!1,error:`NOT_FOUND`}}),[Te.void]:N(on,async(n,r)=>t(await Ut(r,e,n.orderNumber,n.reason)),[`POST`]),[Te.allowance]:N(sn,async(n,r)=>t(await Wt(r,e,n.orderNumber,n.amount,n.reason)),[`POST`]),"invoices/due":N(p({}),async(t,n)=>({ok:!0,reports:await Yt(n,e)}),[`POST`]),"invoices/query":N(nn,async(t,n)=>{let r=await I(n).invoices.get(t.orderNumber);if(!r?.relateNumber||!r.invoiceNo)return{ok:!1,error:`NOT_ISSUED`};if(!e.query)return{ok:!1,error:`UNSUPPORTED`};try{return{ok:!0,result:await e.query(n,r)}}catch(e){return{ok:!1,error:`PROVIDER_ERROR`,detail:e instanceof Error?e.message:String(e)}}})}}const fn=p({kind:u([`member`,`mobile`,`certificate`,`paper`,`donate`,`company`]),carrierNum:h(i({max:64})),loveCode:h(i({max:10})),taxId:h(i({max:8})),companyTitle:h(i({max:100}))});function pn(e,t,n){return{"bus/invoice/options":k(`bus/invoice/options`,p({}),async(r,i)=>{if(!await n(i))return{ok:!0,provider:e.pluginId,label:t,kinds:[]};let a=e.checkoutOptions?await e.checkoutOptions(i):{kinds:[...e.kinds]};return{ok:!0,provider:e.pluginId,label:t,...a}}),"bus/invoice/verify":k(`bus/invoice/verify`,fn,async(t,n)=>{if(!(e.checkoutOptions?(await e.checkoutOptions(n)).kinds:e.kinds).includes(t.kind))return{ok:!0,valid:!1,errors:[`不支援的發票類型:${t.kind}`]};let r=pt(t);if(r.length)return{ok:!0,valid:!1,errors:r};if(e.verifyOnline&&await L(n,`invoiceVerifyOnline`,!0))try{let r=await e.verifyOnline(n,{kind:t.kind,carrierNum:t.carrierNum?.trim().toUpperCase(),loveCode:t.loveCode?.trim()});if(r)return{ok:!0,valid:!1,errors:[r]}}catch{}return{ok:!0,valid:!0,errors:[]}})}}const mn={created:`已建立`,in_progress:`處理中`,shipped:`已出貨`,arrived:`已到店`,completed:`已完成`,store_closed:`門市異常`,unclaimed:`逾期未取`,returned:`已退回`,failed:`失敗`,void:`已作廢`},hn={issued:`已開立`,failed:`開立失敗`,void:`已作廢`};async function gn(e,t,n,r){let{shipments:i,orders:a}=I(e),[o,s,c]=await Promise.all([i.query({orderBy:{updatedAt:`desc`},limit:30}),i.count({open:!0}),a.query({orderBy:{updatedAt:`desc`},limit:50})]),l=c.items.map(e=>e.data).filter(e=>e.lastError?.startsWith(`物流`));return{blocks:[{type:`header`,text:t},{type:`stats`,items:[...n,{label:`未完成的物流單`,value:String(s)}]},...r?[{type:`context`,text:r}]:[],...l.length?[{type:`table`,columns:[{key:`order`,label:`訂單`},{key:`error`,label:`最近的自動建單錯誤`}],rows:l.slice(0,10).map(e=>({order:e.number,error:e.lastError??``})),page_action_id:`browse_errors`}]:[],{type:`table`,columns:[{key:`shipmentId`,label:`物流單`},{key:`orderNumber`,label:`訂單`},{key:`carrier`,label:`物流`},{key:`trackingNo`,label:`追蹤號碼`},{key:`status`,label:`狀態`},{key:`updatedAt`,label:`更新時間`}],rows:o.items.map(({data:e})=>({shipmentId:e.shipmentId,orderNumber:e.orderNumber,carrier:`${e.carrier}${e.isCollection?`(代收)`:``}`,trackingNo:e.trackingNo??``,status:`${mn[e.status]??e.status}${e.statusText?`:${e.statusText}`:``}`,updatedAt:e.updatedAt.slice(0,16).replace(`T`,` `)})),page_action_id:`browse_shipments`,empty_text:`尚無物流單。`}]}}async function _n(e,t,n){let{invoices:r}=I(e),[i,a,o]=await Promise.all([r.query({orderBy:{updatedAt:`desc`},limit:30}),r.count({status:`issued`}),r.count({status:`failed`})]);return{blocks:[{type:`header`,text:t},{type:`stats`,items:[...n,{label:`已開立`,value:String(a)},{label:`開立失敗`,value:String(o)}]},{type:`table`,columns:[{key:`orderNumber`,label:`訂單`},{key:`invoiceNo`,label:`發票號碼`},{key:`amount`,label:`金額`},{key:`status`,label:`狀態`},{key:`date`,label:`開立時間`}],rows:i.items.map(({data:e})=>({orderNumber:e.orderNumber,invoiceNo:e.invoiceNo??``,amount:`NT$${e.amount}${e.allowanceTotal?`(折讓 NT$${e.allowanceTotal})`:``}`,status:`${hn[e.status]??e.status}${e.error?`:${e.error}`:``}`,date:e.invoiceDate??``})),page_action_id:`browse_invoices`,empty_text:`尚無發票。`}]}}const vn={import:`migrate/import`,kv:`migrate/kv`,stats:`migrate/stats`};function yn(e,t=[]){let n=p({collection:u(e),mode:g(u([`create`,`replace`]),`create`),items:f(p({id:i({min:1,max:300}),data:m(c(),{maxKeys:200})}),{min:1,max:100})}),r=p({mode:g(u([`create`,`replace`]),`create`),items:f(p({key:i({min:1,max:300}),value:c()}),{min:1,max:100})}),a=(e,t)=>e.storage[t];return{[vn.import]:N(n,async(e,t)=>{let n=a(t,e.collection),r=0,i=0;for(let t of e.items)e.mode===`replace`?(await n.put(t.id,t.data),r++):(await n.compareAndSet(t.id,null,t.data)).applied?r++:i++;return{ok:!0,created:r,skipped:i}},[`POST`]),[vn.kv]:N(r,async(e,n)=>{let r=0,i=0;for(let n of e.items)if(!t.some(e=>n.key.startsWith(e)))return{ok:!1,error:`INVALID_INPUT`,detail:`key ${n.key}`};for(let t of e.items)e.mode===`replace`?(await n.kv.set(t.key,t.value),r++):(await n.kv.compareAndSet(t.key,null,t.value)).applied?r++:i++;return{ok:!0,created:r,skipped:i}},[`POST`]),[vn.stats]:N(p({}),async(t,n)=>({ok:!0,counts:Object.fromEntries(await Promise.all(e.map(async e=>[e,await a(n,e).count()])))}),[`GET`,`POST`])}}function bn(e){if(![16,24,32].includes(e.length))throw Error(`AES key must be 16, 24 or 32 bytes, got ${e.length}`)}function xn(e,t,n){return bn(e),crypto.subtle.importKey(`raw`,D(e),{name:t},!1,n)}function Sn(e,t,n=16){if(t===`none`){if(e.length%n!==0)throw Error(`Data length must be a multiple of the block size without padding`);return e}if(t===`zero`){let t=e.length%n;return t===0&&e.length>0?e:je(e,new Uint8Array(n-t))}let r=n-e.length%n;return je(e,new Uint8Array(r).fill(r))}function Cn(e,t){if(t===`none`)return e;if(t===`zero`){let t=e.length;for(;t>0&&e[t-1]===0;)t--;return e.slice(0,t)}let n=e[e.length-1];if(n===void 0||n===0||n>e.length)return null;for(let t=e.length-n;t<e.length;t++)if(e[t]!==n)return null;return e.slice(0,e.length-n)}async function wn(e,t,n){return new Uint8Array(await crypto.subtle.encrypt({name:`AES-CBC`,iv:D(t)},e,D(n))).slice(0,n.length)}async function Tn(e,t,n){let r=n.slice(n.length-16),i=new Uint8Array(await crypto.subtle.encrypt({name:`AES-CBC`,iv:D(r)},e,new ArrayBuffer(0)));return new Uint8Array(await crypto.subtle.decrypt({name:`AES-CBC`,iv:D(t)},e,D(je(n,i))))}async function En(e,t,n,r=`pkcs7`){let i=C(t);if(i.length!==16)throw Error(`AES-CBC IV must be 16 bytes`);return wn(await xn(C(e),`AES-CBC`,[`encrypt`]),i,Sn(C(n),r))}async function Dn(e,t,n,r=`pkcs7`){let i=C(t);if(i.length!==16)throw Error(`AES-CBC IV must be 16 bytes`);return n.length===0||n.length%16!=0?null:Cn(await Tn(await xn(C(e),`AES-CBC`,[`encrypt`,`decrypt`]),i,n),r)}async function On(e,t,n,r,i=128){let a=await xn(C(e),`AES-GCM`,[`encrypt`]),o={name:`AES-GCM`,iv:D(C(t)),tagLength:i};return r!==void 0&&(o.additionalData=D(C(r))),new Uint8Array(await crypto.subtle.encrypt(o,a,D(C(n))))}async function kn(e,t,n,r,i=128){let a=await xn(C(e),`AES-GCM`,[`decrypt`]),o={name:`AES-GCM`,iv:D(C(t)),tagLength:i};r!==void 0&&(o.additionalData=D(C(r)));try{return new Uint8Array(await crypto.subtle.decrypt(o,a,D(n)))}catch{return null}}function An(e){return Object.fromEntries(new URLSearchParams(e))}function jn(e){return encodeURIComponent(e).replace(/[!'()*~]/g,e=>`%`+e.charCodeAt(0).toString(16).toUpperCase()).replace(/%20/g,`+`)}const R=`methodLabels`,Mn={name:100,desc:300};function Nn(e,t){return String(e??``).trim().slice(0,Mn[t])}async function Pn(e,t){let n=await e.kv.get(R);if(n)return n;let r=new Set(t),i=[...await e.settings.list(`name`),...await e.settings.list(`desc`)].filter(e=>r.has(e.key.slice(4))),a={};for(let{key:e,value:t}of i){let n=e.startsWith(`name`)?`name`:`desc`,r=Nn(t,n);r&&(a[e.slice(4)]={...a[e.slice(4)],[n]:r})}let o=await e.kv.compareAndSet(R,null,a);for(let{key:t}of i)await e.settings.delete(t);return o.applied?a:await e.kv.get(R)??a}function Fn(e,t,n){let r=e[t]??{};return{code:t,label:r.name||n,...r.desc?{description:r.desc}:{}}}const In=`label:`;function Ln(e,t,n,r){return{blocks:[{type:`header`,text:`${e}:付款方式名稱與說明`},{type:`section`,text:`結帳頁顯示的付款方式名稱與說明。名稱留空時用預設名稱,說明留空不顯示。付款方式的開關在外掛設定。`},...t.map(e=>({type:`form`,block_id:`label-${e.code}`,fields:[{type:`text_input`,action_id:`name`,label:`「${e.label}」顯示名稱`,placeholder:e.label,initial_value:n[e.code]?.name??``},{type:`text_input`,action_id:`desc`,label:`「${e.label}」說明`,multiline:!0,initial_value:n[e.code]?.desc??``}],submit:{label:`儲存「${e.label}」`,action_id:`${In}${e.code}`}}))],...r?{toast:r}:{}}}async function Rn(e,t,n,r,i){if(!r.startsWith(In))return null;let a=n.find(e=>e.code===r.slice(6)),o=n.map(e=>e.code);if(!a)return Ln(t,n,await Pn(e,o),{message:`沒有這個付款方式`,type:`error`});await Pn(e,o);let s={name:Nn(i.name,`name`),desc:Nn(i.desc,`desc`)};for(let r=0;r<5;r++){let r=await e.kv.getVersioned(R),i={...r?.value};if(s.name||s.desc?i[a.code]={...s.name?{name:s.name}:{},...s.desc?{desc:s.desc}:{}}:delete i[a.code],(await e.kv.compareAndSet(R,r?.revision??null,i)).applied)return Ln(t,n,i,{message:`已儲存「${a.label}」`,type:`success`})}return Ln(t,n,await Pn(e,o),{message:`儲存失敗,請再試一次`,type:`error`})}async function z(e,t){return await e.kv.get(t.key)??{}}const zn=(e,t)=>e.fieldsFor?.(t)??e.fields;function Bn(e,t,n,r){return{blocks:[{type:`header`,text:e.heading},{type:`section`,text:e.intro},...t.map(t=>({type:`form`,block_id:`${e.action}${t.code}`,fields:zn(e,t).map(e=>({type:`text_input`,action_id:e.id,label:e.label(t),...e.kind===`multiline`?{multiline:!0}:{},...e.placeholder?{placeholder:e.placeholder(t)}:{},initial_value:n[t.code]?.[e.id]===void 0?``:String(n[t.code][e.id])})),submit:{label:`儲存「${t.label}」`,action_id:`${e.action}${t.code}`}}))],...r?{toast:r}:{}}}function Vn(e,t){let n=String(t??``).trim();if(n)return e.kind===`amount`?/^\d{1,8}$/.test(n)?Number(n):null:n.slice(0,e.max??300)}async function Hn(e,t,n,r,i){if(!r.startsWith(t.action))return null;let a=n.find(e=>e.code===r.slice(t.action.length));if(!a)return Bn(t,n,await z(e,t),{message:`沒有這個${t.noun}`,type:`error`});let o={};for(let r of zn(t,a)){let s=Vn(r,i[r.id]);if(s===null)return Bn(t,n,await z(e,t),{message:`「${r.label(a)}」請填 0 以上的整數,或留空`,type:`error`});s!==void 0&&(o[r.id]=s)}for(let r=0;r<5;r++){let r=await e.kv.getVersioned(t.key),i={...r?.value};if(Object.keys(o).length?i[a.code]=o:delete i[a.code],(await e.kv.compareAndSet(t.key,r?.revision??null,i)).applied)return Bn(t,n,i,{message:`已儲存「${a.label}」`,type:`success`})}return Bn(t,n,await z(e,t),{message:`儲存失敗,請再試一次`,type:`error`})}function Un(e){let t=e??{};return t.type!==`form_submit`||typeof t.action_id!=`string`?null:{actionId:t.action_id,values:t.values&&typeof t.values==`object`?t.values:{}}}const Wn=[7,12,17,22,5,9,14,20,4,11,16,23,6,10,15,21],Gn=Array.from({length:64},(e,t)=>Math.floor(Math.abs(Math.sin(t+1))*2**32)>>>0);function Kn(e,t){return(e<<t|e>>>32-t)>>>0}function qn(e){let t=C(e),n=t.length*8,r=new Uint8Array((t.length+8>>6)+1<<6);r.set(t),r[t.length]=128;let i=new DataView(r.buffer);i.setUint32(r.length-8,n>>>0,!0),i.setUint32(r.length-4,Math.floor(n/2**32),!0);let a=1732584193,o=4023233417,s=2562383102,c=271733878,l=Array(16);for(let e=0;e<r.length;e+=64){for(let t=0;t<16;t++)l[t]=i.getUint32(e+t*4,!0);let t=a,n=o,r=s,u=c;for(let e=0;e<64;e++){let i,a;e<16?(i=n&r|~n&u,a=e):e<32?(i=u&n|~u&r,a=(5*e+1)%16):e<48?(i=n^r^u,a=(3*e+5)%16):(i=r^(n|~u),a=7*e%16);let o=t+i+Gn[e]+l[a]>>>0;t=u,u=r,r=n,n=n+Kn(o,Wn[(e>>4)*4+e%4])>>>0}a=a+t>>>0,o=o+n>>>0,s=s+r>>>0,c=c+u>>>0}let u=new Uint8Array(16),d=new DataView(u.buffer);return d.setUint32(0,a,!0),d.setUint32(4,o,!0),d.setUint32(8,s,!0),d.setUint32(12,c,!0),u}function Jn(e){return w(qn(e))}const Yn={merchantId:`3002607`,hashKey:`pwFHCqoQZGmho4w6`,hashIv:`EkRm7iFT261dpevs`},Xn={stage:`payment-stage.ecpay.com.tw`,production:`payment.ecpay.com.tw`},Zn={"%2d":`-`,"%5f":`_`,"%2e":`.`,"%21":`!`,"%2a":`*`,"%28":`(`,"%29":`)`};function Qn(e,t,n){return jn(`HashKey=${t}&${Object.keys(e).filter(e=>e!==`CheckMacValue`).sort((e,t)=>{let n=e.toLowerCase(),r=t.toLowerCase();return n<r?-1:n>r?1:0}).map(t=>`${t}=${e[t]}`).join(`&`)}&HashIV=${n}`).toLowerCase().replace(/%2d|%5f|%2e|%21|%2a|%28|%29/g,e=>Zn[e])}async function B(e,t,n,r=`sha256`){let i=Qn(e,t,n);return(r===`md5`?Jn(i):await Fe(i)).toUpperCase()}async function $n(e,t,n,r=`sha256`){let i=e.CheckMacValue;return!i||!t||!n?!1:Me(i.toUpperCase(),await B(e,t,n,r))}function er(e=new Date){let t=new Date(e.getTime()+8*36e5).toISOString();return`${t.slice(0,4)}/${t.slice(5,7)}/${t.slice(8,10)} ${t.slice(11,19)}`}function tr(e){let t=e.map(e=>e.replace(/#/g,`#`)).join(`#`);return t.length>400?`${t.slice(0,397)}...`:t}const nr=`moksa-ecpay`,rr={normal:1,refrigerated:2,frozen:3},ir={normal:`常溫`,refrigerated:`冷藏`,frozen:`冷凍`},ar=[`normal`,`refrigerated`,`frozen`],or={C2C:{merchantId:`2000933`,hashKey:`XBERn1YOvpM9nfZc`,hashIv:`h1ONHk4P4yqbl5LK`},B2C:{merchantId:`2000132`,hashKey:`5294y06JbISpM5x9`,hashIv:`v77hoKGq4kWxNNIS`}},sr={stage:`logistics-stage.ecpay.com.tw`,production:`logistics.ecpay.com.tw`},cr=ar,V=[`normal`],lr=[{id:`ecpay-cvs-unimart`,setting:`cvsUnimart`,label:`7-ELEVEN 超商取貨`,kind:`cvs`,subtype:{C2C:`UNIMARTC2C`,B2C:`UNIMART`},carrier:`7-ELEVEN`,temps:{C2C:V,B2C:[`normal`,`frozen`]},enabledByDefault:!0},{id:`ecpay-cvs-fami`,setting:`cvsFami`,label:`全家超商取貨`,kind:`cvs`,subtype:{C2C:`FAMIC2C`,B2C:`FAMI`},carrier:`全家`,temps:{C2C:V,B2C:V},enabledByDefault:!0},{id:`ecpay-cvs-hilife`,setting:`cvsHilife`,label:`萊爾富超商取貨`,kind:`cvs`,subtype:{C2C:`HILIFEC2C`,B2C:`HILIFE`},carrier:`萊爾富`,temps:{C2C:V,B2C:V},enabledByDefault:!1},{id:`ecpay-cvs-okmart`,setting:`cvsOkmart`,label:`OK 超商取貨`,kind:`cvs`,subtype:{C2C:`OKMARTC2C`,B2C:`OKMART`},carrier:`OK`,temps:{C2C:V,B2C:V},enabledByDefault:!1},{id:`ecpay-cvs-unimart-freeze`,setting:`cvsUnimartFreeze`,label:`7-ELEVEN 冷凍取貨`,kind:`cvs`,subtype:{C2C:`UNIMARTFREEZE`,B2C:`UNIMARTFREEZE`},carrier:`7-ELEVEN 冷凍`,temps:{C2C:[`frozen`],B2C:[`frozen`]},enabledByDefault:!1},{id:`ecpay-home-tcat`,setting:`homeTcat`,label:`黑貓宅急便`,kind:`home`,subtype:{C2C:`TCAT`,B2C:`TCAT`},carrier:`黑貓宅急便`,temps:{C2C:cr,B2C:cr},enabledByDefault:!0},{id:`ecpay-home-post`,setting:`homePost`,label:`中華郵政`,kind:`home`,subtype:{C2C:`POST`,B2C:`POST`},carrier:`中華郵政`,temps:{C2C:V,B2C:V},enabledByDefault:!1}];function ur(e){return lr.find(t=>t.id===e)}function H(e){return e.endsWith(`C2C`)?`C2C`:`B2C`}function dr(e,t){return e===`UNIMART`&&t===`frozen`?`UNIMARTFREEZE`:e}function fr(e){let t=e.trim(),n=t.startsWith(`1|`);if(!/^[01]\|/.test(t))return{ok:!1,message:t.slice(0,200),fields:{}};let r=t.slice(2);if(!n)return{ok:!1,message:r,fields:{}};let i=Object.fromEntries(new URLSearchParams(r));return{ok:n,message:i.RtnMsg??``,fields:i}}async function pr(e,t,n){let r=e.trim();if(/^[01]\|/.test(r))return fr(r);if(!r.includes(`CheckMacValue=`))return{ok:!1,message:r.slice(0,200),fields:{}};let i=Object.fromEntries(new URLSearchParams(r));return await $n(i,t,n,`md5`)?{ok:!0,message:``,fields:i}:{ok:!1,message:`CheckMacValue mismatch`,fields:{}}}const mr={shipped:[`2030`,`2068`,`3001`,`3006`,`3024`,`3032`,`3119`,`3301`,`3312`],arrived:[`2063`,`2073`,`3018`,`3029`,`3314`],completed:[`2067`,`3022`,`3003`,`3307`,`3308`,`3309`],store_closed:[`2016`,`2026`,`2037`,`2049`,`2050`,`2101`,`2104`],unclaimed:`2065.2074.2076.2078.2079.2080.2081.2082.2083.2084.2085.2086.2087.2088.2089.2092.2093.2072.2044.2070.3020.3025.3019.3031.3023.3310`.split(`.`),failed:[`2033`,`2034`,`2042`,`3303`,`3305`,`3306`,`4004`,`5001`,`5002`]};function hr(e){for(let[t,n]of Object.entries(mr))if(n.includes(e))return t;return`in_progress`}function gr(e){let t=e.replace(/[\^'`!@#$%&*+\\"<>|_[\]]/gu,` `).replace(/\s+/gu,` `).trim(),n=0,r=``;for(let e of t){let t=/[ᄀ-ᅟ⺀-꓏가-힣豈-﫿︰-﹏＀-⦆¢-₩]|[\u{20000}-\u{3fffd}]/u.test(e)?2:1;if(n+t>50)break;n+=t,r+=e}return r.trim()||`網路商品一批`}async function U(e,t){return String(await e.settings.get(t)??``).trim()}async function _r(e,t,n){let r=await e.settings.get(t);if(r==null||r===``)return n;let i=Number(r);return Number.isFinite(i)&&i>=0?i:n}async function vr(e,t,n){let r=await e.settings.get(t);return r==null?n:r===!0}async function yr(e){return await U(e,`logisticsEnvironment`)!==`production`}async function W(e,t){let n=await yr(e),r=t===`C2C`?`C2c`:`B2c`,i=await U(e,`logistics${r}MerchantId`),a=await e.settings.get(`logistics${r}HashKey`)??``,o=await e.settings.get(`logistics${r}HashIv`)??``;return i&&a&&o?{merchantId:i,hashKey:a,hashIv:o,stage:n}:n?{...or[t],stage:n}:{merchantId:i,hashKey:a,hashIv:o,stage:n}}async function br(e,t){let n=await W(e,t);return!!(n.merchantId&&n.hashKey&&n.hashIv)}function xr(e){return`https://${e?sr.stage:sr.production}`}async function Sr(e){return vr(e,`logisticsEnabled`,!1)}async function G(e){return await U(e,`logisticsContract`)===`B2C`?`B2C`:`C2C`}async function Cr(e,t){return await vr(e,t.setting,t.enabledByDefault)?t.id===`ecpay-cvs-unimart-freeze`?await G(e)===`B2C`:t.id===`ecpay-cvs-okmart`?await G(e)===`C2C`:!0:!1}async function wr(e,t){return t.temps[await G(e)]}const Tr=lr.map(e=>({code:e.id,label:e.label})),Er=e=>lr.some(t=>t.id===e&&[...t.temps.C2C,...t.temps.B2C].some(e=>e!==`normal`)),Dr=e=>lr.some(t=>t.id===e&&![...t.temps.C2C,...t.temps.B2C].includes(`normal`)),Or=[{id:`name`,label:e=>`「${e.label}」顯示名稱`,kind:`text`,max:100,placeholder:e=>e.label},{id:`desc`,label:e=>`「${e.label}」說明`,kind:`multiline`,max:300},{id:`fee`,label:e=>`「${e.label}」運費(常溫)`,kind:`amount`,placeholder:e=>e.code.includes(`-cvs-`)?`留空:用外掛設定的超商取貨運費`:`留空:用外掛設定的宅配運費`},{id:`coldFee`,label:e=>`「${e.label}」低溫運費(購物車含冷藏或冷凍時)`,kind:`amount`,placeholder:()=>`留空:用外掛設定的低溫配送運費`},{id:`freeMin`,label:e=>`「${e.label}」免運門檻`,kind:`amount`,placeholder:()=>`留空:用商店的全站免運門檻;0 為一律免運`},{id:`spec`,label:e=>`「${e.label}」包裹尺寸(1=60 公分、2=90 公分、3=120 公分)`,kind:`amount`,placeholder:()=>`留空:1(60 公分)`}],kr={key:`shippingMethods`,heading:`綠界物流:物流方式設定`,intro:`結帳頁顯示的物流方式名稱、說明、運費與免運門檻。留空的欄位沿用外掛設定的運費與商店的全站免運門檻。物流方式的開關在外掛設定。`,noun:`物流方式`,action:`ship:`,fields:Or,fieldsFor:e=>Or.filter(t=>(t.id!==`coldFee`||Er(e.code))&&(t.id!==`fee`||!Dr(e.code))&&(t.id!==`spec`||e.code===`ecpay-home-tcat`))};async function Ar(e,t){if(!await Sr(e))return[];let n=[...new Set((t.length?t:[`normal`]).map(e=>e===`refrigerated`||e===`frozen`?e:`normal`))],r=n.some(e=>e!==`normal`),i=await z(e,kr),a=[];for(let t of lr){if(!await Cr(e,t))continue;let o=await wr(e,t);if(!n.every(e=>o.includes(e)))continue;let s=i[t.id]??{},c=e=>typeof s[e]==`number`?s[e]:void 0,l=c(r?`coldFee`:`fee`)??await _r(e,r?`coldFee`:t.kind===`cvs`?`cvsFee`:`homeFee`,r?160:t.kind===`cvs`?60:100),u=c(`freeMin`);a.push({id:t.id,label:typeof s.name==`string`&&s.name?s.name:t.label,...typeof s.desc==`string`&&s.desc?{description:s.desc}:{},...u===void 0?{}:{freeMin:u},carrier:t.carrier,requires:t.kind===`cvs`?`store`:`address`,fee:Math.floor(l),collect:t.id!==`ecpay-home-post`,maxAmount:null,...t.kind===`cvs`?{mapUrl:`/api/shop/ecpay/map?shipping=${encodeURIComponent(t.id)}&sbx=1`}:{}})}return a}const jr=(e,t)=>({ok:!0,response:{kind:`text`,status:e,body:t}});function Mr(e){return`${e}L${w(E(3)).toUpperCase()}`.slice(0,20)}async function Nr(e,t){let n=new URLSearchParams(t.query);if(n.get(`sbx`)!==`1`)return{...jr(404,`not ours`),passThrough:!0};let r=ur(n.get(`shipping`)??``);if(!r||r.kind!==`cvs`||!await Sr(e)||!await Cr(e,r))return jr(400,`不支援的運送方式。`);let i=await Qt(e);if(!i)return jr(503,`尚未設定網站網址。`);let a=r.subtype[await G(e)],o=await W(e,H(a)),s=Mr(`MAP`);await Mt(e,s,{methodId:r.id});let c={MerchantID:o.merchantId,MerchantTradeNo:s,LogisticsType:`CVS`,LogisticsSubType:a,IsCollection:n.get(`cod`)===`1`?`Y`:`N`,ServerReplyURL:`${i}/api/shop/ecpay/map-reply`,ExtraData:``,Device:n.get(`mobile`)===`1`?`1`:`0`};return c.CheckMacValue=await B(c,o.hashKey,o.hashIv,`md5`),{ok:!0,response:{kind:`form`,title:`正在開啟門市地圖…`,action:`${xr(o.stage)}/Express/map`,params:c}}}async function Pr(e,t){let n=An(t.body),r=await Nt(e,n.MerchantTradeNo??``);if(!r)return{ok:!0,passThrough:!0,response:{kind:`text`,status:403,body:`Unknown trade`}};let i=ur(r.methodId??``),a=(n.CVSStoreID??``).trim();if(!i||!a||a.length>20)return{ok:!0,response:{kind:`redirect`,status:303,location:`/shop/checkout?store_error=1`}};let o={provider:nr,type:n.LogisticsSubType||i.subtype[await G(e)],id:a,name:(n.CVSStoreName??``).slice(0,100),address:(n.CVSAddress??``).slice(0,200)};return{ok:!0,response:{kind:`redirect`,status:303,location:await tn(e,i.id,o)}}}function Fr(e,t){let n=t.includes(`normal`)?`normal`:t[0]??`normal`,r=new Map;for(let i of ht(e)){let a=i.temperature??e.shipping?.temperature??`normal`;t.includes(a)||(a=n);let o=r.get(a)??{temp:a,amount:0,names:[],weightKg:0};o.amount+=i.amount,o.names.push(i.name),o.weightKg+=(i.weightKg??0)*i.quantity,r.set(a,o)}let i=ar.filter(e=>r.has(e)).map(e=>r.get(e));return i[0]&&e.shippingFee>0&&(i[0].amount+=e.shippingFee),i}async function Ir(e,t){return(await U(e,`senderZipcode`)).replace(/\D/g,``)||(/^\s*(\d{3,6})/.exec(t)?.[1]??``)}function Lr(e,t){let n=0,r=``;for(let i of e){let e=/[ᄀ-ᅟ⺀-꓏가-힣豈-﫿︰-﹏＀-⦆¢-₩]|[\u{20000}-\u{3fffd}]/u.test(i)?2:1;if(n+e>t)break;n+=e,r+=i}return r}async function Rr(e,t,n,r,i,a=new Date){let o=t.shipping,s=![`TCAT`,`POST`].includes(i),c=o.collect&&n.amount>0,l=s&&!c?Math.min(2e4,Math.max(1,n.amount)):Math.max(1,n.amount),u=await W(e,H(i)),d=await Qt(e);if(!d)throw new P(`尚未設定網站網址(siteUrl),綠界無法回傳物流狀態。`);let f=await U(e,`senderAddress`),p={MerchantID:u.merchantId,MerchantTradeNo:r,MerchantTradeDate:er(a),LogisticsType:s?`CVS`:`HOME`,LogisticsSubType:i,GoodsAmount:String(l),IsCollection:c?`Y`:`N`,CollectionAmount:String(c?l:0),GoodsName:gr(n.names.join(`#`)),SenderName:Lr(await U(e,`senderName`),10),SenderPhone:await U(e,`senderPhone`),SenderCellPhone:await U(e,`senderCellphone`),ReceiverName:[...(o.address?.name||t.customer.name).trim()].slice(0,10).join(``),ReceiverPhone:``,ReceiverCellPhone:t.customer.phone.replace(/\D/g,``),ReceiverEmail:t.email,TradeDesc:``,ServerReplyURL:`${d}/api/shop/ecpay/logistics-notify`,ClientReplyURL:``,LogisticsC2CReplyURL:``,Remark:``,PlatformID:``};if(s){if(!o.store?.id)throw new P(`訂單沒有取貨門市。`);p.ReceiverStoreID=o.store.id,p.ReturnStoreID=o.store.id}else{if(!o.address?.line)throw new P(`訂單沒有收件地址。`);p.SenderZipCode=await Ir(e,f),p.SenderAddress=f,p.ReceiverZipCode=o.address.zip??``,p.ReceiverAddress=`${o.address.city??``}${o.address.line}`.trim(),p.Temperature=`000${rr[n.temp]}`,p.Distance=`00`;let t=Number((await z(e,kr))[o.methodId]?.spec??1);p.Specification=`000${t>=1&&t<=3?t:1}`,p.ScheduledPickupTime=`4`,p.ScheduledDeliveryTime=`4`,p.GoodsWeight=String(n.weightKg>0?Math.min(20,Math.max(1,Math.ceil(n.weightKg))):1)}return p.CheckMacValue=await B(p,u.hashKey,u.hashIv,`md5`),{payload:p,cred:u}}async function zr(e,t,n){if(!e.http)throw new P(`沒有對外連線權限。`);let r;try{r=await e.http.fetch(t,{method:`POST`,headers:{"content-type":`application/x-www-form-urlencoded`},body:new URLSearchParams(n).toString()})}catch(e){throw new P(`無法連線綠界物流:${e instanceof Error?e.message:String(e)}`)}return r.text()}function Br(e){return e.CVSPaymentNo?`${e.CVSPaymentNo}${e.CVSValidationNo??``}`:e.BookingNote||e.AllPayLogisticsID||null}const Vr=new Set([`300`,`2001`]);async function Hr(e,t,n){let r=t.shipping?ur(t.shipping.methodId):void 0;if(!t.shipping||!r)throw new P(`這筆訂單不是綠界物流的運送方式。`);let i=r.subtype[await G(e)],a=Fr(t,await wr(e,r));if(!a.length)throw new P(`這筆訂單沒有可出貨的商品。`);let o=new Set(n.map(e=>e.details.溫層)),s=a.length>1,c=[],l=[];for(let n of a){let a=ir[n.temp];if(o.has(a))continue;let u=dr(i,n.temp),d=H(u);if(!await br(e,d)){l.push(`${a}(${d} 帳號)尚未設定金鑰`);continue}let f=s?`${Mr(t.number).slice(0,18)}T${rr[n.temp]}`:Mr(t.number),{payload:p,cred:m}=await Rr(e,t,n,f,u),h;try{h=await zr(e,`${xr(m.stage)}/Express/Create`,p)}catch(e){l.push(`${a}物流單建立失敗:${e instanceof Error?e.message:String(e)}`);continue}let g=fr(h),_=g.fields;if(!g.ok||!Vr.has(_.RtnCode??``)){l.push(`${a}物流單建立失敗:${g.ok?`${_.RtnMsg??``}(狀態碼 ${_.RtnCode??`?`})`:g.message}`);continue}c.push({shipmentId:f,carrier:u===`UNIMARTFREEZE`?`7-ELEVEN 冷凍`:r.carrier,trackingNo:Br(_),providerRef:_.AllPayLogisticsID||null,isCollection:t.shipping.collect,statusText:_.RtnMsg||`已建立物流單`,details:{子類型:u,溫層:a,綠界物流編號:_.AllPayLogisticsID??``,寄貨編號:_.CVSPaymentNo??``,驗證碼:_.CVSValidationNo??``,託運單號:_.BookingNote??``,金額:`NT$${p.GoodsAmount}${t.shipping.collect?`(代收)`:``}`}})}if(!c.length&&l.length)throw new P(l.join(` / `));return{shipments:c,errors:l}}async function Ur(e,t){let n=await W(e,H(t.details.子類型??`UNIMARTC2C`)),r={MerchantID:n.merchantId,AllPayLogisticsID:t.providerRef??``,TimeStamp:String(Math.floor(Date.now()/1e3))};r.CheckMacValue=await B(r,n.hashKey,n.hashIv,`md5`);let i=await pr(await zr(e,`${xr(n.stage)}/Helper/QueryLogisticsTradeInfo/V2`,r),n.hashKey,n.hashIv),a=i.fields.LogisticsStatus;if(!i.ok||!a)throw new P(`查詢物流狀態失敗:${i.message||`no LogisticsStatus in response`}`);return{status:hr(a),statusText:i.fields.LogisticsStatusMsg||i.fields.RtnMsg||`狀態 ${a}`,statusCode:a}}function Wr(e){return jn(e).replace(/%2[Aa]/g,`*`).replace(/%21/g,`!`).replace(/%28/g,`(`).replace(/%29/g,`)`)}const Gr=new Set([`UNIMARTC2C`,`UNIMART`,`UNIMARTFREEZE`,`POST`]);function Kr(e,t){return t===`A6`&&Gr.has(e)?2:1}async function qr(e){return await U(e,`labelMode`)===`2`?`A6`:`A4`}async function Jr(e,t){let n=t.details.子類型??`UNIMARTC2C`;return Xr(e,n,[t.providerRef??``],Kr(n,await qr(e)))}async function Yr(e,t,n){let r=n??await qr(e),i=new Map,a=[];for(let e of t){if(!e.active||!e.providerRef){a.push(`${e.shipmentId}:${e.active?`沒有綠界物流編號`:`物流單已作廢`}`);continue}let t=e.details.子類型??`UNIMARTC2C`;i.set(t,[...i.get(t)??[],e])}let o=[];for(let[t,n]of i){let i=Kr(t,r);try{let{html:r}=await Xr(e,t,n.map(e=>e.providerRef),i);o.push({subtype:t,mode:i===2?`A6`:`A4`,shipmentIds:n.map(e=>e.shipmentId),html:r})}catch(e){a.push(`${t}:${e instanceof Error?e.message:String(e)}`)}}return{labels:o,errors:a}}async function Xr(e,t,n,r){if(!e.http)throw new P(`沒有對外連線權限。`);let i=await W(e,H(t)),a=JSON.stringify({MerchantID:i.merchantId,LogisticsID:n,LogisticsSubType:t,PrintMode:r}),o=JSON.stringify({MerchantID:i.merchantId,RqHeader:{Timestamp:Math.floor(Date.now()/1e3),Revision:`1.0.0`},Data:T(await En(i.hashKey,i.hashIv,Wr(a),`pkcs7`))}),s;try{s=await e.http.fetch(`${xr(i.stage)}/Express/v2/PrintTradeDocument`,{method:`POST`,headers:{"content-type":`application/json`},body:o})}catch(e){throw new P(`無法連線綠界物流:${e instanceof Error?e.message:String(e)}`)}let c=await s.text();if(s.status!==200)throw new P(`ECPay HTTP ${s.status}: ${c.slice(0,200)}`);return{html:c}}const Zr={pluginId:nr,owns:e=>!!ur(e),multi:!0,usable:Sr,create:Hr,sync:Ur,print:Jr};async function Qr(e,t){let n=An(t.body),r=await I(e).shipments.get(n.MerchantTradeNo??``);if(!r)return{...jr(404,`Order not found`),passThrough:!0};let i=await W(e,H(r.details.子類型||n.LogisticsSubType||`UNIMARTC2C`));if(!i.hashKey||!i.hashIv||(n.MerchantID??``)!==i.merchantId||!await $n(n,i.hashKey,i.hashIv,`md5`))return jr(400,`CheckMacValue mismatch`);let a=n.RtnCode??``,o=await Ot(e,r.shipmentId,{status:hr(a),statusText:n.RtnMsg||`狀態 ${a}`,statusCode:a,trackingNo:Br(n),providerRef:n.AllPayLogisticsID||null});return{ok:!0,outcome:o.result,response:{kind:`text`,status:200,body:`1|OK`},reports:o.result!==`unknown`&&o.report?[o.report]:[]}}const $r={merchantId:`2000132`,hashKey:`ejCk326UnaZWKisg`,hashIv:`q9jcZX8Ib9LM8wYk`},ei={stage:`einvoice-stage.ecpay.com.tw`,production:`einvoice.ecpay.com.tw`};async function ti(e,t,n){return T(await En(t,n,jn(JSON.stringify(e)),`pkcs7`))}async function ni(e,t,n){let r=ke(e);if(!r)return null;let i=await Dn(t,n,r,`pkcs7`);if(!i)return null;try{return JSON.parse(decodeURIComponent(Oe(i).replace(/\+/g,` `)))}catch{return null}}async function ri(e){let t=String(await e.settings.get(`invoiceMerchantId`)??``).trim(),n=await e.settings.get(`invoiceHashKey`)??``,r=await e.settings.get(`invoiceHashIv`)??``;return t&&n&&r?{merchantId:t,hashKey:n,hashIv:r,stage:await e.settings.get(`invoiceEnvironment`)!==`production`}:{...$r,stage:!0}}async function ii(e){return await e.settings.get(`invoiceEnabled`)===!0}async function K(e,t,n){if(!e.http)throw new P(`沒有對外連線權限。`);let r=await ri(e),i={MerchantID:r.merchantId,RqHeader:{Timestamp:Math.floor(Date.now()/1e3),Revision:`3.0.0`},Data:await ti({MerchantID:r.merchantId,...n},r.hashKey,r.hashIv)},a;try{a=await e.http.fetch(`https://${r.stage?ei.stage:ei.production}/B2CInvoice/${t}`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify(i)})}catch(e){throw new P(`無法連線綠界電子發票:${e instanceof Error?e.message:String(e)}`)}let o=await a.json().catch(()=>null);if(!o||Number(o.TransCode)!==1||!o.Data)throw new P(`綠界發票 API 傳輸失敗:${o?.TransMsg??`HTTP ${a.status}`}`);let s=await ni(o.Data,r.hashKey,r.hashIv);if(!s)throw new P(`綠界發票回應無法解密,請確認 HashKey/HashIV。`);return s}function ai(e,t){if(Number(e.RtnCode)!==1)throw new P(`${t}:${e.RtnMsg}(${e.RtnCode})`)}function oi(e,t,n){let r=[],i=0,a=(e,t,n,i,a)=>r.push({ItemSeq:r.length+1,ItemName:[...e].slice(0,100).join(``),ItemCount:t,ItemWord:n,ItemPrice:i,ItemTaxType:`1`,ItemAmount:a});for(let e of t)i+=e.amount,a(e.name,e.quantity,`批`,e.quantity>0?Math.round(e.amount/e.quantity):0,e.amount);return e.shippingFee>0&&(i+=e.shippingFee,a(e.shipping?.label||`運費`,1,`式`,e.shippingFee,e.shippingFee)),i!==n&&a(`其他`,1,`式`,n-i,n-i),r}function si(e,t){return e===`company`||e===`donate`||e===`paper`?{CarrierType:``,CarrierNum:``}:e===`mobile`?{CarrierType:`3`,CarrierNum:t??``}:e===`certificate`?{CarrierType:`2`,CarrierNum:t??``}:{CarrierType:`1`,CarrierNum:``}}function ci(e,t){let n=e.invoice,r=n.kind===`company`&&!!n.taxId,i=n.kind===`donate`&&!!n.loveCode,a=r||n.kind===`paper`;if(a&&t.buyerAddress===`無`)throw new P(`列印發票(紙本或統編)需要買受人地址,這筆訂單沒有收件或門市地址。請補上地址後重新開立。`);let o=t.buyerAddress===`無`?``:t.buyerAddress;return{RelateNumber:t.relateNumber,CustomerID:``,CustomerIdentifier:r?n.taxId??``:``,CustomerName:[...(r?n.companyTitle:e.customer.name)||`消費者`].slice(0,60).join(``),CustomerAddr:[...o].slice(0,100).join(``),CustomerPhone:e.customer.phone,CustomerEmail:e.email,Print:a?`1`:`0`,Donation:i?`1`:`0`,LoveCode:i?n.loveCode??``:``,...si(r||i?`company`:n.kind,n.carrierNum),TaxType:`1`,SalesAmount:t.amount,InvoiceRemark:``,Items:oi(e,t.discounted,t.amount),InvType:`07`,vat:`1`}}const li=e=>(e.meta.invoiceDate??e.invoiceDate??``).slice(0,10);async function ui(e,t){let n=String(await e.settings.get(`invoicePrefix`)??``).replace(/[^A-Za-z0-9]/g,``).slice(0,5);return`${n?`${n}INV`:``}${t.number}R${w(E(4))}`.slice(0,30)}const di={pluginId:nr,kinds:[`member`,`mobile`,`certificate`,`paper`,`donate`,`company`],checkoutOptions:e=>Kt(e,di.kinds),relateNumber:ui,async issue(e,t,n){let r=await K(e,`Issue`,ci(t,n));ai(r,`電子發票開立失敗`);let i=String(r.InvoiceDate??``);return{invoiceNo:String(r.InvoiceNo),invoiceDate:vt(i),randomNumber:String(r.RandomNumber??``),meta:{invoiceDate:i}}},async void(e,t,n){ai(await K(e,`Invalid`,{InvoiceNo:t.invoiceNo,InvoiceDate:li(t),Reason:n.slice(0,20)}),`作廢失敗`)},async allowance(e,t,n,r){let i=await K(e,`Allowance`,{InvoiceNo:t.invoiceNo,InvoiceDate:li(t),AllowanceNotify:`E`,CustomerName:n.customer.name,NotifyMail:n.email,AllowanceAmount:r,Items:[{ItemSeq:1,ItemName:[...n.lines[0]?.name||`其他`].slice(0,100).join(``),ItemCount:1,ItemWord:`式`,ItemPrice:r,ItemTaxType:`1`,ItemAmount:r}]});return ai(i,`折讓開立失敗`),{allowanceNo:String(i.IA_Allow_No??i.AllowanceNo??``)}},async voidAllowance(e,t,n,r){ai(await K(e,`AllowanceInvalid`,{InvoiceNo:t.invoiceNo,AllowanceNo:n.allowanceNo,Reason:r.slice(0,20)}),`折讓作廢失敗`)},async query(e,t){return K(e,`GetIssue`,{RelateNumber:t.relateNumber})},async verifyOnline(e,t){if(t.kind===`donate`){let n=await K(e,`CheckLoveCode`,{LoveCode:t.loveCode});if(Number(n.RtnCode)===1&&n.IsExist!==`Y`)return`查無此捐贈碼。`}else if(t.kind===`mobile`){let n=await K(e,`CheckBarcode`,{BarCode:t.carrierNum});if(Number(n.RtnCode)===1&&n.IsExist!==`Y`)return`查無此手機條碼,請確認後再輸入。`}return null}},q={Credit:`信用卡`,Credit3:`信用卡分 3 期`,Credit6:`信用卡分 6 期`,Credit12:`信用卡分 12 期`,Credit18:`信用卡分 18 期`,Credit24:`信用卡分 24 期`,Credit30N:`信用卡分 30 期(永豐)`,UnionPay:`銀聯卡`,ATM:`ATM 轉帳`,CVS:`超商代碼繳費`,BARCODE:`超商條碼繳費`,WebATM:`網路 ATM`,ApplePay:`Apple Pay`,TWQR:`TWQR 行動支付`,BNPL:`無卡分期(裕富/中租)`,WeiXin:`微信支付`,Jkopay:`街口支付`,iPASS:`一卡通 iPASS MONEY`,ALL:`綠界付款頁自選`,CreditBind:`信用卡(定期定額自動扣款)`},fi=Object.keys(q),pi={Credit:{choose:`Credit`,card:!0},Credit3:{choose:`Credit`,installment:3,card:!0},Credit6:{choose:`Credit`,installment:6,card:!0},Credit12:{choose:`Credit`,installment:12,card:!0},Credit18:{choose:`Credit`,installment:18,card:!0},Credit24:{choose:`Credit`,installment:24,card:!0},Credit30N:{choose:`Credit`,installment:`30N`,card:!0},UnionPay:{choose:`Credit`,unionPay:!0,card:!0},ATM:{choose:`ATM`,offline:!0,max:49999},CVS:{choose:`CVS`,offline:!0,max:2e4},BARCODE:{choose:`BARCODE`,offline:!0,max:2e4},WebATM:{choose:`WebATM`},ApplePay:{choose:`ApplePay`,card:!0},TWQR:{choose:`TWQR`,min:6,max:49999},BNPL:{choose:`BNPL`,min:50,max:5e5},WeiXin:{choose:`WeiXin`,min:6,max:5e5},Jkopay:{choose:`DigitalPayment`,sub:`Jkopay`},iPASS:{choose:`DigitalPayment`,sub:`iPASS`},ALL:{choose:`ALL`},CreditBind:{choose:`Credit`,card:!0}};function mi(e){if(!e)return``;let t={Credit_CreditCard:`信用卡`,Flexible_Installment:`信用卡分期(永豐 30 期)`,BARCODE_BARCODE:`超商條碼`,CVS_CVS:`超商代碼`,TWQR_OPAY:`TWQR 行動支付`,WeiXin_OPAY:`微信支付`,BNPL_URICH:`無卡分期(裕富)`,BNPL_ZINGALA:`無卡分期(中租)`,DigitalPayment_Jkopay:`街口支付`,DigitalPayment_IPASS:`一卡通 iPASS MONEY`,ApplePay:`Apple Pay`};if(t[e])return t[e];let n=[[`Credit_`,`信用卡`],[`WebATM_`,`網路 ATM`],[`ATM_`,`ATM 轉帳`],[`CVS_`,`超商代碼`],[`BNPL_`,`無卡分期`],[`DigitalPayment_`,`電子支付`],[`ApplePay`,`Apple Pay`]].find(([t])=>e.startsWith(t));return n?`${n[1]}(${e})`:e}const hi=`/api/shop/ecpay/notify`,gi=new Set([`2`,`10100073`]);async function J(e){let t=await e.settings.get(`environment`)===`production`?`production`:`stage`,n=(await e.settings.get(`merchantId`)??``).trim(),r=await e.settings.get(`hashKey`)??``,i=await e.settings.get(`hashIv`)??``;return n&&r&&i?{environment:t,merchantId:n,hashKey:r,hashIv:i}:t===`stage`?{environment:t,...Yn}:null}async function _i(e,t){let n=[];for(let r of fi){if(r===`CreditBind`)continue;let i=await e.settings.get(`method${r}`);if(!(i===!0||i==null&&r===`Credit`))continue;let a=pi[r];if(!(t!=null&&(a.min!=null&&t<a.min||a.max!=null&&t>a.max))){if(r===`Credit30N`){let n=await vi(e);if(t==null?n===1/0:!(t>n))continue}n.push(r)}}return n}async function vi(e){let t=Number(await e.settings.get(`credit30NMinAmount`));return Number.isFinite(t)&&t>0?t:1/0}const yi=fi.map(e=>({code:e,label:q[e]}));async function bi(e,t,n,r){let i=Math.floor(Number(await e.settings.get(t)));return Number.isFinite(i)&&i>=1?Math.min(i,r):n}async function xi(e){return String(await e.settings.get(`orderPrefix`)??``).replace(/[^A-Za-z0-9]/g,``).slice(0,5)}async function Si(e,t,n){let r=pi[n.method],i=String(await e.settings.get(`itemName`)??``).trim(),a={MerchantID:t.merchantId,MerchantTradeNo:n.merchantTradeNo,MerchantTradeDate:er(n.now),PaymentType:`aio`,TotalAmount:String(Math.round(n.amount)),TradeDesc:`線上購物`,ItemName:tr(i?[i]:n.itemNames),ReturnURL:`${n.origin}${hi}`,ChoosePayment:r.choose,EncryptType:`1`,ClientBackURL:n.orderUrl,NeedExtraPaidInfo:`Y`};return r.sub&&(a.ChooseSubPayment=r.sub),r.installment&&(a.CreditInstallment=String(r.installment)),r.unionPay&&(a.UnionPay=`1`),n.method===`Credit`&&await e.settings.get(`creditRedeem`)===!0&&(a.Redeem=`Y`),r.card||(a.PaymentInfoURL=`${n.origin}${hi}`),(n.method===`ATM`||n.method===`ALL`)&&(a.ExpireDate=String(await bi(e,`atmExpireDays`,3,60))),n.method===`CVS`&&(a.StoreExpireDate=String(Math.min(43200,await bi(e,`cvsExpireDays`,7,30)*1440))),n.method===`BARCODE`&&(a.StoreExpireDate=String(await bi(e,`barcodeExpireDays`,7,30))),r.offline||(a.OrderResultURL=`${n.origin}/api/shop/gw/ecpay/return`),a.CheckMacValue=await B(a,t.hashKey,t.hashIv),{action:`https://${Xn[t.environment]}/Cashier/AioCheckOut/V5`,params:a}}const Ci=[`BankCode`,`vAccount`,`PaymentNo`,`ExpireDate`,`Barcode1`,`Barcode2`,`Barcode3`,`BNPLTradeNo`,`BNPLInstallment`],wi=[`card4no`,`card6no`,`gwsr`,`auth_code`,`stage`,`process_date`];async function Ti(e,t){let n=An(t),r=await J(e);if(!r)return Ze(`not_configured`,`0|Not configured`,503);if(!await $n(n,r.hashKey,r.hashIv))return Ze(`bad_checkmac`,`0|CheckMacValue Error`,400);if(n.MerchantID!==r.merchantId)return Ze(`bad_merchant`,`0|MerchantID Error`,400);let i=n.MerchantTradeNo??``,a=await j(e).trades.get(i);if(!a)return Ze(`unknown_trade`,`0|Unknown MerchantTradeNo`,404);let o={ok:!0,reply:`1|OK`,httpStatus:200,orderNumber:a.orderNumber},s=n.SimulatePaid===`1`;if(s&&r.environment===`production`)return{...o,outcome:`ignored_simulated`};let c=n.RtnCode??``,l=n.TradeNo??``;if(c===`1`){let t=Number(n.TradeAmt)===a.amount,r={};for(let e of wi)n[e]&&(r[e]=n[e]);return await M(e,i,{status:t?`paid`:`failed`,tradeNo:l,paymentType:n.PaymentType,resultCode:c,resultMessage:t?n.RtnMsg:`amount_mismatch:${n.TradeAmt}`,simulated:s,...Object.keys(r).length?{info:r}:{}}),{...o,outcome:t?`paid`:`amount_mismatch`,event:{eventId:`ecpay:${l||i}:paid`,provider:`ecpay`,orderNumber:a.orderNumber,amount:Number(n.TradeAmt),status:`paid`,tradeNo:l,simulated:s}}}if(gi.has(c)){let t={PaymentType:n.PaymentType??``};for(let e of Ci)n[e]&&(t[e]=n[e]);if((await M(e,i,{status:`awaiting`,tradeNo:l,paymentType:n.PaymentType,resultCode:c,resultMessage:n.RtnMsg,info:t}))?.status===`paid`)return{...o,outcome:`already_paid`};let r=(n.PaymentType??``).startsWith(`BNPL`)||a.method===`BNPL`;return{...o,outcome:`payment_info`,paymentInfo:{eventId:`ecpay:${l||i}:info`,provider:`ecpay`,orderNumber:a.orderNumber,merchantTradeNo:i,tradeNo:l,info:t,expiresAt:r?new Date(Date.now()+6048e5).toISOString():ot(n.ExpireDate)}}}return(await M(e,i,{status:`failed`,tradeNo:l,paymentType:n.PaymentType,resultCode:c,resultMessage:n.RtnMsg}))?.status===`paid`?{...o,outcome:`already_paid`}:{...o,outcome:`failed`,event:{eventId:`ecpay:${l||i}:rtn${c}`,provider:`ecpay`,orderNumber:a.orderNumber,amount:Number(n.TradeAmt)||0,status:`failed`,tradeNo:l,simulated:s}}}async function Ei(e,t,n){if(!e.http)throw Error(`NO_NETWORK`);let r=await e.http.fetch(t,{method:`POST`,headers:{"content-type":`application/x-www-form-urlencoded`},body:new URLSearchParams(n).toString()});return{status:r.status,reply:An((await r.text()).trim())}}async function Di(e,t,n){let r={MerchantID:t.merchantId,MerchantTradeNo:n,TimeStamp:String(Math.floor(Date.now()/1e3))};r.CheckMacValue=await B(r,t.hashKey,t.hashIv);let{status:i,reply:a}=await Ei(e,`https://${Xn[t.environment]}/Cashier/QueryTradeInfo/V5`,r),o=await $n(a,t.hashKey,t.hashIv);return{ok:i>=200&&i<300,status:i,verified:o,tradeStatus:a.TradeStatus??null,tradeAmt:a.TradeAmt??null,merchantTradeNo:a.MerchantTradeNo??null,paymentType:a.PaymentType??null}}const Oi=e=>/^(Credit_|ApplePay)/.test(e.paymentType??``)||!!(pi[e.method]?.card&&!e.paymentType),ki=e=>!!pi[e.method]?.installment||Number(e.info?.stage??0)>0||e.paymentType===`Flexible_Installment`;async function Ai(e,t,n,r,i){let a={MerchantID:t.merchantId,MerchantTradeNo:n.merchantTradeNo,TradeNo:n.tradeNo??``,Action:r,TotalAmount:String(i)};a.CheckMacValue=await B(a,t.hashKey,t.hashIv);let{status:o,reply:s}=await Ei(e,`https://${Xn[t.environment]}/CreditDetail/DoAction`,a);return{ok:s.RtnCode===`1`,code:s.RtnCode??``,message:s.RtnMsg??`HTTP ${o}`}}async function ji(e,t,n){if(!Oi(t))return{ok:!1,manual:!0,message:`這筆以 ${mi(t.paymentType)||`非信用卡`} 付款,綠界沒有退款 API,請在綠界(或該支付的)廠商後台退款,或以轉帳等方式人工退款。`};let r=t.amount-(t.refunded??0);if(n>r)return{ok:!1,message:`退款金額超過可退餘額 NT$${r}。`};if(ki(t)&&n!==r)return{ok:!1,message:`分期付款的交易只能全額退刷(NT$${r})。`};let i=await J(e);if(!i)return{ok:!1,message:`綠界金流未設定。`};let a=await Ai(e,i,t,`R`,n);if(a.ok)return{ok:!0,action:`refund`,message:`綠界信用卡退刷成功(NT$${n})`};if(n===r){let r=await Ai(e,i,t,`N`,n);return r.ok?{ok:!0,action:`release`,message:`綠界信用卡授權已取消(尚未請款)`}:{ok:!1,message:`綠界退刷失敗:${a.message};取消授權也失敗:${r.message}`}}return{ok:!1,message:`綠界退刷失敗:${a.message}(${a.code})。尚未請款的交易只能全額取消授權。`}}async function Mi(e,t){if(t.status!==`paid`||!Oi(t))return{ok:!1,message:`只有已付款的信用卡交易可以請款。`};let n=await J(e);if(!n)return{ok:!1,message:`綠界金流未設定。`};let r=t.amount-(t.refunded??0),i=await Ai(e,n,t,`C`,r);return i.ok?{ok:!0,message:`綠界信用卡請款(關帳)成功(NT$${r})`}:{ok:!1,code:i.code,message:`綠界請款失敗:${i.message}(${i.code})`}}const Ni={stage:`ecpayment-stage.ecpay.com.tw`,production:`ecpayment.ecpay.com.tw`},Pi={Authorized:`已授權(尚未請款)`,"To be captured":`請款中`,Captured:`已請款`,Refunded:`已退刷`,Cancelled:`已取消授權`,Failed:`失敗`};async function Fi(e,t){if(!Oi(t))return{ok:!1,error:`NOT_CARD`,message:`這筆不是信用卡交易。`};let n=await J(e);if(!n)return{ok:!1,error:`NOT_CONFIGURED`,message:`綠界金流未設定。`};if(!e.http)return{ok:!1,error:`NO_NETWORK`,message:`沒有網路權限。`};let r={MerchantID:n.merchantId,RqHeader:{Timestamp:Math.floor(Date.now()/1e3)},Data:await ti({MerchantID:n.merchantId,MerchantTradeNo:t.merchantTradeNo},n.hashKey,n.hashIv)},i=await e.http.fetch(`https://${Ni[n.environment]}/1.0.0/CreditDetail/QueryTrade`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify(r)}),a={};try{a=await i.json()}catch{return{ok:!1,error:`BAD_REPLY`,message:`綠界回應無法解析(HTTP ${i.status})`}}if(Number(a.TransCode)!==1)return{ok:!1,error:`ECPAY`,message:`綠界查詢失敗:${String(a.TransMsg??`HTTP ${i.status}`)}`};let o=await ni(String(a.Data??``),n.hashKey,n.hashIv),s=o?.RtnValue;if(!s)return{ok:!1,error:`ECPAY`,message:`綠界查詢失敗:${o?.RtnMsg||`沒有資料`}`};let c=String(s.Status??``);return{ok:!0,status:c,statusLabel:Pi[c]??c,amount:Number(s.Amount)||0,captured:Number(s.ClsAmt)||0,authorizedAt:String(s.AuthTime??``),history:(Array.isArray(o?.CloseData)?o.CloseData:[]).map(e=>({status:String(e.Status??``),amount:Number(e.Amount)||0,at:String(e.DateTime??``)}))}}const Ii=i({min:1,max:20,pattern:/^[A-Z0-9]+$/}),Li=i({min:1,max:40,pattern:/^[a-z0-9-]+$/}),Ri=i({min:6,max:20,pattern:/^S[A-Z0-9]+$/}),zi=o({min:0,max:99999999}),Y=i({min:1,max:120});o({min:1,max:99}),o({min:5,max:86400});const Bi=p({brand:g(i({max:20}),``),last4:g(i({max:4,pattern:/^\d{0,4}$/}),``),expiry:g(i({max:7}),``)}),Vi=[`paid`,`failed`,`declined`,`uncertain`],Hi=p({kind:d(`mandate`),reportId:i({min:1,max:200}),provider:Li,subscriptionId:h(Ri),orderNumber:h(_(Ii)),mandateId:Y,status:u([`active`,`updated`,`blocked`,`suspended`,`cancelled`,`failed`]),display:h(Bi),reason:g(i({max:200}),``)}),Ui=p({kind:d(`recurring-charge`),reportId:i({min:1,max:200}),provider:Li,subscriptionId:Ri,mandateId:Y,orderNumber:Ii,amount:zi,itemName:i({min:1,max:200}),attempt:o({min:1,max:50}),chargeRef:Y}),Wi=p({kind:d(`recurring-result`),reportId:i({min:1,max:200}),provider:Li,subscriptionId:Ri,orderNumber:Ii,chargeRef:Y,outcome:u(Vi),code:g(i({max:40}),``),message:g(i({max:300}),``),tradeNo:g(i({max:64}),``),occurredAt:i({min:10,max:40})}),Gi=p({kind:d(`period-charge`),reportId:i({min:1,max:200}),provider:Li,mandateId:Y,seq:Y,status:u([`paid`,`failed`]),amount:zi,tradeNo:g(i({max:64}),``),occurredAt:i({min:10,max:40}),message:g(i({max:300}),``)}),Ki=p({kind:d(`mandate-action`),reportId:i({min:1,max:200}),provider:Li,subscriptionId:Ri,mandateId:Y,action:u([`cancel`,`suspend`,`resume`,`amount`,`query`]),amount:h(zi)}),qi={mandate:Hi,"recurring-charge":Ui,"recurring-result":Wi,"period-charge":Gi,"mandate-action":Ki};function Ji(t){let n=qi[String(t?.kind)];return n?e(n,t):{ok:!1,error:`kind: not a subscription report`,issues:[{path:`kind`,message:`not a subscription report`}]}}const Yi={"recurring-charge":`bus/recurring/charge`,"mandate-action":`bus/recurring/action`},Xi=`vaultKey`;async function Zi(e){for(let t=0;t<4;t++){let t=await e.settings.get(Xi),n=t?ke(t):null;if(n&&n.length===32)return n;let r=T(E(32));if((await e.settings.compareAndSet(Xi,null,r)).applied)return ke(r)}throw Error(`VAULT_UNAVAILABLE`)}async function Qi(e,t,n){let r=E(12),i=await On(await Zi(e),r,C(n),C(t));return`v1:${T(r)}:${T(i)}`}async function $i(e,t,n){let[r,i,a]=n.split(`:`),o=i?ke(i):null,s=a?ke(a):null;if(r!==`v1`||!o||!s)return null;let c=await kn(await Zi(e),o,s,C(t));return c?Oe(c):null}async function ea(e,t){return(await Le(await Zi(e),`card:${t}`)).slice(0,40)}function X(e){return e.storage}const ta=()=>new Date().toISOString();async function na(e,t){let n=ta(),r={id:t.id,provider:t.provider,status:`active`,sealed:await Qi(e,t.id,t.token),cardRef:await ea(e,t.token),memberId:t.memberId,email:t.email,orderNumber:t.orderNumber,display:t.display,reason:``,createdAt:n,updatedAt:n};if(!(await X(e).mandates.compareAndSet(t.id,null,r)).applied)return null;let i={kind:`mandate`,reportId:`mandate:${t.id}:active`,provider:t.provider,orderNumber:t.orderNumber,mandateId:t.id,status:`active`,display:t.display,reason:``};return await A(e,i,Ji),i}async function ra(e,t){let n=await X(e).mandates.get(t);if(!n)return{ok:!1,code:`MANDATE_NOT_FOUND`,message:`找不到這張綁定的卡片`};if(n.status!==`active`)return{ok:!1,code:`MANDATE_INACTIVE`,message:n.reason||`這張綁定的卡片已停用`};let r=await $i(e,t,n.sealed);return r===null?{ok:!1,code:`TOKEN_UNREADABLE`,message:`無法讀取綁定卡片的資料(金鑰不符)`}:{ok:!0,mandate:n,token:r}}async function ia(e,t,n,r,i){let{mandates:a}=X(e);for(let e=0;e<8;e++){let e=await a.getVersioned(t.id);if(!e||e.value.status!==`active`)return;if((await a.compareAndSet(t.id,e.revision,{...e.value,status:n,reason:r.slice(0,200),updatedAt:ta()})).applied)break}i&&await A(e,{kind:`mandate`,reportId:`mandate:${t.id}:${n}`,provider:t.provider,orderNumber:t.orderNumber,mandateId:t.id,status:n,reason:r.slice(0,200)},Ji)}const aa=5*6e4;async function oa(e,t){if(t.state===`calling`)return[];let n={kind:`recurring-result`,reportId:`rr:${t.provider}:${t.id}:${t.state}`,provider:t.provider,subscriptionId:t.subscriptionId,orderNumber:t.orderNumber,chargeRef:t.id,outcome:t.state,code:t.code.slice(0,40),message:t.message.slice(0,300),tradeNo:t.tradeNo.slice(0,64),occurredAt:t.updatedAt},r=[];if(t.state===`paid`){let n={kind:`payment`,reportId:`recurring-payment:${t.provider}:${t.gatewayRef}`,orderNumber:t.orderNumber,payment:{eventId:`${t.provider}:recurring:${t.gatewayRef}`.slice(0,120),provider:t.provider,orderNumber:t.orderNumber,amount:t.amount,status:`paid`,tradeNo:t.tradeNo.slice(0,64)}};await A(e,n),r.push(n)}return await A(e,n,Ji),r.push(n),r}async function sa(e,t,n,r){let{charges:i}=X(e);for(let e=0;e<8;e++){let e=await i.getVersioned(t);if(!e)return null;if(!r.includes(e.value.state))return{row:e.value,moved:!1};let a={...e.value,state:n.outcome,code:(n.code??``).slice(0,40),message:(n.message??``).slice(0,300),tradeNo:(n.tradeNo??e.value.tradeNo).slice(0,64),amount:n.outcome===`paid`&&typeof n.amount==`number`?n.amount:e.value.amount,checks:e.value.state===`uncertain`?e.value.checks+1:e.value.checks,updatedAt:ta()};if((await i.compareAndSet(t,e.revision,a)).applied)return{row:a,moved:!0}}return null}async function ca(e,t,n,r,i){let a=await sa(e,n.id,r,i);if(!a?.moved)return{rec:a?.row??n,reports:[]};let o=a.row;if(o.state===`paid`&&await t.onPaid?.(e,o),r.revoked){let t=await X(e).mandates.get(o.mandateId);t&&await ia(e,t,`failed`,r.message||`綁定的卡片已失效`,!0)}return{rec:o,reports:await oa(e,o)}}async function la(e,t,n,r=Date.now()){if(n.provider!==t.provider)return{ok:!0,result:`invalid`,detail:`provider ${n.provider}`};let{charges:i}=X(e),a=new Date(r).toISOString(),o={id:n.chargeRef,reportId:n.reportId,provider:t.provider,subscriptionId:n.subscriptionId,mandateId:n.mandateId,orderNumber:n.orderNumber,amount:n.amount,attempt:n.attempt,gatewayRef:await t.gatewayRef(e,n),state:`calling`,code:``,message:``,tradeNo:``,checks:0,startedAt:a,updatedAt:a};if(!(await i.compareAndSet(o.id,null,o)).applied){let n=await i.get(o.id);if(!n)return{ok:!1,error:`CONFLICT`};if(n.state===`calling`&&r-Date.parse(n.startedAt)>aa){let r=await ca(e,t,n,(t.lookup?await t.lookup(e,n).catch(()=>null):null)??{outcome:`uncertain`,code:`INTERRUPTED`,message:`扣款途中中斷,請向金流確認這筆是否已扣款`},[`calling`]);return{ok:!0,result:`applied`,state:r.rec.state,reports:r.reports}}return n.state!==`calling`&&await oa(e,n),{ok:!0,result:`duplicate`,state:n.state}}let s=await ra(e,n.mandateId),c;if(!s.ok)c={outcome:`declined`,code:s.code,message:s.message};else try{c=await t.charge(e,{mandate:s.mandate,token:s.token,report:n,gatewayRef:o.gatewayRef})}catch(e){c={outcome:`uncertain`,code:`EXCEPTION`,message:String(e?.message??e).slice(0,200)}}let l=await ca(e,t,o,c,[`calling`]);return{ok:!0,result:`applied`,state:l.rec.state,reports:l.reports}}async function ua(e,t,n={}){if(!t.lookup)return 0;let r=n.now??Date.now(),{charges:i}=X(e),a=await i.query({where:n.mandateId?{mandateId:n.mandateId}:{state:{in:[`uncertain`,`calling`]}},orderBy:{updatedAt:`asc`},limit:n.limit??20}),o=0;for(let{data:n}of a.items){if(n.state!==`uncertain`&&!(n.state===`calling`&&r-Date.parse(n.startedAt)>aa))continue;let i=await ca(e,t,n,await t.lookup(e,n).catch(()=>null)??{outcome:`uncertain`,code:n.code||`INTERRUPTED`,message:n.message||`扣款途中中斷,請向金流確認這筆是否已扣款`},[`calling`,`uncertain`]);i.rec.state!==`uncertain`&&i.reports.length&&o++}return o}async function da(e,t,n){if(n.provider!==t.provider)return{ok:!0,result:`invalid`,detail:`provider ${n.provider}`};let{mandates:r}=X(e),i=await r.get(n.mandateId);if(!i)return{ok:!0,result:`stale`};if(n.action===`query`)return{ok:!0,result:`applied`,resolved:await ua(e,t,{mandateId:i.id})};if(n.action!==`cancel`)return{ok:!0,result:`applied`};if(i.status!==`active`)return{ok:!0,result:`duplicate`};await ia(e,i,`cancelled`,`訂閱已結束或已換卡`,!1);let a=await r.query({where:{cardRef:i.cardRef,status:`active`},limit:1}),o=null;if(!a.items.length&&t.revoke){let n=await $i(e,i.id,i.sealed);o=n?await t.revoke(e,i,n).catch(e=>({ok:!1,message:String(e.message)})):{ok:!1,message:`TOKEN_UNREADABLE`},o.ok||e.log.warn(`recurring: could not delete the saved card at the gateway`,{mandate:i.id,message:o.message})}return{ok:!0,result:`applied`,revoked:o?.ok??!1,shared:a.items.length>0}}function fa(t){let n=Yi[`recurring-charge`],r=Yi[`mandate-action`];return{[n]:k(n,c(),async(n,r)=>{let i=e(Ui,n);return i.ok?la(r,t,i.value):{ok:!0,result:`invalid`,detail:i.error}}),[r]:k(r,c(),async(n,r)=>{let i=e(Ki,n);return i.ok?da(r,t,i.value):{ok:!0,result:`invalid`,detail:i.error}})}}async function pa(e,t){let n={};return t.orderNumber&&(n.orderNumber=t.orderNumber),t.state&&(n.state=t.state),(await X(e).charges.query({where:n,orderBy:{updatedAt:`desc`},limit:t.limit??20})).items.map(e=>e.data)}async function ma(e,t){return(await X(e).mandates.query({...t.orderNumber?{where:{orderNumber:t.orderNumber}}:{},orderBy:{updatedAt:`desc`},limit:t.limit??20})).items.map(({data:{sealed:e,cardRef:t,...n}})=>n)}const Z=`CreditBind`,ha=`/api/shop/gw/ecpay/bind-notify`,ga={stage:{api:`https://ecpg-stage.ecpay.com.tw`,sdk:`https://ecpg-stage.ecpay.com.tw/Scripts/sdk-1.0.0.js`,serverType:`Stage`,query:`https://ecpayment-stage.ecpay.com.tw`},production:{api:`https://ecpg.ecpay.com.tw`,sdk:`https://ecpg.ecpay.com.tw/Scripts/sdk-1.0.0.js`,serverType:`Prod`,query:`https://ecpayment.ecpay.com.tw`}},_a=10300066,va=new Set([10100248,10100251,10100252,10100254,10100255,10100256]),ya=new Set([10100251,10100255,10100256]),ba=e=>e.storage.members,xa=()=>new Date().toISOString(),Sa=()=>w(E(16));function Q(e){return ga[e.environment]}async function Ca(e,t){let n=(await Fe(`ecpay-member:${t.trim().toLowerCase()}`)).slice(0,40),r={id:n,memberId:`msk${w(E(12))}`,createdAt:xa()};return await ba(e).compareAndSet(n,null,r),(await ba(e).get(n))?.memberId??r.memberId}function wa(e,t){let n=t.email&&/^[a-zA-Z\d._%+-]+@[a-zA-Z\d.-]+\.[a-zA-Z]{2,6}$/.test(t.email)&&t.email.length<=100?t.email:``,r=(t.phone??``).replace(/\D+/g,``).slice(0,20),i=(t.name??``).replace(/[^\p{Script=Han}A-Za-z0-9 :,.()/–-]+/gu,``).trim().slice(0,50);return Object.fromEntries(Object.entries({MerchantMemberID:e,Email:n,Phone:r,Name:i,CountryCode:`158`}).filter(([,e])=>e!==``))}function Ta(e){return(e.map(e=>e.replace(/#/g,` `)).join(`#`).trim()||`線上購物`).slice(0,200)}async function Ea(e,t){let n,r=new Promise((e,r)=>{n=setTimeout(()=>r(Error(`TIMEOUT`)),t)});try{return await Promise.race([e,r])}finally{clearTimeout(n)}}async function Da(e,t,n,r,i=25e3){if(!e.http)return{ok:!1,transCode:-1,message:`NO_NETWORK`,data:{},unreadable:!1};let a=JSON.stringify({MerchantID:t.merchantId,RqHeader:{Timestamp:Math.floor(Date.now()/1e3)},Data:await ti(r,t.hashKey,t.hashIv)}),o;try{let t=await Ea(e.http.fetch(n,{method:`POST`,headers:{"content-type":`application/json`},body:a}),i);if(t.status!==200)return{ok:!1,transCode:-1,message:`HTTP ${t.status}`,data:{},unreadable:!1};o=await Ea(t.json(),i)}catch(e){return{ok:!1,transCode:-1,message:String(e?.message??e).slice(0,200),data:{},unreadable:!1}}let s=Number(o?.TransCode??0),c=s===1?await ni(String(o.Data??``),t.hashKey,t.hashIv):null;return c?{ok:Number(c.RtnCode)===1,transCode:s,message:String(c.RtnMsg??``),data:c,unreadable:!1}:{ok:!1,transCode:s,message:String(o?.TransMsg??`ECPay data could not be decoded`),data:{},unreadable:s===1}}async function Oa(e,t){let n=t;return!n||String(n.MerchantID??``)!==e.merchantId||typeof n.Data!=`string`?null:ni(n.Data,e.hashKey,e.hashIv)}function ka(e){return/^4/.test(e)?`VISA`:/^(5[1-5]|2[2-7])/.test(e)?`Mastercard`:/^35/.test(e)?`JCB`:/^3[47]/.test(e)?`AMEX`:/^62/.test(e)?`UnionPay`:`信用卡`}const Aa=e=>{let t=new URL(e.orderUrl);return`${t.pathname}${t.search}`};async function ja(e,t){if(!t.email)return{ok:!1,error:`MEMBER_REQUIRED`,detail:`綁定信用卡需要會員 Email。`};if(t.amount>2e5)return{ok:!1,error:`AMOUNT_TOO_LARGE`,detail:`綠界綁卡交易金額不可超過 20 萬元。`};let n=await xi(e);if(n.length+t.orderNumber.length+2>20)return{ok:!1,error:`TRADE_NO_TOO_LONG`};let r;try{r=await $e(e,t.orderNumber,t.amount,Z,n)}catch(e){return{ok:!1,error:e.message}}let i={...r,stepToken:Sa(),resultKey:Sa(),origin:t.origin.replace(/\/$/,``),orderUrl:t.orderUrl,email:t.email,name:t.customer?.name??``,phone:t.customer?.phone??``,itemName:Ta(t.itemNames),memberId:await Ca(e,t.email)};return await j(e).trades.put(r.merchantTradeNo,i),{ok:!0,action:`${i.origin}/shop/pay/ecpay-bind`,params:{a:i.merchantTradeNo,t:i.stepToken},formMethod:`get`,merchantTradeNo:i.merchantTradeNo}}async function Ma(e,t,n){let r=await j(e).trades.get(t);return r&&r.method===Z&&r.stepToken&&Me(r.stepToken,n)?r:null}async function Na(e,t){let n=await Ma(e,t.attemptNo,t.token);if(!n)return{ok:!1,error:`NOT_FOUND`};let r=await J(e);if(!r)return{ok:!1,error:`NOT_CONFIGURED`};let i={ok:!0,orderNumber:n.orderNumber,orderPath:Aa(n),amount:n.amount,sdk:{url:Q(r).sdk,serverType:Q(r).serverType}};if(n.status!==`created`)return{...i,payable:!1};if(n.token&&n.tokenExpiresAt&&Date.parse(n.tokenExpiresAt)-Date.now()>6e4)return{...i,payable:!0,cardToken:n.token};if(n.token)return{...i,payable:!1,expired:!0};let a=await Da(e,r,`${Q(r).api}/Merchant/GetTokenbyBindingCard`,{PlatformID:``,MerchantID:r.merchantId,ConsumerInfo:wa(n.memberId,n),OrderInfo:{MerchantTradeDate:er(),MerchantTradeNo:n.merchantTradeNo,TotalAmount:n.amount,TradeDesc:n.itemName,ItemName:n.itemName,ReturnURL:`${n.origin}${ha}`},OrderResultURL:`${n.origin}/api/shop/gw/ecpay/bind-result?a=${encodeURIComponent(n.merchantTradeNo)}&k=${n.resultKey}`}),o=String(a.data.Token??``);if(!a.ok||!o)return await M(e,n.merchantTradeNo,{status:`created`,resultCode:String(a.data.RtnCode??a.transCode),resultMessage:a.message.slice(0,200)}),{ok:!1,error:`TOKEN_FAILED`,detail:a.message};let s=String(a.data.TokenExpireDate??``),c=/^(\d{4})\/(\d{2})\/(\d{2}) (\d{2}):(\d{2}):(\d{2})$/.exec(s),l=c?new Date(`${c[1]}-${c[2]}-${c[3]}T${c[4]}:${c[5]}:${c[6]}+08:00`).toISOString():new Date(Date.now()+30*6e4).toISOString();return await j(e).trades.put(n.merchantTradeNo,{...n,token:o,tokenExpiresAt:l,updatedAt:xa()}),{...i,payable:!0,cardToken:o}}const $=(e,t)=>({ok:!0,response:{kind:`text`,status:e,body:JSON.stringify(t),contentType:`application/json; charset=utf-8`}});function Pa(e){try{let t=new URL(String(e??``));return t.protocol===`https:`&&/(^|\.)ecpay\.com\.tw$/.test(t.hostname)?t.href:null}catch{return null}}async function Fa(e,t){let n;try{n=JSON.parse(t)}catch{return $(400,{error:`格式錯誤`})}let r=await Ma(e,String(n.a??``),String(n.t??``));if(!r)return $(404,{error:`找不到這筆付款`});if(r.status===`paid`)return $(200,{redirect:Aa(r)});let i=String(n.payToken??``);if(!/^[A-Za-z0-9]{8,64}$/.test(i))return $(400,{error:`沒有收到卡片資料,請重新輸入。`});let a=await J(e);if(!a)return $(503,{error:`綠界金流未設定`});let o=await Da(e,a,`${Q(a).api}/Merchant/CreateBindCard`,{PlatformID:``,MerchantID:a.merchantId,BindCardPayToken:i,MerchantMemberID:r.memberId}),s=Pa(o.data.ThreeDInfo?.ThreeDURL);if(o.ok&&s)return $(200,{redirect:s});let c=Number(o.data.RtnCode??0);if(!o.ok&&c!==_a)return await M(e,r.merchantTradeNo,{status:`created`,resultCode:String(c||o.transCode),resultMessage:o.message.slice(0,200)}),$(200,{error:o.message?`刷卡沒有成功:${o.message}`:`刷卡沒有成功,請確認卡片後再試一次。`});let l=await Ia(e,r,o.data,!1);return{...$(200,{redirect:Aa(r)}),...l.extra}}async function Ia(e,t,n,r){if(String(n.SimulatePaid??`0`)===`1`)return{outcome:`ignored_simulated`,extra:{}};let i=Number(n.RtnCode??0),a=n.OrderInfo??{},o=n.CardInfo??{},s=String(a.TradeNo??``);if(i===_a)return await M(e,t.merchantTradeNo,{status:`awaiting`,tradeNo:s,resultCode:String(i),resultMessage:String(n.RtnMsg??``).slice(0,200)}),{outcome:`pending`,extra:{}};if(i!==1||String(a.TradeStatus??``)!==`1`)return(await M(e,t.merchantTradeNo,{status:`failed`,tradeNo:s,resultCode:String(i),resultMessage:String(n.RtnMsg??``).slice(0,200)}))?.status===`paid`?{outcome:`already_paid`,extra:{}}:{outcome:`failed`,extra:{payment:{eventId:`ecpay:${t.merchantTradeNo}:rtn${i}`,provider:`ecpay`,orderNumber:t.orderNumber,amount:Number(a.TradeAmt)||0,status:`failed`,tradeNo:s}}};let c=Number(a.TradeAmt);if(r){let n=await za(e,t.merchantTradeNo);if(!n||![`Authorized`,`To be captured`,`Captured`].includes(n.status)||n.amount!==c)return{outcome:`unconfirmed`,extra:{}}}let l=c===t.amount,u={};for(let[e,t]of[[`card4no`,`Card4No`],[`card6no`,`Card6No`],[`auth_code`,`AuthCode`],[`gwsr`,`Gwsr`],[`process_date`,`ProcessDate`]])o[t]!==void 0&&o[t]!==``&&(u[e]=String(o[t]));let d=await M(e,t.merchantTradeNo,{status:l?`paid`:`failed`,tradeNo:s,paymentType:`Credit_CreditCard`,resultCode:`1`,resultMessage:l?String(n.RtnMsg??``):`amount_mismatch:${c}`,info:u}),f={eventId:`ecpay:${s||t.merchantTradeNo}:paid`,provider:`ecpay`,orderNumber:t.orderNumber,amount:c,status:`paid`,tradeNo:s},p=[],m=String(n.BindCardID??``);if(l&&d?.status===`paid`&&m){let r=String(o.CardValidYY??``),i=String(o.CardValidMM??``),a=await na(e,{id:`ec_${t.merchantTradeNo}`,provider:`ecpay`,token:JSON.stringify({bindCardId:m,memberId:String(n.MerchantMemberID??t.memberId),origin:t.origin}),memberId:String(n.MerchantMemberID??t.memberId),email:t.email,orderNumber:t.orderNumber,display:{brand:ka(String(o.Card6No??``)),last4:String(o.Card4No??``).slice(-4),expiry:/^\d{2}$/.test(r)&&/^\d{2}$/.test(i)?`20${r}-${i}`:``}});a&&p.push(a)}return{outcome:l?`paid`:`amount_mismatch`,extra:{payment:f,...p.length?{reports:p}:{}}}}async function La(e,t){let n=new URLSearchParams(t.query),r=await j(e).trades.get(n.get(`a`)??``),i={ok:!0,response:{kind:`redirect`,status:303,location:`/shop`}};if(!r||r.method!==Z||!Me(r.resultKey??``,n.get(`k`)??``))return i;let a={ok:!0,response:{kind:`redirect`,status:303,location:`/shop/order/${r.orderNumber}`}},o=await J(e);if(!o)return a;let s=null;try{s=JSON.parse(An(t.body).ResultData??``)}catch{return a}let c=await Oa(o,s),l=c?.OrderInfo??{};if(!c||String(l.MerchantTradeNo??``)!==r.merchantTradeNo)return a;let u=await Ia(e,r,c,!0);return{...a,outcome:u.outcome,...u.extra}}async function Ra(e,t){let n=await xi(e),r=`${n}${t}`;return r.length<=20&&/^[A-Za-z0-9]+$/.test(r)?r:`${n}R${(await Fe(t)).slice(0,19-n.length).toUpperCase()}`}async function za(e,t){let n=await J(e);if(!n)return null;let r=await Da(e,n,`${Q(n).query}/1.0.0/CreditDetail/QueryTrade`,{PlatformID:``,MerchantID:n.merchantId,MerchantTradeNo:t});if(r.transCode!==1||r.unreadable)return null;let i=String(r.data.RtnMsg??``);if(i===`error_nopay`)return{status:``,amount:0,tradeId:``,missing:!0};let a=r.data.RtnValue;return i||!a?null:{status:String(a.Status??``),amount:Number(a.Amount)||0,tradeId:String(a.TradeID??``),missing:!1}}function Ba(e,t){let n=e.data,r=Number(n.RtnCode??0),i=n.OrderInfo??{},a=(e.message||String(n.RtnMsg??``)).slice(0,200);return e.transCode===-1||e.unreadable?{outcome:`uncertain`,code:e.unreadable?`UNREADABLE`:`NO_REPLY`,message:a}:e.ok&&String(i.TradeStatus??``)===`1`?{outcome:`paid`,code:`1`,message:a,tradeNo:String(i.TradeNo??``),amount:Number(i.TradeAmt)||t}:e.ok||r===_a||r===5100012?{outcome:`uncertain`,code:String(r||e.transCode),message:a}:r===5100088?{outcome:`declined`,code:String(r),message:a||`綁定的卡片已不存在`,revoked:!0}:va.has(r)?{outcome:`declined`,code:String(r),message:a,revoked:ya.has(r)}:{outcome:`failed`,code:String(r||e.transCode),message:a}}const Va={provider:`ecpay`,gatewayRef:(e,t)=>Ra(e,t.chargeRef),charge:async(e,{mandate:t,token:n,report:r,gatewayRef:i})=>{let a=await J(e);if(!a)return{outcome:`failed`,code:`NOT_CONFIGURED`,message:`綠界金流未設定`};let o=JSON.parse(n),s=await Qt(e)??o.origin??``;return Ba(await Da(e,a,`${Q(a).api}/Merchant/CreatePaymentWithCardID`,{PlatformID:``,MerchantID:a.merchantId,BindCardID:o.bindCardId,OrderInfo:{MerchantTradeDate:er(),MerchantTradeNo:i,TotalAmount:r.amount,ReturnURL:`${s}${ha}`,TradeDesc:Ta([r.itemName]),ItemName:Ta([r.itemName])},ConsumerInfo:wa(o.memberId,{email:t.email}),Need3D:0}),r.amount)},lookup:async(e,t)=>{let n=await za(e,t.gatewayRef);return n?n.missing?Date.now()-Date.parse(t.startedAt)>18e5?{outcome:`failed`,code:`NO_TRADE`,message:`綠界查無此筆交易(未扣款)`}:null:[`Authorized`,`To be captured`,`Captured`].includes(n.status)?n.amount===t.amount?{outcome:`paid`,code:`1`,message:`查詢確認:${n.status}`,tradeNo:t.tradeNo||n.tradeId,amount:n.amount}:{outcome:`uncertain`,code:`AMOUNT`,message:`綠界金額 ${n.amount} 與應扣 ${t.amount} 不符,請人工確認`}:[`Unauthorized`,`Canceled`,`Cancelled`,`Operation canceled`].includes(n.status)?{outcome:`failed`,code:n.status,message:`查詢結果:${n.status}`}:null:null},revoke:async(e,t,n)=>{let r=await J(e);if(!r)return{ok:!1,message:`NOT_CONFIGURED`};let i=JSON.parse(n),a=await Da(e,r,`${Q(r).api}/Merchant/DeleteMemberBindCard`,{PlatformID:``,MerchantID:r.merchantId,BindCardID:i.bindCardId});return{ok:a.ok,message:a.message}},onPaid:async(e,t)=>{let n=xa(),r={merchantTradeNo:t.gatewayRef,orderNumber:t.orderNumber,amount:t.amount,method:Z,status:`paid`,createdAt:t.startedAt,updatedAt:n,paidAt:n,tradeNo:t.tradeNo,paymentType:`Credit_CreditCard`,resultCode:`1`,resultMessage:`定期定額自動扣款`};await j(e).trades.compareAndSet(t.gatewayRef,null,r)}};async function Ha(e,t){let n=await J(e),r=(e,t)=>({ok:!0,response:{kind:`text`,status:e,body:t}});if(!n)return r(503,`0|Not configured`);let i=null;try{i=JSON.parse(t)}catch{return r(400,`0|Error`)}let a=await Oa(n,i);if(!a)return r(400,`0|Error`);let o=String((a.OrderInfo??{}).MerchantTradeNo??``),s=await j(e).trades.get(o);if(s?.method===Z&&s.stepToken){let t=await Ia(e,s,a,!0);return{...r(200,`1|OK`),outcome:t.outcome,...t.extra}}let c=await xi(e),l=o.startsWith(c)?await X(e).charges.get(o.slice(c.length)):null;return l&&(l.state===`uncertain`||l.state===`calling`)&&await ua(e,Va,{mandateId:l.mandateId}),r(200,`1|OK`)}const Ua=`logistics-reconcile`;async function Wa(e){return await e.settings.get(`methodCreditBind`)===!0}const Ga=p({shipmentIds:f(i({min:1,max:64}),{min:1,max:50}),mode:h(u([`A4`,`A6`]))}),Ka=i({min:1,max:18,pattern:/^[A-Z0-9]+$/}),qa=i({min:1,max:20,pattern:/^[A-Za-z0-9]+$/}),Ja=p({amount:h(o({min:0})),recurring:h(l())}),Ya=p({orderNumber:Ka,amount:o({min:1,max:99999999}),itemNames:f(i({min:1,max:200}),{min:1,max:100}),method:g(u(fi),`Credit`),origin:i({max:200,pattern:st}),orderUrl:i({min:8,max:400,pattern:ct}),email:h(i({max:120})),customer:h(p({name:g(i({max:60}),``),phone:g(i({max:30}),``)}))}),Xa=p({attemptNo:i({min:1,max:20,pattern:/^[A-Za-z0-9]+$/}),token:i({min:16,max:64,pattern:/^[a-f0-9]+$/})}),Za=p({orderNumber:h(Ka),state:h(u([`calling`,`paid`,`failed`,`declined`,`uncertain`])),limit:g(o({min:1,max:100,coerce:!0}),20)}),Qa=p({id:i({min:1,max:200}),type:i({min:1,max:60}),payload:m(c(),{maxKeys:50})}),$a=p({orderNumber:Ka,amount:o({min:1,max:99999999})}),eo=p({orderNumber:h(Ka),limit:g(o({min:1,max:100,coerce:!0}),20)}),to=p({merchantTradeNo:qa}),no=p({merchantTradeNo:qa,amount:o({min:1,max:99999999,coerce:!0})}),ro={created:`待付款`,awaiting:`待繳費`,paid:`已付款`,failed:`未成功`};async function io(e){let{trades:t}=j(e),n=await J(e),[r,i,a]=await Promise.all([t.query({orderBy:{createdAt:`desc`},limit:30}),t.count({status:`paid`}),_i(e)]);return{blocks:[{type:`header`,text:`綠界金流`},{type:`stats`,items:[{label:`環境`,value:n?n.environment===`production`?`正式`:`測試`:`未設定`},{label:`特店編號`,value:n?.merchantId??`—`},{label:`開放的付款方式`,value:a.map(e=>q[e]).join(`、`)||`無`},{label:`已付款交易`,value:String(i)}]},{type:`table`,columns:[{key:`merchantTradeNo`,label:`綠界訂單編號`},{key:`orderNumber`,label:`訂單`},{key:`method`,label:`付款方式`},{key:`amount`,label:`金額`},{key:`status`,label:`狀態`},{key:`tradeNo`,label:`綠界交易序號`}],rows:r.items.map(({data:e})=>({merchantTradeNo:e.merchantTradeNo,orderNumber:e.orderNumber,method:[q[e.method]??e.method??``,e.paymentType?mi(e.paymentType):``,e.info?.card4no?`末四碼 ${e.info.card4no}`:``].filter(Boolean).join(`/`),amount:`NT$${e.amount}${e.refunded?`(已退 NT$${e.refunded})`:``}`,status:e.status===`paid`&&e.simulated?`已付款(模擬)`:ro[e.status]??e.status,tradeNo:e.tradeNo??``})),page_action_id:`browse_trades`,empty_text:`尚無交易。`}]}}async function ao(e,t,n){let r=Un(n);if(r){let t=await Rn(e,`綠界金流`,yi,r.actionId,r.values)??await Hn(e,kr,Tr,r.actionId,r.values);if(t)return t}if(t===`/payment-labels`)return Ln(`綠界金流`,yi,await Pn(e,fi));if(t===`/shipping-methods`)return Bn(kr,Tr,await z(e,kr));if(t===`/shipments`){let t=await W(e,`C2C`);return gn(e,`綠界物流`,[{label:`物流`,value:await Sr(e)?`已啟用`:`未啟用`},{label:`環境`,value:t.stage?`測試`:`正式`},{label:`合約`,value:await e.settings.get(`logisticsContract`)===`B2C`?`B2C 大宗寄倉`:`C2C 店到店`}])}if(t===`/invoices`){let t=await ri(e);return _n(e,`綠界電子發票`,[{label:`電子發票`,value:await ii(e)?`已啟用`:`未啟用`},{label:`環境`,value:t.stage?`測試`:`正式`},{label:`特店編號`,value:t.merchantId}])}return io(e)}async function oo(e,t,n){let r=await et(e,t);return r?nt(r.merchantTradeNo,await ji(e,r,n),()=>tt(e,r.merchantTradeNo,n)):{ok:!1,error:`NOT_PAID`,detail:`這筆訂單沒有已付款的綠界交易。`}}const so={hooks:{"plugin:activate":async(e,t)=>{await t.cron?.schedule(Ua,{schedule:`*/30 * * * *`})},cron:async(e,t)=>{if(e.name!==Ua)return;await Wa(t)&&await ua(t,Va).catch(e=>t.log.warn(`ECPay recurring look-up failed`,{error:String(e)}));let n=await Yt(t,di);if(n.length&&t.log.info(`issued delayed ECPay invoices`,{n:n.length}),!await Sr(t))return;let r=await It(t,Zr);r&&t.log.info(`synced ECPay shipments`,{n:r})}},routes:{...yn([`trades`,`orders`,`shipments`,`invoices`]),"bus/methods":k(`bus/methods`,Ja,async(e,t)=>{if(!await J(t))return{ok:!0,provider:`ecpay`,methods:[]};let n=await _i(t,e.amount),r=await Pn(t,fi),i=n.map(e=>Fn(r,e,q[e]));return e.recurring&&await Wa(t)&&(e.amount==null||e.amount<=2e5)&&i.unshift({...Fn(r,Z,q.CreditBind),recurring:!0}),{ok:!0,provider:`ecpay`,label:`綠界金流`,methods:i}}),"bus/checkout":k(`bus/checkout`,Ya,async(e,t)=>{let n=await J(t);if(!n)return{ok:!1,error:`NOT_CONFIGURED`};if(e.method===Z)return await Wa(t)?(await $t(t,e.origin),ja(t,e)):{ok:!1,error:`METHOD_DISABLED`};if(!(await _i(t,e.amount)).includes(e.method))return{ok:!1,error:`METHOD_DISABLED`};let r=await xi(t);if(r.length+e.orderNumber.length+2>20)return{ok:!1,error:`TRADE_NO_TOO_LONG`,detail:`訂單編號加上前綴超過綠界的 20 字上限,請縮短「訂單編號前綴」。`};let i;try{i=await $e(t,e.orderNumber,e.amount,e.method,r)}catch(e){return{ok:!1,error:e.message}}return{ok:!0,...await Si(t,n,{merchantTradeNo:i.merchantTradeNo,amount:e.amount,itemNames:e.itemNames,method:e.method,origin:e.origin,orderUrl:e.orderUrl}),merchantTradeNo:i.merchantTradeNo}}),"bus/bind/step":k(`bus/bind/step`,Xa,async(e,t)=>Na(t,e)),[x.gateway(`bind-pay`)]:k(x.gateway(`bind-pay`),b,async(e,t)=>Fa(t,e.body)),[x.gateway(`bind-result`)]:k(x.gateway(`bind-result`),b,async(e,t)=>La(t,e)),[x.gateway(`bind-notify`)]:k(x.gateway(`bind-notify`),b,async(e,t)=>Ha(t,e.body)),...fa(Va),"bus/notify":Ue(`bus/notify`,async(e,t)=>Ti(t,e)),[x.gateway(`return`)]:k(x.gateway(`return`),b,async(e,t)=>Qe(await Ti(t,e.body))),"bus/events":k(`bus/events`,Qa,async(e,t)=>rt(t,e,(e,t)=>Xt(e,t,{logistics:Zr,invoice:di}),{provider:`ecpay`,refund:oo})),...Xe(),"bus/shipping/methods":k(`bus/shipping/methods`,en,async(e,t)=>({ok:!0,provider:`moksa-ecpay`,methods:await Ar(t,e.temperatures)})),...pn(di,`綠界電子發票`,ii),[x.gateway(`logistics-map`)]:k(x.gateway(`logistics-map`),b,async(e,t)=>(await $t(t,e.origin),Nr(t,e))),[x.gateway(`logistics-map-reply`)]:k(x.gateway(`logistics-map-reply`),b,async(e,t)=>Pr(t,e)),[x.gateway(`logistics-notify`)]:k(x.gateway(`logistics-notify`),b,async(e,t)=>(await $t(t,e.origin),Qr(t,e))),"bus/refund":k(`bus/refund`,$a,async(e,t)=>oo(t,e.orderNumber,e.amount)),"recurring/list":N(Za,async(e,t)=>({ok:!0,charges:await pa(t,e),cards:await ma(t,{orderNumber:e.orderNumber,limit:e.limit})})),"trades/list":N(eo,async(e,t)=>({ok:!0,trades:(await j(t).trades.query({where:e.orderNumber?{orderNumber:e.orderNumber}:void 0,orderBy:{createdAt:`desc`},limit:e.limit})).items.map(e=>e.data)})),"trades/query":N(to,async(e,t)=>{let n=await J(t);return n?t.http?Di(t,n,e.merchantTradeNo):{ok:!1,error:`NO_NETWORK`}:{ok:!1,error:`NOT_CONFIGURED`}}),"trades/refund":N(no,async(e,t)=>{let n=await j(t).trades.get(e.merchantTradeNo);if(!n||n.status!==`paid`)return{ok:!1,error:`NOT_PAID`};let r=await oo(t,n.orderNumber,e.amount);if(!r.ok)return r;let i=await it(t,{provider:`ecpay`,refundId:`${n.merchantTradeNo}:${(n.refunded??0)+e.amount}`,orderNumber:n.orderNumber,amount:e.amount,message:r.message});return{...r,reports:[i]}},[`POST`]),"trades/capture":N(to,async(e,t)=>{let n=await j(t).trades.get(e.merchantTradeNo);if(!n)return{ok:!1,error:`NOT_FOUND`};let r=await Mi(t,n);return r.ok?{ok:!0,message:r.message}:{ok:!1,error:`CAPTURE_FAILED`,detail:r.message}},[`POST`]),"trades/card-status":N(to,async(e,t)=>{let n=await j(t).trades.get(e.merchantTradeNo);if(!n)return{ok:!1,error:`NOT_FOUND`};let r=await Fi(t,n);return r.ok?r:{ok:!1,error:r.error,detail:r.message}}),...un(Zr),"shipments/print-batch":N(Ga,async(e,t)=>{let n=await Promise.all([...new Set(e.shipmentIds)].map(async e=>[e,await I(t).shipments.get(e)])),r=n.filter(([,e])=>!e).map(([e])=>`${e}:找不到物流單`),i=await Yr(t,n.flatMap(([,e])=>e?[e]:[]),e.mode),a=[...r,...i.errors];return i.labels.length?{ok:!0,labels:i.labels,errors:a}:{ok:!1,error:`NOTHING_PRINTED`,detail:a.join(` / `)}}),...dn(di),admin:{permission:`plugins:manage`,handler:async(e,t)=>ao(t,String(e.input?.page??``),e.input)}}};export{so as default};
@@ -7,7 +7,9 @@
7
7
  "license": "MIT",
8
8
  "author": { "name": "Moksa", "url": "https://moksaweb.com" },
9
9
  "security": { "email": "services@moksaweb.com" },
10
- "description": "綠界:信用卡、ATM、超商代碼與條碼付款及退款,超商取貨與宅配,以及 B2C 電子發票,需搭配 @moksa/emdash-shop 網站整合套件。",
10
+ "name": "ECPay 綠界 Payments, Shipping & E-Invoice",
11
+ "description": "ECPay for Taiwan: cards, ATM, CVS payments, convenience-store pickup and home delivery, B2C e-invoices. 綠界金流、物流、發票。",
12
+ "keywords": ["payments","taiwan","shipping","invoice","ecpay"],
11
13
 
12
14
  // Only ECPay's payment, logistics and e-invoice hosts; credentials stay in this plugin's encrypted settings.
13
15
  "capabilities": ["network:request"],
@@ -16,6 +18,9 @@
16
18
  "payment.ecpay.com.tw",
17
19
  "ecpayment-stage.ecpay.com.tw",
18
20
  "ecpayment.ecpay.com.tw",
21
+ // 站內付 2.0 綁卡 (§47): GetTokenbyBindingCard, CreateBindCard, CreatePaymentWithCardID, DeleteMemberBindCard.
22
+ "ecpg-stage.ecpay.com.tw",
23
+ "ecpg.ecpay.com.tw",
19
24
  "logistics-stage.ecpay.com.tw",
20
25
  "logistics.ecpay.com.tw",
21
26
  "einvoice-stage.ecpay.com.tw",
@@ -29,7 +34,13 @@
29
34
  "orders": { "indexes": ["updatedAt", "invoiceDueAt"] },
30
35
  "shipments": { "indexes": ["orderNumber", "status", "updatedAt", ["open", "updatedAt"], ["orderNumber", "updatedAt"]] },
31
36
  "invoices": { "indexes": ["status", "updatedAt"] },
32
- "reports": { "indexes": ["status", "nextAt", ["status", "nextAt"]] }
37
+ "reports": { "indexes": ["status", "nextAt", ["status", "nextAt"]] },
38
+ // 站內付 2.0 綁卡 (§47, @moksa/sandbox-kit/recurring): saved cards (BindCardID sealed with vaultKey),
39
+ // the renewal charge ledger (one row per charge reference, made before ECPay is called) and the
40
+ // random MerchantMemberID per shopper.
41
+ "mandates": { "indexes": ["status", "cardRef", "orderNumber", "updatedAt"] },
42
+ "charges": { "indexes": ["state", "mandateId", "orderNumber", "updatedAt", ["state", "updatedAt"]] },
43
+ "members": { "indexes": ["createdAt"] }
33
44
  },
34
45
 
35
46
  "admin": {
@@ -79,6 +90,7 @@
79
90
  "methodJkopay": { "label": "街口支付(需申請)", "type": "boolean", "default": false },
80
91
  "methodiPASS": { "label": "一卡通 iPASS MONEY(需申請)", "type": "boolean", "default": false },
81
92
  "methodALL": { "label": "綠界付款頁自選", "type": "boolean", "default": false },
93
+ "methodCreditBind": { "label": "信用卡綁定:定期定額自動扣款(站內付 2.0,需向綠界申請綁卡)", "type": "boolean", "default": false, "description": "只在訂閱商品結帳時出現。首期刷卡同時綁定卡片,之後每期由網站自動扣款;請在綠界廠商後台開啟每日自動關帳。測試環境用綠界公開測試帳號。" },
82
94
  "atmExpireDays": { "label": "ATM 繳費期限(天)", "type": "number", "default": 3, "min": 1, "max": 60 },
83
95
  "cvsExpireDays": { "label": "超商代碼繳費期限(天,最多 30)", "type": "number", "default": 7, "min": 1, "max": 30 },
84
96
  "barcodeExpireDays": { "label": "超商條碼繳費期限(天,最多 30)", "type": "number", "default": 7, "min": 1, "max": 30 },
@@ -184,6 +196,7 @@
184
196
  "default": "member"
185
197
  },
186
198
  "invoiceDonateOrgs": { "label": "捐贈單位(每行「名稱|愛心碼」)", "type": "string", "default": "", "description": "例:伊甸社會福利基金會|25885。填寫後結帳改為下拉選單。" },
199
+ "vaultKey": { "label": "卡片憑證加密金鑰", "type": "secret", "description": "第一次綁卡時自動產生,用來加密綠界綁卡代碼(BindCardID)。請勿修改或刪除,否則已綁定的卡片無法再扣款。" },
187
200
  "busKey": {
188
201
  "label": "匯流排金鑰",
189
202
  "type": "secret",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moksa/plugin-ecpay",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "description": "綠界:信用卡、ATM、超商代碼與條碼付款及退款,超商取貨與宅配,以及 B2C 電子發票,需搭配 @moksa/emdash-shop 網站整合套件。",
6
6
  "license": "MIT",