@moksa/plugin-ecpay 0.0.0-stage → 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.d.mts +5 -0
- package/dist/index.mjs +1587 -0
- package/dist/manifest.json +1597 -0
- package/dist/moksa-ecpay-0.1.0.tar.gz +0 -0
- package/dist/moksa-ecpay-0.1.1.tar.gz +0 -0
- package/dist/plugin.d.mts +6 -0
- package/dist/plugin.mjs +1 -0
- package/emdash-plugin.jsonc +207 -0
- package/package.json +43 -5
- package/README.md +0 -3
|
Binary file
|
|
Binary file
|
package/dist/plugin.mjs
ADDED
|
@@ -0,0 +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]+$/}),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};
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../node_modules/@emdash-cms/plugin-cli/schemas/emdash-plugin.schema.json",
|
|
3
|
+
|
|
4
|
+
"slug": "moksa-ecpay",
|
|
5
|
+
"publisher": "did:plc:6kgbzcykx44dbvjw3puns3w2",
|
|
6
|
+
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"author": { "name": "Moksa", "url": "https://moksaweb.com" },
|
|
9
|
+
"security": { "email": "services@moksaweb.com" },
|
|
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"],
|
|
13
|
+
|
|
14
|
+
// Only ECPay's payment, logistics and e-invoice hosts; credentials stay in this plugin's encrypted settings.
|
|
15
|
+
"capabilities": ["network:request"],
|
|
16
|
+
"allowedHosts": [
|
|
17
|
+
"payment-stage.ecpay.com.tw",
|
|
18
|
+
"payment.ecpay.com.tw",
|
|
19
|
+
"ecpayment-stage.ecpay.com.tw",
|
|
20
|
+
"ecpayment.ecpay.com.tw",
|
|
21
|
+
// 站內付 2.0 綁卡 (§47): GetTokenbyBindingCard, CreateBindCard, CreatePaymentWithCardID, DeleteMemberBindCard.
|
|
22
|
+
"ecpg-stage.ecpay.com.tw",
|
|
23
|
+
"ecpg.ecpay.com.tw",
|
|
24
|
+
"logistics-stage.ecpay.com.tw",
|
|
25
|
+
"logistics.ecpay.com.tw",
|
|
26
|
+
"einvoice-stage.ecpay.com.tw",
|
|
27
|
+
"einvoice.ecpay.com.tw"
|
|
28
|
+
],
|
|
29
|
+
|
|
30
|
+
"storage": {
|
|
31
|
+
"trades": { "indexes": ["orderNumber", "status", "createdAt", ["orderNumber", "createdAt"]] },
|
|
32
|
+
"events": { "indexes": ["createdAt"] },
|
|
33
|
+
// Logistics / e-invoice (@moksa/sandbox-kit/fulfillment): snapshots of orders this plugin ships or invoices.
|
|
34
|
+
"orders": { "indexes": ["updatedAt", "invoiceDueAt"] },
|
|
35
|
+
"shipments": { "indexes": ["orderNumber", "status", "updatedAt", ["open", "updatedAt"], ["orderNumber", "updatedAt"]] },
|
|
36
|
+
"invoices": { "indexes": ["status", "updatedAt"] },
|
|
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"] }
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
"admin": {
|
|
47
|
+
"pages": [
|
|
48
|
+
{ "path": "/trades", "label": "綠界金流", "icon": "credit-card" },
|
|
49
|
+
{ "path": "/payment-labels", "label": "付款方式名稱與說明", "icon": "list" },
|
|
50
|
+
{ "path": "/shipments", "label": "綠界物流", "icon": "truck" },
|
|
51
|
+
{ "path": "/shipping-methods", "label": "物流方式設定", "icon": "list" },
|
|
52
|
+
{ "path": "/invoices", "label": "綠界電子發票", "icon": "receipt" }
|
|
53
|
+
],
|
|
54
|
+
"settingsSchema": {
|
|
55
|
+
"environment": {
|
|
56
|
+
"label": "環境",
|
|
57
|
+
"type": "select",
|
|
58
|
+
"options": [
|
|
59
|
+
{ "value": "stage", "label": "測試環境(stage)" },
|
|
60
|
+
{ "value": "production", "label": "正式環境" }
|
|
61
|
+
],
|
|
62
|
+
"default": "stage"
|
|
63
|
+
},
|
|
64
|
+
"merchantId": {
|
|
65
|
+
"label": "特店編號(MerchantID)",
|
|
66
|
+
"type": "string",
|
|
67
|
+
"default": "",
|
|
68
|
+
"description": "測試環境留空時使用綠界公開測試帳號 3002607。"
|
|
69
|
+
},
|
|
70
|
+
"hashKey": { "label": "HashKey", "type": "secret" },
|
|
71
|
+
"hashIv": { "label": "HashIV", "type": "secret" },
|
|
72
|
+
"methodCredit": { "label": "信用卡", "type": "boolean", "default": true },
|
|
73
|
+
"methodCredit3": { "label": "信用卡分 3 期(需向綠界申請)", "type": "boolean", "default": false },
|
|
74
|
+
"methodCredit6": { "label": "信用卡分 6 期(需向綠界申請)", "type": "boolean", "default": false },
|
|
75
|
+
"methodCredit12": { "label": "信用卡分 12 期(需向綠界申請)", "type": "boolean", "default": false },
|
|
76
|
+
"methodCredit18": { "label": "信用卡分 18 期(需向綠界申請)", "type": "boolean", "default": false },
|
|
77
|
+
"methodCredit24": { "label": "信用卡分 24 期(需向綠界申請)", "type": "boolean", "default": false },
|
|
78
|
+
"methodCredit30N": { "label": "信用卡分 30 期(永豐,需向綠界申請)", "type": "boolean", "default": false },
|
|
79
|
+
"credit30NMinAmount": { "label": "永豐 30 期最低交易金額", "type": "number", "default": 0, "min": 0, "description": "綠界廠商後台可查;訂單金額大於這個金額才提供 30 期。0 表示不提供。" },
|
|
80
|
+
"methodUnionPay": { "label": "銀聯卡(需向綠界申請)", "type": "boolean", "default": false },
|
|
81
|
+
"creditRedeem": { "label": "信用卡紅利折抵(需向綠界申請)", "type": "boolean", "default": false, "description": "一次付清的信用卡付款頁提供紅利折抵;分期與銀聯卡不適用。" },
|
|
82
|
+
"methodATM": { "label": "ATM 轉帳", "type": "boolean", "default": false },
|
|
83
|
+
"methodCVS": { "label": "超商代碼繳費", "type": "boolean", "default": false },
|
|
84
|
+
"methodBARCODE": { "label": "超商條碼繳費", "type": "boolean", "default": false },
|
|
85
|
+
"methodWebATM": { "label": "網路 ATM(手機版不支援)", "type": "boolean", "default": false },
|
|
86
|
+
"methodApplePay": { "label": "Apple Pay", "type": "boolean", "default": false },
|
|
87
|
+
"methodTWQR": { "label": "TWQR 行動支付(需申請,6–49,999 元)", "type": "boolean", "default": false },
|
|
88
|
+
"methodBNPL": { "label": "無卡分期:裕富/中租(需申請,50–500,000 元)", "type": "boolean", "default": false },
|
|
89
|
+
"methodWeiXin": { "label": "微信支付(需申請,6–500,000 元)", "type": "boolean", "default": false },
|
|
90
|
+
"methodJkopay": { "label": "街口支付(需申請)", "type": "boolean", "default": false },
|
|
91
|
+
"methodiPASS": { "label": "一卡通 iPASS MONEY(需申請)", "type": "boolean", "default": false },
|
|
92
|
+
"methodALL": { "label": "綠界付款頁自選", "type": "boolean", "default": false },
|
|
93
|
+
"methodCreditBind": { "label": "信用卡綁定:定期定額自動扣款(站內付 2.0,需向綠界申請綁卡)", "type": "boolean", "default": false, "description": "只在訂閱商品結帳時出現。首期刷卡同時綁定卡片,之後每期由網站自動扣款;請在綠界廠商後台開啟每日自動關帳。測試環境用綠界公開測試帳號。" },
|
|
94
|
+
"atmExpireDays": { "label": "ATM 繳費期限(天)", "type": "number", "default": 3, "min": 1, "max": 60 },
|
|
95
|
+
"cvsExpireDays": { "label": "超商代碼繳費期限(天,最多 30)", "type": "number", "default": 7, "min": 1, "max": 30 },
|
|
96
|
+
"barcodeExpireDays": { "label": "超商條碼繳費期限(天,最多 30)", "type": "number", "default": 7, "min": 1, "max": 30 },
|
|
97
|
+
"orderPrefix": { "label": "訂單編號前綴(最多 5 個英數字)", "type": "string", "default": "", "description": "多個商店共用同一個綠界帳號時,用前綴區分訂單編號。" },
|
|
98
|
+
"itemName": { "label": "付款頁顯示的商品名稱", "type": "string", "default": "", "description": "留空時列出訂單的商品;填寫固定文字(例如「網路商品」)時每筆訂單都顯示這段文字。" },
|
|
99
|
+
"siteUrl": { "label": "網站網址", "type": "string", "default": "", "description": "例:https://shop.example.com。物流狀態回呼使用;留空時沿用網站最近一次轉來的網址。" },
|
|
100
|
+
|
|
101
|
+
"logisticsEnabled": { "label": "啟用綠界物流", "type": "boolean", "default": false },
|
|
102
|
+
"logisticsEnvironment": {
|
|
103
|
+
"label": "物流環境",
|
|
104
|
+
"type": "select",
|
|
105
|
+
"options": [
|
|
106
|
+
{ "value": "stage", "label": "測試環境(stage)" },
|
|
107
|
+
{ "value": "production", "label": "正式環境" }
|
|
108
|
+
],
|
|
109
|
+
"default": "stage"
|
|
110
|
+
},
|
|
111
|
+
"logisticsContract": {
|
|
112
|
+
"label": "超商合約類型",
|
|
113
|
+
"type": "select",
|
|
114
|
+
"options": [
|
|
115
|
+
{ "value": "C2C", "label": "C2C 店到店" },
|
|
116
|
+
{ "value": "B2C", "label": "B2C 大宗寄倉" }
|
|
117
|
+
],
|
|
118
|
+
"default": "C2C"
|
|
119
|
+
},
|
|
120
|
+
"logisticsC2cMerchantId": { "label": "C2C 物流特店編號", "type": "string", "default": "", "description": "店到店(交貨便)用。測試環境未填時使用綠界公開測試帳號 2000933。" },
|
|
121
|
+
"logisticsC2cHashKey": { "label": "C2C 物流 HashKey", "type": "secret" },
|
|
122
|
+
"logisticsC2cHashIv": { "label": "C2C 物流 HashIV", "type": "secret" },
|
|
123
|
+
"logisticsB2cMerchantId": { "label": "B2C/宅配物流特店編號", "type": "string", "default": "", "description": "大宗寄倉、黑貓、郵局用。測試環境未填時使用綠界公開測試帳號 2000132。" },
|
|
124
|
+
"logisticsB2cHashKey": { "label": "B2C/宅配物流 HashKey", "type": "secret" },
|
|
125
|
+
"logisticsB2cHashIv": { "label": "B2C/宅配物流 HashIV", "type": "secret" },
|
|
126
|
+
"cvsUnimart": { "label": "7-ELEVEN 取貨(B2C 可含冷凍,另拆一張冷凍單)", "type": "boolean", "default": true },
|
|
127
|
+
"cvsFami": { "label": "全家取貨", "type": "boolean", "default": true },
|
|
128
|
+
"cvsHilife": { "label": "萊爾富取貨", "type": "boolean", "default": false },
|
|
129
|
+
"cvsOkmart": { "label": "OK 取貨", "type": "boolean", "default": false },
|
|
130
|
+
"cvsUnimartFreeze": { "label": "7-ELEVEN 冷凍取貨(僅 B2C)", "type": "boolean", "default": false },
|
|
131
|
+
"homeTcat": { "label": "黑貓宅急便(常溫/冷藏/冷凍,依溫層拆單)", "type": "boolean", "default": true },
|
|
132
|
+
"homePost": { "label": "中華郵政(僅常溫)", "type": "boolean", "default": false },
|
|
133
|
+
"cvsFee": { "label": "超商取貨運費", "type": "number", "default": 60, "min": 0 },
|
|
134
|
+
"homeFee": { "label": "宅配運費(常溫)", "type": "number", "default": 100, "min": 0 },
|
|
135
|
+
"coldFee": { "label": "低溫配送運費", "type": "number", "default": 160, "min": 0 },
|
|
136
|
+
"senderName": { "label": "寄件人姓名(最多 10 字)", "type": "string", "default": "" },
|
|
137
|
+
"senderPhone": { "label": "寄件人電話", "type": "string", "default": "" },
|
|
138
|
+
"senderCellphone": { "label": "寄件人手機", "type": "string", "default": "" },
|
|
139
|
+
"senderZipcode": { "label": "寄件人郵遞區號(宅配用)", "type": "string", "default": "", "description": "3、5 或 6 碼數字。留空時只採用寄件地址開頭的郵遞區號。" },
|
|
140
|
+
"senderAddress": { "label": "寄件地址(宅配用)", "type": "string", "default": "", "description": "沒填郵遞區號時,地址開頭的 3–6 位數字當作郵遞區號。" },
|
|
141
|
+
"labelMode": {
|
|
142
|
+
"label": "託運單格式",
|
|
143
|
+
"type": "select",
|
|
144
|
+
"options": [
|
|
145
|
+
{ "value": "1", "label": "A4" },
|
|
146
|
+
{ "value": "2", "label": "A6" }
|
|
147
|
+
],
|
|
148
|
+
"default": "1",
|
|
149
|
+
"description": "A6 只用於 7-ELEVEN(含冷凍)與中華郵政;全家、萊爾富、OK、黑貓沒有 A6,一律印 A4。"
|
|
150
|
+
},
|
|
151
|
+
"logisticsAutoShip": { "label": "付款後(貨到付款為下單後)自動建立物流單", "type": "boolean", "default": false },
|
|
152
|
+
|
|
153
|
+
"invoiceEnabled": { "label": "啟用綠界電子發票", "type": "boolean", "default": false },
|
|
154
|
+
"invoiceEnvironment": {
|
|
155
|
+
"label": "發票環境",
|
|
156
|
+
"type": "select",
|
|
157
|
+
"options": [
|
|
158
|
+
{ "value": "stage", "label": "測試環境(stage)" },
|
|
159
|
+
{ "value": "production", "label": "正式環境" }
|
|
160
|
+
],
|
|
161
|
+
"default": "stage"
|
|
162
|
+
},
|
|
163
|
+
"invoiceMerchantId": { "label": "發票特店編號", "type": "string", "default": "", "description": "未填時使用綠界公開測試帳號 2000132(測試環境開立的發票不會上傳財政部)。" },
|
|
164
|
+
"invoiceHashKey": { "label": "發票 HashKey", "type": "secret" },
|
|
165
|
+
"invoiceHashIv": { "label": "發票 HashIV", "type": "secret" },
|
|
166
|
+
"invoicePrefix": { "label": "發票關聯編號前綴(最多 5 個英數字)", "type": "string", "default": "", "description": "多個商店共用同一個綠界發票帳號時,用前綴區分。" },
|
|
167
|
+
"invoiceAuto": { "label": "自動開立", "type": "boolean", "default": true },
|
|
168
|
+
"invoiceIssueWhen": {
|
|
169
|
+
"label": "開立時機",
|
|
170
|
+
"type": "select",
|
|
171
|
+
"options": [
|
|
172
|
+
{ "value": "paid", "label": "付款完成(貨到付款於下單後)" },
|
|
173
|
+
{ "value": "completed", "label": "訂單完成(取貨完成)" }
|
|
174
|
+
],
|
|
175
|
+
"default": "paid"
|
|
176
|
+
},
|
|
177
|
+
"invoiceDelayDays": { "label": "延後開立天數(0–30)", "type": "number", "default": 0, "min": 0, "max": 30 },
|
|
178
|
+
"invoiceAutoVoid": { "label": "訂單取消或全額退款時自動作廢", "type": "boolean", "default": false },
|
|
179
|
+
"invoiceAutoAllowance": { "label": "部分退款時自動開立折讓", "type": "boolean", "default": false },
|
|
180
|
+
"invoiceVerifyOnline": { "label": "結帳時向綠界驗證捐贈碼與手機條碼", "type": "boolean", "default": true },
|
|
181
|
+
"invoiceAllowDonate": { "label": "結帳可選捐贈發票", "type": "boolean", "default": true },
|
|
182
|
+
"invoiceAllowB2b": { "label": "結帳可打統編(三聯式)", "type": "boolean", "default": true },
|
|
183
|
+
"invoiceChannelMember": { "label": "個人載具:綠界會員載具", "type": "boolean", "default": true },
|
|
184
|
+
"invoiceChannelMobile": { "label": "個人載具:手機條碼", "type": "boolean", "default": true },
|
|
185
|
+
"invoiceChannelCert": { "label": "個人載具:自然人憑證", "type": "boolean", "default": true },
|
|
186
|
+
"invoiceChannelPaper": { "label": "個人載具:紙本發票", "type": "boolean", "default": true, "description": "至少保留一種;全部取消時自動保留紙本。紙本發票請到綠界後台列印寄送。" },
|
|
187
|
+
"invoiceDefaultCarrier": {
|
|
188
|
+
"label": "預設的個人載具",
|
|
189
|
+
"type": "select",
|
|
190
|
+
"options": [
|
|
191
|
+
{ "value": "member", "label": "綠界會員載具" },
|
|
192
|
+
{ "value": "mobile", "label": "手機條碼" },
|
|
193
|
+
{ "value": "cert", "label": "自然人憑證" },
|
|
194
|
+
{ "value": "paper", "label": "紙本發票" }
|
|
195
|
+
],
|
|
196
|
+
"default": "member"
|
|
197
|
+
},
|
|
198
|
+
"invoiceDonateOrgs": { "label": "捐贈單位(每行「名稱|愛心碼」)", "type": "string", "default": "", "description": "例:伊甸社會福利基金會|25885。填寫後結帳改為下拉選單。" },
|
|
199
|
+
"vaultKey": { "label": "卡片憑證加密金鑰", "type": "secret", "description": "第一次綁卡時自動產生,用來加密綠界綁卡代碼(BindCardID)。請勿修改或刪除,否則已綁定的卡片無法再扣款。" },
|
|
200
|
+
"busKey": {
|
|
201
|
+
"label": "匯流排金鑰",
|
|
202
|
+
"type": "secret",
|
|
203
|
+
"description": "由網站部署腳本寫入(scripts/provision-bus.mjs),請勿手動修改。"
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,44 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"name": "@moksa/plugin-ecpay",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "綠界:信用卡、ATM、超商代碼與條碼付款及退款,超商取貨與宅配,以及 B2C 電子發票,需搭配 @moksa/emdash-shop 網站整合套件。",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "Moksa",
|
|
9
|
+
"url": "https://moksaweb.com"
|
|
10
|
+
},
|
|
11
|
+
"main": "dist/index.mjs",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./dist/index.mjs",
|
|
15
|
+
"types": "./dist/index.d.mts"
|
|
16
|
+
},
|
|
17
|
+
"./sandbox": "./dist/plugin.mjs"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"emdash-plugin.jsonc"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "emdash-plugin build",
|
|
25
|
+
"bundle": "emdash-plugin bundle",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"test": "emdash-plugin validate && vitest run"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"emdash": ">=1.3.1"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@moksa/sandbox-kit": "*",
|
|
34
|
+
"@emdash-cms/plugin-cli": "0.13.4",
|
|
35
|
+
"@emdash-cms/plugin-test": "0.2.10",
|
|
36
|
+
"emdash": "1.3.1",
|
|
37
|
+
"typescript": "^5.9.0",
|
|
38
|
+
"vitest": "^4.1.0"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://moksaweb.com"
|
|
44
|
+
}
|
package/README.md
DELETED