@moksa/plugin-ecpay 0.0.0-stage → 0.1.0
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 +1410 -0
- package/dist/manifest.json +1418 -0
- package/dist/moksa-ecpay-0.1.0.tar.gz +0 -0
- package/dist/plugin.d.mts +6 -0
- package/dist/plugin.mjs +1 -0
- package/emdash-plugin.jsonc +194 -0
- package/package.json +43 -5
- package/README.md +0 -3
|
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]+$/}),b=i({min:10,max:40}),ee=[`member`,`mobile`,`certificate`,`paper`,`donate`,`company`],te=[`normal`,`refrigerated`,`frozen`],ne=p({name:i({max:60}),phone:i({max:30}),zip:h(i({max:10})),city:h(i({max:60})),line:h(i({max:200}))}),re=p({provider:i({min:1,max:40}),type:i({max:40}),id:i({min:1,max:40}),name:i({max:100}),address:i({max:200})}),ie=p({methodId:i({min:1,max:80}),label:i({max:100}),provider:_(i({max:64})),fee:v,address:h(ne),store:h(re),temperature:g(u(te),`normal`),collect:g(l(),!1),collectAmount:g(v,0)}),ae=p({kind:u(ee),carrierNum:h(i({max:64})),loveCode:h(i({max:10})),taxId:h(i({max:8,pattern:/^\d{8}$/})),companyTitle:h(i({max:100})),address:h(i({max:200})),provider:_(i({max:64}))}),oe=p({sku:i({min:1,max:64}),name:i({max:200}),quantity:o({min:1,max:9999}),unitPrice:v,amount:v,temperature:h(u(te)),weightKg:h(s({min:0,max:1e3}))}),se=p({id:i({min:1,max:120}),source:i({min:1,max:40}),provider:_(i({max:64})),label:i({max:100}),amount:o({min:-99999999,max:99999999}),holdId:_(i({max:80}))}),ce=p({number:y,status:i({max:20}),email:i({max:120}),customer:p({id:_(i({max:64})),name:i({max:60}),phone:i({max:30}),lineUserId:_(i({max:64}))}),lines:f(oe,{max:200}),subtotal:v,discountTotal:v,shippingFee:v,total:v,currency:d(`TWD`),shipping:_(ie),invoice:_(ae),payment:_(p({provider:i({max:40}),tradeNo:i({max:64}),amount:v,simulated:l()})),note:g(i({max:500}),``),createdAt:b,paidAt:_(b),adjustments:h(f(se,{max:30}))}),le=p({provider:i({min:1,max:64}),shipmentId:i({min:1,max:64}),carrier:i({max:40}),method:i({max:80}),trackingNo:_(i({max:64})),trackingUrl:_(i({max:300})),status:u([`created`,`in_progress`,`shipped`,`arrived`,`completed`,`store_closed`,`unclaimed`,`returned`,`failed`,`void`]),statusText:i({max:200}),isCollection:l(),occurredAt:b}),ue=p({provider:i({min:1,max:64}),status:u([`issued`,`failed`,`void`]),invoiceNo:_(i({max:20})),invoiceDate:_(i({max:20})),randomNumber:_(i({max:8})),kind:u(ee),amount:v,allowanceTotal:g(v,0),error:_(i({max:300})),occurredAt:b}),de=p({kind:d(`shipment`),reportId:i({min:1,max:200}),orderNumber:y,shipment:le}),fe=p({kind:d(`invoice`),reportId:i({min:1,max:200}),orderNumber:y,invoice:ue}),pe=p({kind:d(`payment`),reportId:i({min:1,max:200}),orderNumber:y,payment:p({eventId:i({min:1,max:120}),provider:i({min:1,max:40,pattern:/^[a-z0-9-]+$/}),orderNumber:y,amount:o({min:0}),status:u([`paid`,`failed`,`cancelled`]),tradeNo:i({max:64})}),reason:h(i({min:1,max:80}))}),me=p({kind:d(`refund`),reportId:i({min:1,max:200}),orderNumber:y,requestId:h(i({min:1,max:80})),status:u([`refunded`,`manual`,`failed`]),amount:v,provider:i({min:1,max:64}),message:g(i({max:300}),``),origin:h(d(`plugin`)),reason:h(i({max:200}))}),he=p({kind:d(`coupon`),reportId:i({min:1,max:200}),coupon:p({code:i({min:4,max:40,pattern:/^[A-Z0-9_-]+$/}),type:u([`percent`,`fixed_cart`]),amount:o({min:0,max:1e7}),freeShipping:g(l(),!1),description:g(i({max:200}),``),expiresAt:_(b),email:_(i({max:120})),customerId:_(i({max:64})),campaign:g(i({max:40}),``)})}),ge=p({kind:d(`points`),reportId:i({min:1,max:200}),credit:p({customerId:i({min:1,max:64}),email:i({min:3,max:120}),amount:o({min:1,max:99999999}),reason:i({min:1,max:200})})}),_e=p({kind:d(`order-action`),reportId:i({min:1,max:200}),orderNumber:y,action:u([`cancel`,`refund`]),amount:h(o({min:1,max:99999999})),reason:i({min:1,max:200})}),ve=p({kind:d(`order-request`),reportId:i({min:1,max:200}),email:i({min:3,max:120,format:`email`}),customer:p({name:i({min:1,max:60}),phone:i({max:30}),id:_(i({max:64}))}),lines:f(p({slug:i({min:1,max:200}),sku:i({min:1,max:64}),quantity:o({min:1,max:99})}),{min:1,max:20}),pricing:g(m(c(),{maxKeys:20}),{}),note:g(i({max:500}),``)}),ye=p({kind:d(`stock`),reportId:i({min:1,max:200}),sku:i({min:1,max:64}),stock:o({min:0,max:1e6}),reason:g(i({max:200}),``)}),be=p({kind:d(`points-grant`),reportId:i({min:1,max:200}),grant:p({customerId:i({min:1,max:64}),email:i({min:3,max:120}),points:o({min:1,max:1e6}),reason:i({min:1,max:200})})}),xe=p({kind:d(`line-push`),reportId:i({min:1,max:200}),push:p({customerId:h(_(i({max:64}))),userId:h(_(i({max:64,pattern:/^U[0-9a-f]{32}$/i}))),topic:i({min:1,max:40,pattern:/^[a-z0-9_]+$/}),text:i({min:1,max:5e3})})}),Se=`shipment, invoice, payment, refund, coupon, points, order-action, order-request, stock, points-grant or line-push`;function Ce(t){let n=t?.kind;return n===`shipment`?e(de,t):n===`invoice`?e(fe,t):n===`payment`?e(pe,t):n===`refund`?e(me,t):n===`coupon`?e(he,t):n===`points`?e(ge,t):n===`order-action`?e(_e,t):n===`order-request`?e(ve,t):n===`stock`?e(ye,t):n===`points-grant`?e(be,t):n===`line-push`?e(xe,t):{ok:!1,error:`kind: must be ${Se}`,issues:[{path:`kind`,message:`must be ${Se}`}]}}const we=p({method:u([`GET`,`POST`]),query:g(i({max:8192}),``),contentType:g(i({max:200}),``),body:g(i({max:2e5}),``),headers:g(m(i({max:1e3}),{maxKeys:10}),{}),origin:h(i({max:200}))});o({min:-99999999,max:99999999}),o({min:0,max:100}),o({min:1,max:9999}),o({min:60,max:3600});const x={events:`bus/events`,reportsDue:`bus/reports/due`,reportsAck:`bus/reports/ack`,notify:`bus/notify`,gateway:e=>`bus/gw/${e}`,customerSession:`bus/customers/session`},S={create:`shipments/create`,list:`shipments/list`,sync:`shipments/sync`,print:`shipments/print`,void:`shipments/void`,syncAll:`shipments/sync-all`},Te={issue:`invoices/issue`,get:`invoices/get`,void:`invoices/void`,allowance:`invoices/allowance`},Ee=new TextEncoder,De=new TextDecoder;function C(e){return typeof e==`string`?Ee.encode(e):e}function Oe(e){return De.decode(e)}function w(e){let t=``;for(let n of e)t+=n.toString(16).padStart(2,`0`);return t}function ke(e){let t=``;for(let n of e)t+=String.fromCharCode(n);return btoa(t)}function Ae(e){let t=e.replace(/-/g,`+`).replace(/_/g,`/`).replace(/\s+/g,``);if(!/^[A-Za-z0-9+/]*={0,2}$/.test(t))return null;try{let e=atob(t.padEnd(Math.ceil(t.length/4)*4,`=`)),n=new Uint8Array(e.length);for(let t=0;t<e.length;t++)n[t]=e.charCodeAt(t);return n}catch{return null}}function je(e){return ke(e).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=+$/,``)}function Me(...e){let t=new Uint8Array(e.reduce((e,t)=>e+t.length,0)),n=0;for(let r of e)t.set(r,n),n+=r.length;return t}function Ne(e,t){let n=C(e),r=C(t);if(n.length!==r.length)return!1;let i=0;for(let e=0;e<n.length;e++)i|=n[e]^r[e];return i===0}function Pe(e){return crypto.getRandomValues(new Uint8Array(e))}function T(e){return e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength)}function Fe(e){return e}async function Ie(e){return new Uint8Array(await crypto.subtle.digest(`SHA-256`,T(C(e))))}async function Le(e){return w(await Ie(e))}async function Re(e,t){let n=await crypto.subtle.importKey(`raw`,T(C(e)),{name:`HMAC`,hash:`SHA-256`},!1,[`sign`]);return new Uint8Array(await crypto.subtle.sign(`HMAC`,n,T(C(t))))}async function ze(e,t){return w(await Re(e,t))}const E={ts:`x-moksa-bus-ts`,id:`x-moksa-bus-id`,sig:`x-moksa-bus-sig`},Be=[E.ts,E.id,E.sig];function Ve(e,t,n,r){return`${e}\n${t}\n${n}\n${r}`}function He(e,t){if(typeof e.get==`function`)return e.get(t)??void 0;let n=e;return n[t]??Object.entries(n).find(([e])=>e.toLowerCase()===t)?.[1]}async function Ue(e,t,n,r,i=Date.now()){if(!e)return{ok:!1,reason:`not_configured`};let a=He(t,E.ts),o=He(t,E.id),s=He(t,E.sig);return!a||!o||!s||!/^\d{1,12}$/.test(a)||o.length>200?{ok:!1,reason:`missing_headers`}:Math.abs(Math.floor(i/1e3)-Number(a))>300?{ok:!1,reason:`stale`}:Ne(await ze(e,Ve(a,o,n,r)),s.toLowerCase())?{ok:!0,id:o}:{ok:!1,reason:`bad_signature`}}function We(e,t,n){return Fe({public:!0,methods:[`POST`],request:{body:`text`,headers:Be,maxBytes:256*1024},handler:async(r,i)=>{let a=r.input,o=await Ue(await i.settings.get(`busKey`),r.request.headers,e,a);if(!o.ok)return{ok:!1,error:`BUS_${o.reason.toUpperCase()}`};let s=t(a);return s.ok?n(s.value,i,o.id):s.reply}})}function D(t,n,r){return We(t,t=>{let r;try{r=JSON.parse(t)}catch{return{ok:!1,reply:{ok:!1,error:`INVALID_JSON`}}}let i=e(n,r);return i.ok?{ok:!0,value:i.value}:{ok:!1,reply:{ok:!1,error:`INVALID_INPUT`,detail:i.error}}},r)}function Ge(e,t){return We(e,e=>({ok:!0,value:e}),t)}function Ke(e){return Math.min(36e5,3e4*2**Math.max(0,e-1))}function qe(e){return e.storage.reports}async function Je(e,t,n=Ce){let r=n(t);if(!r.ok)throw Error(`invalid report: ${r.error}`);let i=new Date().toISOString(),a={id:t.reportId,report:r.value,status:`pending`,attempts:0,nextAt:i,createdAt:i};return(await qe(e).compareAndSet(a.id,null,a)).applied?a:await qe(e).get(a.id)??a}async function Ye(e,t,n=new Date){let r=qe(e),i=await r.query({where:{status:`pending`,nextAt:{lte:n.toISOString()}},orderBy:{nextAt:`asc`},limit:t}),a=[];for(let{id:e,data:t}of i.items){let i=await r.updateIf(e,{where:{status:`pending`,nextAt:t.nextAt},set:{nextAt:new Date(n.getTime()+Ke(t.attempts+1)).toISOString()},delta:{attempts:{inc:1}}});i.applied&&a.push(i.data)}return a}async function Xe(e,t,n,r){let i=qe(e);for(let e=0;e<8;e++){let e=await i.getVersioned(t);if(!e)return null;let a=n?{...e.value,status:`done`,doneAt:new Date().toISOString()}:{...e.value,lastError:(r??`failed`).slice(0,300)};if((await i.compareAndSet(t,e.revision,a)).applied)return a}return null}const Ze=p({limit:g(o({min:1,max:100}),20)}),Qe=p({id:i({min:1,max:200}),ok:l(),error:h(i({max:500}))});function $e(){return{[x.reportsDue]:D(x.reportsDue,Ze,async(e,t)=>({ok:!0,reports:(await Ye(t,e.limit)).map(e=>({id:e.id,attempts:e.attempts,report:e.report}))})),[x.reportsAck]:D(x.reportsAck,Qe,async(e,t)=>{let n=await Xe(t,e.id,e.ok,e.error);return n?{ok:!0,status:n.status}:{ok:!1,error:`NOT_FOUND`}})}}function O(e){return e.storage}function et(e,t=`0|Unknown`,n=404){return{ok:!0,outcome:e,reply:t,httpStatus:n,passThrough:!0}}function tt(e){return e.passThrough?{ok:!0,outcome:e.outcome,passThrough:!0,response:{kind:`redirect`,status:303,location:`/shop`}}:{ok:!0,outcome:e.outcome,response:{kind:`redirect`,status:303,location:e.orderNumber?`/shop/order/${e.orderNumber}`:`/shop`},...e.event?{payment:{...e.event}}:{},...e.paymentInfo?{paymentInfo:e.paymentInfo}:{},...e.paymentFailure?{paymentFailure:e.paymentFailure}:{},...e.orderNumber?{orderNumber:e.orderNumber}:{}}}async function nt(e,t,n,r,i=``){let{trades:a}=O(e),o=await a.query({where:{orderNumber:t},limit:100});if(o.items.some(e=>e.data.status===`paid`))throw Error(`ALREADY_PAID`);for(let e=o.items.length+1;e<1296;e++){let o={merchantTradeNo:`${i}${t}${e.toString(36).toUpperCase().padStart(2,`0`)}`,orderNumber:t,amount:n,method:r,status:`created`,createdAt:new Date().toISOString()};if((await a.compareAndSet(o.merchantTradeNo,null,o)).applied)return o}throw Error(`TOO_MANY_ATTEMPTS`)}async function rt(e,t,n){let{trades:r}=O(e);for(let e=0;e<8;e++){let e=await r.getVersioned(t);if(!e)return null;if(e.value.status===`paid`)return e.value;let i={...e.value,...n,updatedAt:new Date().toISOString()};if(n.status===`paid`&&!i.paidAt&&(i.paidAt=i.updatedAt),(await r.compareAndSet(t,e.revision,i)).applied)return i}return await r.get(t)??null}async function it(e,t){return(await O(e).trades.query({where:{orderNumber:t},limit:100})).items.map(e=>e.data).find(e=>e.status===`paid`)??null}async function at(e,t,n){await O(e).trades.updateIf(t,{where:{},set:{updatedAt:new Date().toISOString()},delta:{refunded:{inc:n}}})}async function ot(e,t,n){return t.ok?(await n(),{ok:!0,action:t.action??`refund`,message:t.message,merchantTradeNo:e}):{ok:!1,error:t.manual?`MANUAL_REFUND`:`REFUND_FAILED`,detail:t.message}}async function st(e,t,n,r){let{events:i,trades:a}=O(e);if(!(await i.compareAndSet(t.id,null,{id:t.id,type:t.type,createdAt:new Date().toISOString()})).applied)return{ok:!0,handled:!1,duplicate:!0};if(t.type===`order.refund_requested`){let n=r?await lt(e,t.payload,r):null;return{ok:!0,handled:!!n,duplicate:!1,...n?{reports:[n]}:{}}}if(t.type===`order.paid`){let e=String(t.payload.orderNumber??``),n=await a.query({where:{orderNumber:e},limit:100});for(let{id:e,data:t}of n.items)t.status===`paid`&&!t.settled&&await a.put(e,{...t,settled:!0})}let o=n?await n(e,t):null;return{ok:!0,handled:!0,duplicate:!1,...o?.reports.length?{reports:o.reports}:{}}}async function ct(e,t){let n={kind:`refund`,reportId:`${t.provider}:${t.refundId}`,orderNumber:t.orderNumber,status:`refunded`,amount:t.amount,provider:t.provider,message:t.message.slice(0,300),origin:`plugin`,...t.reason?{reason:t.reason.slice(0,200)}:{}};return await Je(e,n),n}async function lt(e,t,n){if(t.provider!==n.provider)return null;let r=String(t.orderNumber??``),i=String(t.requestId??``),a=Number(t.amount);if(!r||!i||!Number.isSafeInteger(a)||a<=0)return null;let o,s;try{let t=await n.refund(e,r,a);o=t.ok?`refunded`:t.error===`MANUAL_REFUND`?`manual`:`failed`,s=t.ok?t.message??``:t.detail??t.error??``}catch(e){o=`failed`,s=`退款時發生錯誤,請先到金流後台確認是否已退款:${e.message}`}let c={kind:`refund`,reportId:`${i}:refund`,orderNumber:r,requestId:i,status:o,amount:a,provider:n.provider,message:s.slice(0,300)};return await Je(e,c),c}function k(t,n,r=[`GET`,`POST`]){return{permission:`plugins:manage`,methods:r,handler:async(r,i)=>{let a=e(t,r.input??{});return a.ok?n(a.value,i):{ok:!1,error:`INVALID_INPUT`,detail:a.error}}}}function ut(e,t=`23:59:59`){if(!e)return null;let n=/^(\d{4})[-/]?(\d{2})[-/]?(\d{2})(?:[ T](\d{2}):?(\d{2})(?::?(\d{2}))?)?$/.exec(e.trim());if(!n)return null;let[r,i,a]=t.split(`:`),o=new Date(`${n[1]}-${n[2]}-${n[3]}T${n[4]??r}:${n[5]??i}:${n[6]??a}+08:00`);return Number.isNaN(o.getTime())?null:o.toISOString()}const dt=/^https?:\/\/[A-Za-z0-9.-]+(?::\d{1,5})?$/,ft=/^https?:\/\/[^\s"<>]+$/;function pt(e){return/^\/[0-9A-Z.+-]{7}$/.test(e)}function mt(e){return/^[A-Z]{2}[0-9]{14}$/.test(e)}function ht(e){return/^[0-9]{3,7}$/.test(e)}function gt(e){if(!/^\d{8}$/.test(e))return!1;let t=[1,2,1,2,1,2,4,1],n=0;for(let r=0;r<8;r++){let i=Number(e[r])*t[r];n+=Math.floor(i/10)+i%10}return n%5==0?!0:e[6]===`7`&&(n+1)%5==0}function _t(e){let t=[];return e.kind===`mobile`&&!pt((e.carrierNum??``).trim().toUpperCase())&&t.push(`手機條碼格式為「/」加 7 碼英數字。`),e.kind===`certificate`&&!mt((e.carrierNum??``).trim().toUpperCase())&&t.push(`自然人憑證條碼為 2 碼大寫英文加 14 碼數字。`),e.kind===`donate`&&!ht((e.loveCode??``).trim())&&t.push(`捐贈碼為 3 到 7 碼數字。`),e.kind===`company`&&(gt((e.taxId??``).trim())||t.push(`統一編號格式不正確。`),(e.companyTitle??``).trim()||t.push(`請填寫公司抬頭。`)),t}function vt(e,t,n=`件`){let r=[],i=0;for(let t of e.lines)i+=t.amount,r.push({name:t.name.slice(0,100)||t.sku,quantity:t.quantity,unit:n,unitPrice:t.unitPrice,amount:t.amount});if(e.shippingFee>0&&(i+=e.shippingFee,r.push({name:`運費`,quantity:1,unit:`式`,unitPrice:e.shippingFee,amount:e.shippingFee})),i!==t){let e=t-i;r.push({name:e<0?`折扣`:`其他`,quantity:1,unit:`式`,unitPrice:e,amount:e})}return r}function yt(e){let t=e.lines.reduce((e,t)=>e+t.amount,0),n=Math.min(e.discountTotal,t),r=0;return e.lines.map((i,a)=>{let o=a===e.lines.length-1?n-r:t>0?Math.floor(n*i.amount/t):0;return r+=o,{sku:i.sku,name:i.name,quantity:i.quantity,amount:i.amount-o,temperature:i.temperature,weightKg:i.weightKg}})}function bt(e){let t=e.shipping?.address;return(`${t?.city??``}${t?.line??``}`.trim()||e.shipping?.store?.address||e.invoice?.address?.trim()||`無`).slice(0,100)}function xt(e=new Date){return new Date(e.getTime()+8*36e5).toISOString().replace(`T`,` `).slice(0,19)}function St(e){let t=/^(\d{4})[-/](\d{2})[-/](\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2}))?)?/.exec(e.trim());return t?`${t[1]}-${t[2]}-${t[3]} ${t[4]??`00`}:${t[5]??`00`}:${t[6]??`00`}`:xt()}var A=class extends Error{constructor(e){super(e),this.name=`ProviderError`}};const j=()=>new Date().toISOString();function M(e){return e.storage}async function Ct(e,t){return M(e).orders.get(t)}async function wt(e,t,n={}){let{orders:r}=M(e),i=await r.get(t.number),a={...t,refundedTotal:i?.refundedTotal??0,lastError:i?.lastError??null,invoiceDueAt:i?.invoiceDueAt??null,...n,updatedAt:j()};return await r.put(t.number,a),a}async function N(e,t,n){let{orders:r}=M(e);await r.updateIf(t,{where:{},set:{lastError:n,updatedAt:j()}})}async function Tt(e,t,n){let r=`lock:${t}`,i=await e.kv.getVersioned(r);if(i&&Date.now()-i.value<6e4||!(await e.kv.compareAndSet(r,i?.revision??null,Date.now())).applied)return{busy:!0};try{return await n()}finally{await e.kv.delete(r)}}function Et(e){return!!(e&&typeof e==`object`&&e.busy===!0)}const Dt=new Set([`completed`,`returned`,`failed`,`void`,`unclaimed`]),Ot={created:0,in_progress:1,shipped:2,arrived:3,store_closed:3,unclaimed:4,completed:5,returned:5,failed:5,void:6};function kt(e){return{provider:e.provider,shipmentId:e.shipmentId,carrier:e.carrier,method:e.method,trackingNo:e.trackingNo,trackingUrl:e.trackingUrl,status:e.status,statusText:e.statusText,isCollection:e.isCollection,occurredAt:e.occurredAt}}function At(e){return{kind:`shipment`,reportId:`${e.shipmentId}:${e.status}:${e.occurredAt}`,orderNumber:e.orderNumber,shipment:kt(e)}}async function jt(e,t,n){let{shipments:r}=M(e);for(let i=0;i<8;i++){let i=await r.getVersioned(t);if(!i)return{result:`unknown`};let a=i.value,o={...a.details,...Object.fromEntries(Object.entries(n.details??{}).filter(([,e])=>e))},s=n.trackingNo||a.trackingNo,c=n.providerRef||a.providerRef,l=`duplicate`,u=a.status,d=a.statusText,f=a.statusCode,p=a.statusAt;if(n.status){let e=(n.statusCode??n.status)===(a.statusCode??a.status)&&(n.statusAt??null)===a.statusAt,t=!!(n.statusAt&&a.statusAt&&n.statusAt<a.statusAt),r=Dt.has(a.status)&&Ot[n.status]<Ot[a.status];e?l=`duplicate`:t||r||!a.active?l=`stale`:(l=`applied`,u=n.status,d=n.statusText??d,f=n.statusCode??n.status,p=n.statusAt??null)}let m=s!==a.trackingNo;if(l!==`applied`&&!m&&JSON.stringify(o)===JSON.stringify(a.details)&&c===a.providerRef)return{result:l,shipment:a,report:null};let h=l===`applied`||m,g={...a,status:u,statusText:d,statusCode:f,statusAt:p,trackingNo:s,providerRef:c,details:o,open:a.active&&!Dt.has(u),occurredAt:h?l===`applied`&&p?p:j():a.occurredAt,updatedAt:j()};if(!(await r.compareAndSet(t,i.revision,g)).applied)continue;let _=h?At(g):null;return _&&await Je(e,_),{result:h?l===`applied`?`applied`:`duplicate`:l,shipment:g,report:_}}return{result:`unknown`}}function Mt(e,t){return e.shipping?.provider===t.pluginId&&t.owns(e.shipping.methodId)}function Nt(e,t,n){let r=j();return{provider:e.pluginId,shipmentId:n.shipmentId,carrier:n.carrier,method:t.shipping.methodId,trackingNo:n.trackingNo,trackingUrl:n.trackingUrl??null,status:`created`,statusText:n.statusText??`已建立物流單`,isCollection:n.isCollection,occurredAt:r,orderNumber:t.number,createdAt:r,updatedAt:r,active:!0,open:!0,statusCode:null,statusAt:null,providerRef:n.providerRef,details:Object.fromEntries(Object.entries(n.details??{}).filter(([,e])=>e))}}async function Pt(e,t,n){let r=await Ct(e,n);if(!r)return{ok:!1,error:`NO_ORDER`,detail:`這個外掛沒有這筆訂單的資料(尚未收到訂單事件)。`};if(!Mt(r,t))return{ok:!1,error:`NOT_THIS_PROVIDER`,detail:`這筆訂單不是由這個物流外掛出貨。`};if(!r.paidAt&&!r.shipping?.collect)return{ok:!1,error:`NOT_PAID`,detail:`只有已付款或貨到付款的訂單可以建立物流單。`};if([`cancelled`,`refunded`,`failed`].includes(r.status))return{ok:!1,error:`ORDER_CLOSED`,detail:`訂單已取消或退款。`};let i=await Tt(e,`ship:${n}`,async()=>{let{shipments:i}=M(e),a=(await i.query({where:{orderNumber:n},orderBy:{updatedAt:`desc`},limit:50})).items.map(e=>e.data).filter(e=>e.active&&e.status!==`failed`&&e.status!==`void`),o={ok:!0,shipment:a[0],shipments:a,created:!1,report:null,reports:[]};if(a.length&&!t.multi)return o;let s,c;try{let n=await t.create(e,r,a);s=`shipments`in n?n.shipments:[n],c=`shipments`in n?n.errors:[]}catch(t){let r=t instanceof Error?t.message:String(t);return await N(e,n,`物流單建立失敗:${r}`),{ok:!1,error:`PROVIDER_ERROR`,detail:r}}if(!s.length)return c.length?(await N(e,n,`物流單建立失敗:${c.join(` / `)}`),{ok:!1,error:`PROVIDER_ERROR`,detail:c.join(` / `)}):a.length?o:{ok:!1,error:`NOTHING_TO_SHIP`,detail:`這筆訂單沒有需要建立的物流單。`};let l=[],u=[];for(let n of s){let a=Nt(t,r,n);await i.put(a.shipmentId,a);let o=At(a);await Je(e,o),l.push(a),u.push(o)}return await N(e,n,c.length?`部分物流單建立失敗:${c.join(` / `)}`:null),{ok:!0,shipment:l[0],shipments:[...a,...l],created:!0,report:u[0],reports:u,...c.length?{warning:c.join(` / `)}:{}}});return Et(i)?{ok:!1,error:`BUSY`,detail:`這筆訂單正在建立物流單,請稍後再試。`}:i}async function Ft(e,t,n){await e.kv.set(`map:${t}`,{...n,at:Date.now()})}async function It(e,t){if(!t)return null;let n=await e.kv.get(`map:${t}`);if(!n||(await e.kv.delete(`map:${t}`),Date.now()-Number(n.at)>18e5))return null;let{at:r,...i}=n;return i}async function Lt(e,t,n){let r=await M(e).shipments.get(n);if(!r)return{ok:!1,error:`NOT_FOUND`};let i;try{i=await t.sync(e,r)}catch(e){return{ok:!1,error:`PROVIDER_ERROR`,detail:e instanceof Error?e.message:String(e)}}let a=await jt(e,n,i);return a.result===`unknown`?{ok:!1,error:`NOT_FOUND`}:{ok:!0,shipment:a.shipment,result:a.result}}async function Rt(e,t,n){let{shipments:r}=M(e),i=await r.get(n);if(!i)return{ok:!1,error:`NOT_FOUND`};if(!i.active||i.status===`void`)return{ok:!0,shipment:i};if(Ot[i.status]>=Ot.shipped&&i.status!==`failed`)return{ok:!1,error:`ALREADY_SHIPPED`,detail:`物流單已出貨,無法作廢。`};if(t.cancel)try{await t.cancel(e,i)}catch(e){return{ok:!1,error:`PROVIDER_ERROR`,detail:e instanceof Error?e.message:String(e)}}let a=await jt(e,n,{status:`void`,statusText:t.cancel?`已取消物流單`:`已在商店作廢(物流商端請至其後台取消)`,statusCode:`void`});if(a.result===`unknown`)return{ok:!1,error:`NOT_FOUND`};let o={...a.shipment,active:!1,open:!1,updatedAt:j()};return await r.put(n,o),{ok:!0,shipment:o}}async function zt(e,t,n=20){let r=await M(e).shipments.query({where:{open:!0},orderBy:{updatedAt:`asc`},limit:n}),i=0;for(let{data:n}of r.items)!n.providerRef&&!n.trackingNo||(await Lt(e,t,n.shipmentId)).ok&&i++;return i}function Bt(e){return{provider:e.provider,status:e.status,invoiceNo:e.invoiceNo,invoiceDate:e.invoiceDate,randomNumber:e.randomNumber,kind:e.kind,amount:e.amount,allowanceTotal:e.allowanceTotal,error:e.error,occurredAt:e.occurredAt}}function Vt(e){return{kind:`invoice`,reportId:`${e.orderNumber}:invoice:${e.status}:${e.invoiceNo??`none`}:${e.allowanceTotal}:${e.occurredAt}`,orderNumber:e.orderNumber,invoice:Bt(e)}}async function Ht(e,t){await M(e).invoices.put(t.orderNumber,t);let n=Vt(t);return await Je(e,n),n}function Ut(e){return`${e}I${w(Pe(3)).toUpperCase()}`}async function Wt(e){return(await e.settings.get(`invoiceItemWord`)??``).trim().slice(0,2)||`件`}async function Gt(e,t,n){let r=await Ct(e,n);if(!r)return{ok:!1,error:`NO_ORDER`,detail:`這個外掛沒有這筆訂單的資料(尚未收到訂單事件)。`};if(r.invoice?.provider!==t.pluginId)return{ok:!1,error:`NOT_THIS_PROVIDER`,detail:`這筆訂單不是由這個發票外掛開立。`};if(!r.paidAt&&!r.shipping?.collect)return{ok:!1,error:`NOT_PAID`,detail:`訂單尚未付款,不能開立發票。`};if(!t.kinds.includes(r.invoice.kind))return{ok:!1,error:`UNSUPPORTED_KIND`,detail:`不支援的發票類型:${r.invoice.kind}`};let i=await Tt(e,`invoice:${n}`,async()=>{let{invoices:i}=M(e),a=await i.get(n);if(a?.status===`issued`)return{ok:!0,invoice:a,report:null};let o=r.total-r.refundedTotal;if(o<=0)return{ok:!1,error:`ZERO_AMOUNT`,detail:`訂單金額為 0,不需開立發票。`};let s=a?.status===`failed`&&a.relateNumber?a.relateNumber:t.relateNumber?await t.relateNumber(e,r):Ut(n),c={provider:t.pluginId,status:`issued`,invoiceNo:null,invoiceDate:null,randomNumber:null,kind:r.invoice.kind,amount:o,allowanceTotal:0,error:null,occurredAt:j(),orderNumber:n,relateNumber:s,updatedAt:j(),meta:{},allowances:[]};try{let i=await t.issue(e,r,{relateNumber:s,amount:o,lines:vt(r,o,await Wt(e)),discounted:yt(r),buyerAddress:bt(r)}),a={...c,invoiceNo:i.invoiceNo,invoiceDate:i.invoiceDate,randomNumber:i.randomNumber||null,meta:i.meta??{}},l=await Ht(e,a);return await N(e,n,null),{ok:!0,invoice:a,report:l}}catch(t){let r=(t instanceof Error?t.message:String(t)).slice(0,300),i={...c,status:`failed`,error:r},a=await Ht(e,i);return await N(e,n,`電子發票開立失敗:${r}`),{ok:!1,error:`PROVIDER_ERROR`,detail:r,invoice:i,report:a}}});return Et(i)?{ok:!1,error:`BUSY`,detail:`這筆訂單正在開立發票,請稍後再試。`}:i}async function Kt(e,t,n,r){let{invoices:i}=M(e),a=await i.get(n);if(!a||a.status!==`issued`||!a.invoiceNo)return{ok:!1,error:`NOT_ISSUED`,detail:`這筆訂單沒有已開立的發票。`};let o=await Tt(e,`invoice:${n}`,async()=>{let o=[...a.allowances];try{for(let[n,i]of o.entries())i.status===`issued`&&(await t.voidAllowance(e,a,i,r),o[n]={...i,status:`void`});await t.void(e,a,r)}catch(e){return await i.put(n,{...a,allowances:o,updatedAt:j()}),{ok:!1,error:`PROVIDER_ERROR`,detail:e instanceof Error?e.message:String(e)}}let s={...a,status:`void`,allowances:o,error:null,occurredAt:j(),updatedAt:j()};return{ok:!0,invoice:s,report:await Ht(e,s)}});return Et(o)?{ok:!1,error:`BUSY`}:o}async function qt(e,t,n,r,i){let{invoices:a}=M(e),[o,s]=await Promise.all([a.get(n),Ct(e,n)]);if(!o||o.status!==`issued`||!o.invoiceNo||!s)return{ok:!1,error:`NOT_ISSUED`,detail:`這筆訂單沒有已開立的發票。`};let c=o.amount-o.allowanceTotal;if(r<1||r>c)return{ok:!1,error:`INVALID_AMOUNT`,detail:`折讓金額需介於 1 到 ${c} 元。`};let l=await Tt(e,`invoice:${n}`,async()=>{let n;try{n=(await t.allowance(e,o,s,r,i)).allowanceNo}catch(e){return{ok:!1,error:`PROVIDER_ERROR`,detail:e instanceof Error?e.message:String(e)}}let a={...o,allowanceTotal:o.allowanceTotal+r,allowances:[...o.allowances,{allowanceNo:n,amount:r,status:`issued`,createdAt:j()}],occurredAt:j(),updatedAt:j()};return{ok:!0,invoice:a,report:await Ht(e,a)}});return Et(l)?{ok:!1,error:`BUSY`}:l}const Jt={member:`invoiceChannelMember`,mobile:`invoiceChannelMobile`,certificate:`invoiceChannelCert`,paper:`invoiceChannelPaper`,donate:`invoiceAllowDonate`,company:`invoiceAllowB2b`,defaultCarrier:`invoiceDefaultCarrier`,donateOrgs:`invoiceDonateOrgs`};async function Yt(e,t,n={}){let r=e=>n[e]??Jt[e],i=[];for(let n of[`member`,`mobile`,`certificate`,`paper`])t.includes(n)&&await P(e,r(n),!0)&&i.push(n);!i.length&&t.includes(`paper`)&&i.push(`paper`),t.includes(`donate`)&&await P(e,r(`donate`),!0)&&i.push(`donate`),t.includes(`company`)&&await P(e,r(`company`),!0)&&i.push(`company`);let a=String(await e.settings.get(r(`defaultCarrier`))||`member`).replace(/^cert$/,`certificate`);return{kinds:i,defaultKind:i.find(e=>e===a)??i[0],donateOrgs:String(await e.settings.get(r(`donateOrgs`))??``).split(/\r?\n|;/).map(e=>e.split(`|`).map(e=>e.trim())).filter(([e,t])=>e&&t&&/^\d{3,7}$/.test(t)).map(([e,t])=>({code:t,name:e}))}}async function P(e,t,n){let r=await e.settings.get(t);return r==null?n:r===!0}function Xt(e,t,n,r){return r===`completed`?e===`shipment.updated`&&t.shipment?.status===`completed`:e===`order.paid`&&!n||e===`order.created`&&n}async function Zt(e,t,n){let r=Math.max(0,Math.min(30,Math.floor(Number(await e.settings.get(`invoiceDelayDays`))||0)));return r>0?(await M(e).orders.updateIf(n,{where:{},set:{invoiceDueAt:new Date(Date.now()+r*864e5).toISOString(),updatedAt:j()}}),null):(await Gt(e,t,n)).report??null}async function Qt(e,t,n=20){let{orders:r}=M(e),i=await r.query({where:{invoiceDueAt:{lte:j()}},orderBy:{invoiceDueAt:`asc`},limit:n}),a=[];for(let{id:n}of i.items){await r.updateIf(n,{where:{},set:{invoiceDueAt:null,updatedAt:j()}});let i=await Gt(e,t,n);i.report&&a.push(i.report)}return a}async function $t(t,n,r){let i=e(ce,n.payload.order);if(!i.ok)return{handled:!1,reports:[]};let a=i.value,o=r.logistics&&Mt(a,r.logistics)?r.logistics:void 0,s=r.invoice&&a.invoice?.provider===r.invoice.pluginId?r.invoice:void 0;if(!o&&!s)return{handled:!1,reports:[]};let c=n.type===`order.refunded`?Math.max(0,Number(n.payload.refundedTotal)||0):void 0;await wt(t,a,c===void 0?{}:{refundedTotal:c});let l=[],u=e=>{e&&l.push(e)},d=!!a.shipping?.collect;if(o&&await P(t,`logisticsAutoShip`,!1)&&(!o.usable||await o.usable(t))&&(n.type===`order.created`&&d||n.type===`order.paid`&&!d)){let e=await Pt(t,o,a.number);e.ok&&l.push(...e.reports)}if(s){let e=(await t.settings.get(`invoiceIssueWhen`)??`paid`)===`completed`?`completed`:`paid`,r=n.type===`order.cancelled`||n.type===`order.refunded`&&n.payload.full===!0;if(await P(t,`invoiceAuto`,!0)&&Xt(n.type,n.payload,d,e))u(await Zt(t,s,a.number));else if(r&&await P(t,`invoiceAutoVoid`,!1)){await M(t).orders.updateIf(a.number,{where:{},set:{invoiceDueAt:null}});let e=n.type===`order.cancelled`?`訂單取消(自動作廢)`:`訂單退款(自動作廢)`,r=await Kt(t,s,a.number,e);r.ok&&u(r.report)}else if(n.type===`order.refunded`&&n.payload.full!==!0&&await P(t,`invoiceAutoAllowance`,!1)){let e=Math.floor(Number(n.payload.amount)||0);if(e>0){let n=await qt(t,s,a.number,e,`部分退款`);n.ok&&u(n.report)}}r&&await M(t).orders.updateIf(a.number,{where:{},set:{invoiceDueAt:null}})}return{handled:!0,reports:l}}const en=/^https?:\/\/[A-Za-z0-9.-]+(?::\d{1,5})?$/;async function tn(e){let t=(await e.settings.get(`siteUrl`)??``).trim().replace(/\/$/,``);return en.test(t)?t:await e.kv.get(`site:origin`)??null}async function nn(e,t){!t||!en.test(t)||await e.kv.get(`site:origin`)!==t&&await e.kv.set(`site:origin`,t)}const rn=p({temperatures:g(f(u([`normal`,`refrigerated`,`frozen`]),{max:200}),[])});async function an(e,t,n){let r=je(C(JSON.stringify(n))),i=await ze(await e.settings.get(`busKey`)??``,`store:${t}:${r}`);return`/shop/checkout?${new URLSearchParams({shipping:t,store:`${r}.${i}`})}`}const on=p({orderNumber:i({min:1,max:20,pattern:/^[A-Z0-9]+$/})}),sn=p({shipmentId:i({min:1,max:64})}),cn=p({orderNumber:h(i({max:20})),limit:g(o({min:1,max:100,coerce:!0}),20)}),ln=p({orderNumber:i({min:1,max:20,pattern:/^[A-Z0-9]+$/}),reason:g(i({min:1,max:50}),`訂單取消`)}),un=p({orderNumber:i({min:1,max:20,pattern:/^[A-Z0-9]+$/}),amount:o({min:1,max:99999999,coerce:!0}),reason:g(i({min:1,max:50}),`折讓`)});function F(e){return{...kt(e),orderNumber:e.orderNumber,active:e.active,statusCode:e.statusCode,providerRef:e.providerRef,details:e.details,createdAt:e.createdAt}}function dn(e){return{...Bt(e),orderNumber:e.orderNumber,relateNumber:e.relateNumber,allowances:e.allowances}}function fn(e){return{[S.create]:k(on,async(t,n)=>{let r=await Pt(n,e,t.orderNumber);return r.ok?{ok:!0,created:r.created,shipment:F(r.shipment),shipments:r.shipments.map(F),...r.warning?{warning:r.warning}:{}}:r},[`POST`]),[S.list]:k(cn,async(e,t)=>({ok:!0,shipments:(await M(t).shipments.query({where:e.orderNumber?{orderNumber:e.orderNumber}:void 0,orderBy:{updatedAt:`desc`},limit:e.limit})).items.map(e=>F(e.data))})),[S.sync]:k(sn,async(t,n)=>{let r=await Lt(n,e,t.shipmentId);return r.ok?{ok:!0,result:r.result,shipment:F(r.shipment)}:r}),[S.print]:k(sn,async(t,n)=>{let r=await M(n).shipments.get(t.shipmentId);if(!r)return{ok:!1,error:`NOT_FOUND`};try{return{ok:!0,...await e.print(n,r)}}catch(e){return{ok:!1,error:`PROVIDER_ERROR`,detail:e instanceof Error?e.message:String(e)}}}),[S.syncAll]:k(p({}),async(t,n)=>({ok:!0,synced:await zt(n,e,100)}),[`POST`]),[S.void]:k(sn,async(t,n)=>{let r=await Rt(n,e,t.shipmentId);return r.ok?{ok:!0,shipment:F(r.shipment)}:r},[`POST`])}}function pn(e){let t=e=>e.ok?{ok:!0,invoice:dn(e.invoice)}:{ok:!1,error:e.error,detail:e.detail,...e.invoice?{invoice:dn(e.invoice)}:{}};return{[Te.issue]:k(on,async(n,r)=>t(await Gt(r,e,n.orderNumber)),[`POST`]),[Te.get]:k(on,async(e,t)=>{let n=await M(t).invoices.get(e.orderNumber);return n?{ok:!0,invoice:dn(n)}:{ok:!1,error:`NOT_FOUND`}}),[Te.void]:k(ln,async(n,r)=>t(await Kt(r,e,n.orderNumber,n.reason)),[`POST`]),[Te.allowance]:k(un,async(n,r)=>t(await qt(r,e,n.orderNumber,n.amount,n.reason)),[`POST`]),"invoices/due":k(p({}),async(t,n)=>({ok:!0,reports:await Qt(n,e)}),[`POST`]),"invoices/query":k(on,async(t,n)=>{let r=await M(n).invoices.get(t.orderNumber);if(!r?.relateNumber||!r.invoiceNo)return{ok:!1,error:`NOT_ISSUED`};if(!e.query)return{ok:!1,error:`UNSUPPORTED`};try{return{ok:!0,result:await e.query(n,r)}}catch(e){return{ok:!1,error:`PROVIDER_ERROR`,detail:e instanceof Error?e.message:String(e)}}})}}const mn=p({kind:u([`member`,`mobile`,`certificate`,`paper`,`donate`,`company`]),carrierNum:h(i({max:64})),loveCode:h(i({max:10})),taxId:h(i({max:8})),companyTitle:h(i({max:100}))});function hn(e,t,n){return{"bus/invoice/options":D(`bus/invoice/options`,p({}),async(r,i)=>{if(!await n(i))return{ok:!0,provider:e.pluginId,label:t,kinds:[]};let a=e.checkoutOptions?await e.checkoutOptions(i):{kinds:[...e.kinds]};return{ok:!0,provider:e.pluginId,label:t,...a}}),"bus/invoice/verify":D(`bus/invoice/verify`,mn,async(t,n)=>{if(!(e.checkoutOptions?(await e.checkoutOptions(n)).kinds:e.kinds).includes(t.kind))return{ok:!0,valid:!1,errors:[`不支援的發票類型:${t.kind}`]};let r=_t(t);if(r.length)return{ok:!0,valid:!1,errors:r};if(e.verifyOnline&&await P(n,`invoiceVerifyOnline`,!0))try{let r=await e.verifyOnline(n,{kind:t.kind,carrierNum:t.carrierNum?.trim().toUpperCase(),loveCode:t.loveCode?.trim()});if(r)return{ok:!0,valid:!1,errors:[r]}}catch{}return{ok:!0,valid:!0,errors:[]}})}}const gn={created:`已建立`,in_progress:`處理中`,shipped:`已出貨`,arrived:`已到店`,completed:`已完成`,store_closed:`門市異常`,unclaimed:`逾期未取`,returned:`已退回`,failed:`失敗`,void:`已作廢`},_n={issued:`已開立`,failed:`開立失敗`,void:`已作廢`};async function vn(e,t,n,r){let{shipments:i,orders:a}=M(e),[o,s,c]=await Promise.all([i.query({orderBy:{updatedAt:`desc`},limit:30}),i.count({open:!0}),a.query({orderBy:{updatedAt:`desc`},limit:50})]),l=c.items.map(e=>e.data).filter(e=>e.lastError?.startsWith(`物流`));return{blocks:[{type:`header`,text:t},{type:`stats`,items:[...n,{label:`未完成的物流單`,value:String(s)}]},...r?[{type:`context`,text:r}]:[],...l.length?[{type:`table`,columns:[{key:`order`,label:`訂單`},{key:`error`,label:`最近的自動建單錯誤`}],rows:l.slice(0,10).map(e=>({order:e.number,error:e.lastError??``})),page_action_id:`browse_errors`}]:[],{type:`table`,columns:[{key:`shipmentId`,label:`物流單`},{key:`orderNumber`,label:`訂單`},{key:`carrier`,label:`物流`},{key:`trackingNo`,label:`追蹤號碼`},{key:`status`,label:`狀態`},{key:`updatedAt`,label:`更新時間`}],rows:o.items.map(({data:e})=>({shipmentId:e.shipmentId,orderNumber:e.orderNumber,carrier:`${e.carrier}${e.isCollection?`(代收)`:``}`,trackingNo:e.trackingNo??``,status:`${gn[e.status]??e.status}${e.statusText?`:${e.statusText}`:``}`,updatedAt:e.updatedAt.slice(0,16).replace(`T`,` `)})),page_action_id:`browse_shipments`,empty_text:`尚無物流單。`}]}}async function yn(e,t,n){let{invoices:r}=M(e),[i,a,o]=await Promise.all([r.query({orderBy:{updatedAt:`desc`},limit:30}),r.count({status:`issued`}),r.count({status:`failed`})]);return{blocks:[{type:`header`,text:t},{type:`stats`,items:[...n,{label:`已開立`,value:String(a)},{label:`開立失敗`,value:String(o)}]},{type:`table`,columns:[{key:`orderNumber`,label:`訂單`},{key:`invoiceNo`,label:`發票號碼`},{key:`amount`,label:`金額`},{key:`status`,label:`狀態`},{key:`date`,label:`開立時間`}],rows:i.items.map(({data:e})=>({orderNumber:e.orderNumber,invoiceNo:e.invoiceNo??``,amount:`NT$${e.amount}${e.allowanceTotal?`(折讓 NT$${e.allowanceTotal})`:``}`,status:`${_n[e.status]??e.status}${e.error?`:${e.error}`:``}`,date:e.invoiceDate??``})),page_action_id:`browse_invoices`,empty_text:`尚無發票。`}]}}const bn={import:`migrate/import`,kv:`migrate/kv`,stats:`migrate/stats`};function xn(e,t=[]){let n=p({collection:u(e),mode:g(u([`create`,`replace`]),`create`),items:f(p({id:i({min:1,max:300}),data:m(c(),{maxKeys:200})}),{min:1,max:100})}),r=p({mode:g(u([`create`,`replace`]),`create`),items:f(p({key:i({min:1,max:300}),value:c()}),{min:1,max:100})}),a=(e,t)=>e.storage[t];return{[bn.import]:k(n,async(e,t)=>{let n=a(t,e.collection),r=0,i=0;for(let t of e.items)e.mode===`replace`?(await n.put(t.id,t.data),r++):(await n.compareAndSet(t.id,null,t.data)).applied?r++:i++;return{ok:!0,created:r,skipped:i}},[`POST`]),[bn.kv]:k(r,async(e,n)=>{let r=0,i=0;for(let n of e.items)if(!t.some(e=>n.key.startsWith(e)))return{ok:!1,error:`INVALID_INPUT`,detail:`key ${n.key}`};for(let t of e.items)e.mode===`replace`?(await n.kv.set(t.key,t.value),r++):(await n.kv.compareAndSet(t.key,null,t.value)).applied?r++:i++;return{ok:!0,created:r,skipped:i}},[`POST`]),[bn.stats]:k(p({}),async(t,n)=>({ok:!0,counts:Object.fromEntries(await Promise.all(e.map(async e=>[e,await a(n,e).count()])))}),[`GET`,`POST`])}}function Sn(e){if(![16,24,32].includes(e.length))throw Error(`AES key must be 16, 24 or 32 bytes, got ${e.length}`)}function Cn(e,t,n){return Sn(e),crypto.subtle.importKey(`raw`,T(e),{name:t},!1,n)}function wn(e,t,n=16){if(t===`none`){if(e.length%n!==0)throw Error(`Data length must be a multiple of the block size without padding`);return e}if(t===`zero`){let t=e.length%n;return t===0&&e.length>0?e:Me(e,new Uint8Array(n-t))}let r=n-e.length%n;return Me(e,new Uint8Array(r).fill(r))}function Tn(e,t){if(t===`none`)return e;if(t===`zero`){let t=e.length;for(;t>0&&e[t-1]===0;)t--;return e.slice(0,t)}let n=e[e.length-1];if(n===void 0||n===0||n>e.length)return null;for(let t=e.length-n;t<e.length;t++)if(e[t]!==n)return null;return e.slice(0,e.length-n)}async function En(e,t,n){return new Uint8Array(await crypto.subtle.encrypt({name:`AES-CBC`,iv:T(t)},e,T(n))).slice(0,n.length)}async function Dn(e,t,n){let r=n.slice(n.length-16),i=new Uint8Array(await crypto.subtle.encrypt({name:`AES-CBC`,iv:T(r)},e,new ArrayBuffer(0)));return new Uint8Array(await crypto.subtle.decrypt({name:`AES-CBC`,iv:T(t)},e,T(Me(n,i))))}async function On(e,t,n,r=`pkcs7`){let i=C(t);if(i.length!==16)throw Error(`AES-CBC IV must be 16 bytes`);return En(await Cn(C(e),`AES-CBC`,[`encrypt`]),i,wn(C(n),r))}async function kn(e,t,n,r=`pkcs7`){let i=C(t);if(i.length!==16)throw Error(`AES-CBC IV must be 16 bytes`);return n.length===0||n.length%16!=0?null:Tn(await Dn(await Cn(C(e),`AES-CBC`,[`encrypt`,`decrypt`]),i,n),r)}function An(e){return Object.fromEntries(new URLSearchParams(e))}function jn(e){return encodeURIComponent(e).replace(/[!'()*~]/g,e=>`%`+e.charCodeAt(0).toString(16).toUpperCase()).replace(/%20/g,`+`)}const I=`methodLabels`,Mn={name:100,desc:300};function Nn(e,t){return String(e??``).trim().slice(0,Mn[t])}async function L(e,t){let n=await e.kv.get(I);if(n)return n;let r=new Set(t),i=[...await e.settings.list(`name`),...await e.settings.list(`desc`)].filter(e=>r.has(e.key.slice(4))),a={};for(let{key:e,value:t}of i){let n=e.startsWith(`name`)?`name`:`desc`,r=Nn(t,n);r&&(a[e.slice(4)]={...a[e.slice(4)],[n]:r})}let o=await e.kv.compareAndSet(I,null,a);for(let{key:t}of i)await e.settings.delete(t);return o.applied?a:await e.kv.get(I)??a}function Pn(e,t,n){let r=e[t]??{};return{code:t,label:r.name||n,...r.desc?{description:r.desc}:{}}}const Fn=`label:`;function In(e,t,n,r){return{blocks:[{type:`header`,text:`${e}:付款方式名稱與說明`},{type:`section`,text:`結帳頁顯示的付款方式名稱與說明。名稱留空時用預設名稱,說明留空不顯示。付款方式的開關在外掛設定。`},...t.map(e=>({type:`form`,block_id:`label-${e.code}`,fields:[{type:`text_input`,action_id:`name`,label:`「${e.label}」顯示名稱`,placeholder:e.label,initial_value:n[e.code]?.name??``},{type:`text_input`,action_id:`desc`,label:`「${e.label}」說明`,multiline:!0,initial_value:n[e.code]?.desc??``}],submit:{label:`儲存「${e.label}」`,action_id:`${Fn}${e.code}`}}))],...r?{toast:r}:{}}}async function Ln(e,t,n,r,i){if(!r.startsWith(Fn))return null;let a=n.find(e=>e.code===r.slice(6)),o=n.map(e=>e.code);if(!a)return In(t,n,await L(e,o),{message:`沒有這個付款方式`,type:`error`});await L(e,o);let s={name:Nn(i.name,`name`),desc:Nn(i.desc,`desc`)};for(let r=0;r<5;r++){let r=await e.kv.getVersioned(I),i={...r?.value};if(s.name||s.desc?i[a.code]={...s.name?{name:s.name}:{},...s.desc?{desc:s.desc}:{}}:delete i[a.code],(await e.kv.compareAndSet(I,r?.revision??null,i)).applied)return In(t,n,i,{message:`已儲存「${a.label}」`,type:`success`})}return In(t,n,await L(e,o),{message:`儲存失敗,請再試一次`,type:`error`})}async function R(e,t){return await e.kv.get(t.key)??{}}const Rn=(e,t)=>e.fieldsFor?.(t)??e.fields;function z(e,t,n,r){return{blocks:[{type:`header`,text:e.heading},{type:`section`,text:e.intro},...t.map(t=>({type:`form`,block_id:`${e.action}${t.code}`,fields:Rn(e,t).map(e=>({type:`text_input`,action_id:e.id,label:e.label(t),...e.kind===`multiline`?{multiline:!0}:{},...e.placeholder?{placeholder:e.placeholder(t)}:{},initial_value:n[t.code]?.[e.id]===void 0?``:String(n[t.code][e.id])})),submit:{label:`儲存「${t.label}」`,action_id:`${e.action}${t.code}`}}))],...r?{toast:r}:{}}}function zn(e,t){let n=String(t??``).trim();if(n)return e.kind===`amount`?/^\d{1,8}$/.test(n)?Number(n):null:n.slice(0,e.max??300)}async function Bn(e,t,n,r,i){if(!r.startsWith(t.action))return null;let a=n.find(e=>e.code===r.slice(t.action.length));if(!a)return z(t,n,await R(e,t),{message:`沒有這個${t.noun}`,type:`error`});let o={};for(let r of Rn(t,a)){let s=zn(r,i[r.id]);if(s===null)return z(t,n,await R(e,t),{message:`「${r.label(a)}」請填 0 以上的整數,或留空`,type:`error`});s!==void 0&&(o[r.id]=s)}for(let r=0;r<5;r++){let r=await e.kv.getVersioned(t.key),i={...r?.value};if(Object.keys(o).length?i[a.code]=o:delete i[a.code],(await e.kv.compareAndSet(t.key,r?.revision??null,i)).applied)return z(t,n,i,{message:`已儲存「${a.label}」`,type:`success`})}return z(t,n,await R(e,t),{message:`儲存失敗,請再試一次`,type:`error`})}function Vn(e){let t=e??{};return t.type!==`form_submit`||typeof t.action_id!=`string`?null:{actionId:t.action_id,values:t.values&&typeof t.values==`object`?t.values:{}}}const Hn=[7,12,17,22,5,9,14,20,4,11,16,23,6,10,15,21],Un=Array.from({length:64},(e,t)=>Math.floor(Math.abs(Math.sin(t+1))*2**32)>>>0);function Wn(e,t){return(e<<t|e>>>32-t)>>>0}function Gn(e){let t=C(e),n=t.length*8,r=new Uint8Array((t.length+8>>6)+1<<6);r.set(t),r[t.length]=128;let i=new DataView(r.buffer);i.setUint32(r.length-8,n>>>0,!0),i.setUint32(r.length-4,Math.floor(n/2**32),!0);let a=1732584193,o=4023233417,s=2562383102,c=271733878,l=Array(16);for(let e=0;e<r.length;e+=64){for(let t=0;t<16;t++)l[t]=i.getUint32(e+t*4,!0);let t=a,n=o,r=s,u=c;for(let e=0;e<64;e++){let i,a;e<16?(i=n&r|~n&u,a=e):e<32?(i=u&n|~u&r,a=(5*e+1)%16):e<48?(i=n^r^u,a=(3*e+5)%16):(i=r^(n|~u),a=7*e%16);let o=t+i+Un[e]+l[a]>>>0;t=u,u=r,r=n,n=n+Wn(o,Hn[(e>>4)*4+e%4])>>>0}a=a+t>>>0,o=o+n>>>0,s=s+r>>>0,c=c+u>>>0}let u=new Uint8Array(16),d=new DataView(u.buffer);return d.setUint32(0,a,!0),d.setUint32(4,o,!0),d.setUint32(8,s,!0),d.setUint32(12,c,!0),u}function Kn(e){return w(Gn(e))}const qn={merchantId:`3002607`,hashKey:`pwFHCqoQZGmho4w6`,hashIv:`EkRm7iFT261dpevs`},Jn={stage:`payment-stage.ecpay.com.tw`,production:`payment.ecpay.com.tw`},Yn={"%2d":`-`,"%5f":`_`,"%2e":`.`,"%21":`!`,"%2a":`*`,"%28":`(`,"%29":`)`};function Xn(e,t,n){return jn(`HashKey=${t}&${Object.keys(e).filter(e=>e!==`CheckMacValue`).sort((e,t)=>{let n=e.toLowerCase(),r=t.toLowerCase();return n<r?-1:n>r?1:0}).map(t=>`${t}=${e[t]}`).join(`&`)}&HashIV=${n}`).toLowerCase().replace(/%2d|%5f|%2e|%21|%2a|%28|%29/g,e=>Yn[e])}async function B(e,t,n,r=`sha256`){let i=Xn(e,t,n);return(r===`md5`?Kn(i):await Le(i)).toUpperCase()}async function Zn(e,t,n,r=`sha256`){let i=e.CheckMacValue;return!i||!t||!n?!1:Ne(i.toUpperCase(),await B(e,t,n,r))}function Qn(e=new Date){let t=new Date(e.getTime()+8*36e5).toISOString();return`${t.slice(0,4)}/${t.slice(5,7)}/${t.slice(8,10)} ${t.slice(11,19)}`}function $n(e){let t=e.map(e=>e.replace(/#/g,`#`)).join(`#`);return t.length>400?`${t.slice(0,397)}...`:t}const er=`moksa-ecpay`,tr={normal:1,refrigerated:2,frozen:3},nr={normal:`常溫`,refrigerated:`冷藏`,frozen:`冷凍`},rr=[`normal`,`refrigerated`,`frozen`],ir={C2C:{merchantId:`2000933`,hashKey:`XBERn1YOvpM9nfZc`,hashIv:`h1ONHk4P4yqbl5LK`},B2C:{merchantId:`2000132`,hashKey:`5294y06JbISpM5x9`,hashIv:`v77hoKGq4kWxNNIS`}},ar={stage:`logistics-stage.ecpay.com.tw`,production:`logistics.ecpay.com.tw`},or=rr,V=[`normal`],H=[{id:`ecpay-cvs-unimart`,setting:`cvsUnimart`,label:`7-ELEVEN 超商取貨`,kind:`cvs`,subtype:{C2C:`UNIMARTC2C`,B2C:`UNIMART`},carrier:`7-ELEVEN`,temps:{C2C:V,B2C:[`normal`,`frozen`]},enabledByDefault:!0},{id:`ecpay-cvs-fami`,setting:`cvsFami`,label:`全家超商取貨`,kind:`cvs`,subtype:{C2C:`FAMIC2C`,B2C:`FAMI`},carrier:`全家`,temps:{C2C:V,B2C:V},enabledByDefault:!0},{id:`ecpay-cvs-hilife`,setting:`cvsHilife`,label:`萊爾富超商取貨`,kind:`cvs`,subtype:{C2C:`HILIFEC2C`,B2C:`HILIFE`},carrier:`萊爾富`,temps:{C2C:V,B2C:V},enabledByDefault:!1},{id:`ecpay-cvs-okmart`,setting:`cvsOkmart`,label:`OK 超商取貨`,kind:`cvs`,subtype:{C2C:`OKMARTC2C`,B2C:`OKMART`},carrier:`OK`,temps:{C2C:V,B2C:V},enabledByDefault:!1},{id:`ecpay-cvs-unimart-freeze`,setting:`cvsUnimartFreeze`,label:`7-ELEVEN 冷凍取貨`,kind:`cvs`,subtype:{C2C:`UNIMARTFREEZE`,B2C:`UNIMARTFREEZE`},carrier:`7-ELEVEN 冷凍`,temps:{C2C:[`frozen`],B2C:[`frozen`]},enabledByDefault:!1},{id:`ecpay-home-tcat`,setting:`homeTcat`,label:`黑貓宅急便`,kind:`home`,subtype:{C2C:`TCAT`,B2C:`TCAT`},carrier:`黑貓宅急便`,temps:{C2C:or,B2C:or},enabledByDefault:!0},{id:`ecpay-home-post`,setting:`homePost`,label:`中華郵政`,kind:`home`,subtype:{C2C:`POST`,B2C:`POST`},carrier:`中華郵政`,temps:{C2C:V,B2C:V},enabledByDefault:!1}];function sr(e){return H.find(t=>t.id===e)}function U(e){return e.endsWith(`C2C`)?`C2C`:`B2C`}function cr(e,t){return e===`UNIMART`&&t===`frozen`?`UNIMARTFREEZE`:e}function lr(e){let t=e.trim(),n=t.startsWith(`1|`);if(!/^[01]\|/.test(t))return{ok:!1,message:t.slice(0,200),fields:{}};let r=t.slice(2);if(!n)return{ok:!1,message:r,fields:{}};let i=Object.fromEntries(new URLSearchParams(r));return{ok:n,message:i.RtnMsg??``,fields:i}}async function ur(e,t,n){let r=e.trim();if(/^[01]\|/.test(r))return lr(r);if(!r.includes(`CheckMacValue=`))return{ok:!1,message:r.slice(0,200),fields:{}};let i=Object.fromEntries(new URLSearchParams(r));return await Zn(i,t,n,`md5`)?{ok:!0,message:``,fields:i}:{ok:!1,message:`CheckMacValue mismatch`,fields:{}}}const dr={shipped:[`2030`,`2068`,`3001`,`3006`,`3024`,`3032`,`3119`,`3301`,`3312`],arrived:[`2063`,`2073`,`3018`,`3029`,`3314`],completed:[`2067`,`3022`,`3003`,`3307`,`3308`,`3309`],store_closed:[`2016`,`2026`,`2037`,`2049`,`2050`,`2101`,`2104`],unclaimed:`2065.2074.2076.2078.2079.2080.2081.2082.2083.2084.2085.2086.2087.2088.2089.2092.2093.2072.2044.2070.3020.3025.3019.3031.3023.3310`.split(`.`),failed:[`2033`,`2034`,`2042`,`3303`,`3305`,`3306`,`4004`,`5001`,`5002`]};function fr(e){for(let[t,n]of Object.entries(dr))if(n.includes(e))return t;return`in_progress`}function pr(e){let t=e.replace(/[\^'`!@#$%&*+\\"<>|_[\]]/gu,` `).replace(/\s+/gu,` `).trim(),n=0,r=``;for(let e of t){let t=/[ᄀ-ᅟ⺀-가-힣豈-︰-﹏-⦆¢-₩]|[\u{20000}-\u{3fffd}]/u.test(e)?2:1;if(n+t>50)break;n+=t,r+=e}return r.trim()||`網路商品一批`}async function W(e,t){return String(await e.settings.get(t)??``).trim()}async function mr(e,t,n){let r=await e.settings.get(t);if(r==null||r===``)return n;let i=Number(r);return Number.isFinite(i)&&i>=0?i:n}async function hr(e,t,n){let r=await e.settings.get(t);return r==null?n:r===!0}async function gr(e){return await W(e,`logisticsEnvironment`)!==`production`}async function G(e,t){let n=await gr(e),r=t===`C2C`?`C2c`:`B2c`,i=await W(e,`logistics${r}MerchantId`),a=await e.settings.get(`logistics${r}HashKey`)??``,o=await e.settings.get(`logistics${r}HashIv`)??``;return i&&a&&o?{merchantId:i,hashKey:a,hashIv:o,stage:n}:n?{...ir[t],stage:n}:{merchantId:i,hashKey:a,hashIv:o,stage:n}}async function _r(e,t){let n=await G(e,t);return!!(n.merchantId&&n.hashKey&&n.hashIv)}function vr(e){return`https://${e?ar.stage:ar.production}`}async function K(e){return hr(e,`logisticsEnabled`,!1)}async function q(e){return await W(e,`logisticsContract`)===`B2C`?`B2C`:`C2C`}async function yr(e,t){return await hr(e,t.setting,t.enabledByDefault)?t.id===`ecpay-cvs-unimart-freeze`?await q(e)===`B2C`:t.id===`ecpay-cvs-okmart`?await q(e)===`C2C`:!0:!1}async function br(e,t){return t.temps[await q(e)]}const xr=H.map(e=>({code:e.id,label:e.label})),Sr=e=>H.some(t=>t.id===e&&[...t.temps.C2C,...t.temps.B2C].some(e=>e!==`normal`)),Cr=e=>H.some(t=>t.id===e&&![...t.temps.C2C,...t.temps.B2C].includes(`normal`)),wr=[{id:`name`,label:e=>`「${e.label}」顯示名稱`,kind:`text`,max:100,placeholder:e=>e.label},{id:`desc`,label:e=>`「${e.label}」說明`,kind:`multiline`,max:300},{id:`fee`,label:e=>`「${e.label}」運費(常溫)`,kind:`amount`,placeholder:e=>e.code.includes(`-cvs-`)?`留空:用外掛設定的超商取貨運費`:`留空:用外掛設定的宅配運費`},{id:`coldFee`,label:e=>`「${e.label}」低溫運費(購物車含冷藏或冷凍時)`,kind:`amount`,placeholder:()=>`留空:用外掛設定的低溫配送運費`},{id:`freeMin`,label:e=>`「${e.label}」免運門檻`,kind:`amount`,placeholder:()=>`留空:用商店的全站免運門檻;0 為一律免運`},{id:`spec`,label:e=>`「${e.label}」包裹尺寸(1=60 公分、2=90 公分、3=120 公分)`,kind:`amount`,placeholder:()=>`留空:1(60 公分)`}],J={key:`shippingMethods`,heading:`綠界物流:物流方式設定`,intro:`結帳頁顯示的物流方式名稱、說明、運費與免運門檻。留空的欄位沿用外掛設定的運費與商店的全站免運門檻。物流方式的開關在外掛設定。`,noun:`物流方式`,action:`ship:`,fields:wr,fieldsFor:e=>wr.filter(t=>(t.id!==`coldFee`||Sr(e.code))&&(t.id!==`fee`||!Cr(e.code))&&(t.id!==`spec`||e.code===`ecpay-home-tcat`))};async function Tr(e,t){if(!await K(e))return[];let n=[...new Set((t.length?t:[`normal`]).map(e=>e===`refrigerated`||e===`frozen`?e:`normal`))],r=n.some(e=>e!==`normal`),i=await R(e,J),a=[];for(let t of H){if(!await yr(e,t))continue;let o=await br(e,t);if(!n.every(e=>o.includes(e)))continue;let s=i[t.id]??{},c=e=>typeof s[e]==`number`?s[e]:void 0,l=c(r?`coldFee`:`fee`)??await mr(e,r?`coldFee`:t.kind===`cvs`?`cvsFee`:`homeFee`,r?160:t.kind===`cvs`?60:100),u=c(`freeMin`);a.push({id:t.id,label:typeof s.name==`string`&&s.name?s.name:t.label,...typeof s.desc==`string`&&s.desc?{description:s.desc}:{},...u===void 0?{}:{freeMin:u},carrier:t.carrier,requires:t.kind===`cvs`?`store`:`address`,fee:Math.floor(l),collect:t.id!==`ecpay-home-post`,maxAmount:null,...t.kind===`cvs`?{mapUrl:`/api/shop/ecpay/map?shipping=${encodeURIComponent(t.id)}&sbx=1`}:{}})}return a}const Y=(e,t)=>({ok:!0,response:{kind:`text`,status:e,body:t}});function Er(e){return`${e}L${w(Pe(3)).toUpperCase()}`.slice(0,20)}async function Dr(e,t){let n=new URLSearchParams(t.query);if(n.get(`sbx`)!==`1`)return{...Y(404,`not ours`),passThrough:!0};let r=sr(n.get(`shipping`)??``);if(!r||r.kind!==`cvs`||!await K(e)||!await yr(e,r))return Y(400,`不支援的運送方式。`);let i=await tn(e);if(!i)return Y(503,`尚未設定網站網址。`);let a=r.subtype[await q(e)],o=await G(e,U(a)),s=Er(`MAP`);await Ft(e,s,{methodId:r.id});let c={MerchantID:o.merchantId,MerchantTradeNo:s,LogisticsType:`CVS`,LogisticsSubType:a,IsCollection:n.get(`cod`)===`1`?`Y`:`N`,ServerReplyURL:`${i}/api/shop/ecpay/map-reply`,ExtraData:``,Device:n.get(`mobile`)===`1`?`1`:`0`};return c.CheckMacValue=await B(c,o.hashKey,o.hashIv,`md5`),{ok:!0,response:{kind:`form`,title:`正在開啟門市地圖…`,action:`${vr(o.stage)}/Express/map`,params:c}}}async function Or(e,t){let n=An(t.body),r=await It(e,n.MerchantTradeNo??``);if(!r)return{ok:!0,passThrough:!0,response:{kind:`text`,status:403,body:`Unknown trade`}};let i=sr(r.methodId??``),a=(n.CVSStoreID??``).trim();if(!i||!a||a.length>20)return{ok:!0,response:{kind:`redirect`,status:303,location:`/shop/checkout?store_error=1`}};let o={provider:er,type:n.LogisticsSubType||i.subtype[await q(e)],id:a,name:(n.CVSStoreName??``).slice(0,100),address:(n.CVSAddress??``).slice(0,200)};return{ok:!0,response:{kind:`redirect`,status:303,location:await an(e,i.id,o)}}}function kr(e,t){let n=t.includes(`normal`)?`normal`:t[0]??`normal`,r=new Map;for(let i of yt(e)){let a=i.temperature??e.shipping?.temperature??`normal`;t.includes(a)||(a=n);let o=r.get(a)??{temp:a,amount:0,names:[],weightKg:0};o.amount+=i.amount,o.names.push(i.name),o.weightKg+=(i.weightKg??0)*i.quantity,r.set(a,o)}let i=rr.filter(e=>r.has(e)).map(e=>r.get(e));return i[0]&&e.shippingFee>0&&(i[0].amount+=e.shippingFee),i}async function Ar(e,t){return(await W(e,`senderZipcode`)).replace(/\D/g,``)||(/^\s*(\d{3,6})/.exec(t)?.[1]??``)}function jr(e,t){let n=0,r=``;for(let i of e){let e=/[ᄀ-ᅟ⺀-가-힣豈-︰-﹏-⦆¢-₩]|[\u{20000}-\u{3fffd}]/u.test(i)?2:1;if(n+e>t)break;n+=e,r+=i}return r}async function Mr(e,t,n,r,i,a=new Date){let o=t.shipping,s=![`TCAT`,`POST`].includes(i),c=o.collect&&n.amount>0,l=s&&!c?Math.min(2e4,Math.max(1,n.amount)):Math.max(1,n.amount),u=await G(e,U(i)),d=await tn(e);if(!d)throw new A(`尚未設定網站網址(siteUrl),綠界無法回傳物流狀態。`);let f=await W(e,`senderAddress`),p={MerchantID:u.merchantId,MerchantTradeNo:r,MerchantTradeDate:Qn(a),LogisticsType:s?`CVS`:`HOME`,LogisticsSubType:i,GoodsAmount:String(l),IsCollection:c?`Y`:`N`,CollectionAmount:String(c?l:0),GoodsName:pr(n.names.join(`#`)),SenderName:jr(await W(e,`senderName`),10),SenderPhone:await W(e,`senderPhone`),SenderCellPhone:await W(e,`senderCellphone`),ReceiverName:[...(o.address?.name||t.customer.name).trim()].slice(0,10).join(``),ReceiverPhone:``,ReceiverCellPhone:t.customer.phone.replace(/\D/g,``),ReceiverEmail:t.email,TradeDesc:``,ServerReplyURL:`${d}/api/shop/ecpay/logistics-notify`,ClientReplyURL:``,LogisticsC2CReplyURL:``,Remark:``,PlatformID:``};if(s){if(!o.store?.id)throw new A(`訂單沒有取貨門市。`);p.ReceiverStoreID=o.store.id,p.ReturnStoreID=o.store.id}else{if(!o.address?.line)throw new A(`訂單沒有收件地址。`);p.SenderZipCode=await Ar(e,f),p.SenderAddress=f,p.ReceiverZipCode=o.address.zip??``,p.ReceiverAddress=`${o.address.city??``}${o.address.line}`.trim(),p.Temperature=`000${tr[n.temp]}`,p.Distance=`00`;let t=Number((await R(e,J))[o.methodId]?.spec??1);p.Specification=`000${t>=1&&t<=3?t:1}`,p.ScheduledPickupTime=`4`,p.ScheduledDeliveryTime=`4`,p.GoodsWeight=String(n.weightKg>0?Math.min(20,Math.max(1,Math.ceil(n.weightKg))):1)}return p.CheckMacValue=await B(p,u.hashKey,u.hashIv,`md5`),{payload:p,cred:u}}async function Nr(e,t,n){if(!e.http)throw new A(`沒有對外連線權限。`);let r;try{r=await e.http.fetch(t,{method:`POST`,headers:{"content-type":`application/x-www-form-urlencoded`},body:new URLSearchParams(n).toString()})}catch(e){throw new A(`無法連線綠界物流:${e instanceof Error?e.message:String(e)}`)}return r.text()}function Pr(e){return e.CVSPaymentNo?`${e.CVSPaymentNo}${e.CVSValidationNo??``}`:e.BookingNote||e.AllPayLogisticsID||null}const Fr=new Set([`300`,`2001`]);async function Ir(e,t,n){let r=t.shipping?sr(t.shipping.methodId):void 0;if(!t.shipping||!r)throw new A(`這筆訂單不是綠界物流的運送方式。`);let i=r.subtype[await q(e)],a=kr(t,await br(e,r));if(!a.length)throw new A(`這筆訂單沒有可出貨的商品。`);let o=new Set(n.map(e=>e.details.溫層)),s=a.length>1,c=[],l=[];for(let n of a){let a=nr[n.temp];if(o.has(a))continue;let u=cr(i,n.temp),d=U(u);if(!await _r(e,d)){l.push(`${a}(${d} 帳號)尚未設定金鑰`);continue}let f=s?`${Er(t.number).slice(0,18)}T${tr[n.temp]}`:Er(t.number),{payload:p,cred:m}=await Mr(e,t,n,f,u),h;try{h=await Nr(e,`${vr(m.stage)}/Express/Create`,p)}catch(e){l.push(`${a}物流單建立失敗:${e instanceof Error?e.message:String(e)}`);continue}let g=lr(h),_=g.fields;if(!g.ok||!Fr.has(_.RtnCode??``)){l.push(`${a}物流單建立失敗:${g.ok?`${_.RtnMsg??``}(狀態碼 ${_.RtnCode??`?`})`:g.message}`);continue}c.push({shipmentId:f,carrier:u===`UNIMARTFREEZE`?`7-ELEVEN 冷凍`:r.carrier,trackingNo:Pr(_),providerRef:_.AllPayLogisticsID||null,isCollection:t.shipping.collect,statusText:_.RtnMsg||`已建立物流單`,details:{子類型:u,溫層:a,綠界物流編號:_.AllPayLogisticsID??``,寄貨編號:_.CVSPaymentNo??``,驗證碼:_.CVSValidationNo??``,託運單號:_.BookingNote??``,金額:`NT$${p.GoodsAmount}${t.shipping.collect?`(代收)`:``}`}})}if(!c.length&&l.length)throw new A(l.join(` / `));return{shipments:c,errors:l}}async function Lr(e,t){let n=await G(e,U(t.details.子類型??`UNIMARTC2C`)),r={MerchantID:n.merchantId,AllPayLogisticsID:t.providerRef??``,TimeStamp:String(Math.floor(Date.now()/1e3))};r.CheckMacValue=await B(r,n.hashKey,n.hashIv,`md5`);let i=await ur(await Nr(e,`${vr(n.stage)}/Helper/QueryLogisticsTradeInfo/V2`,r),n.hashKey,n.hashIv),a=i.fields.LogisticsStatus;if(!i.ok||!a)throw new A(`查詢物流狀態失敗:${i.message||`no LogisticsStatus in response`}`);return{status:fr(a),statusText:i.fields.LogisticsStatusMsg||i.fields.RtnMsg||`狀態 ${a}`,statusCode:a}}function Rr(e){return jn(e).replace(/%2[Aa]/g,`*`).replace(/%21/g,`!`).replace(/%28/g,`(`).replace(/%29/g,`)`)}const zr=new Set([`UNIMARTC2C`,`UNIMART`,`UNIMARTFREEZE`,`POST`]);function Br(e,t){return t===`A6`&&zr.has(e)?2:1}async function Vr(e){return await W(e,`labelMode`)===`2`?`A6`:`A4`}async function Hr(e,t){let n=t.details.子類型??`UNIMARTC2C`;return Wr(e,n,[t.providerRef??``],Br(n,await Vr(e)))}async function Ur(e,t,n){let r=n??await Vr(e),i=new Map,a=[];for(let e of t){if(!e.active||!e.providerRef){a.push(`${e.shipmentId}:${e.active?`沒有綠界物流編號`:`物流單已作廢`}`);continue}let t=e.details.子類型??`UNIMARTC2C`;i.set(t,[...i.get(t)??[],e])}let o=[];for(let[t,n]of i){let i=Br(t,r);try{let{html:r}=await Wr(e,t,n.map(e=>e.providerRef),i);o.push({subtype:t,mode:i===2?`A6`:`A4`,shipmentIds:n.map(e=>e.shipmentId),html:r})}catch(e){a.push(`${t}:${e instanceof Error?e.message:String(e)}`)}}return{labels:o,errors:a}}async function Wr(e,t,n,r){if(!e.http)throw new A(`沒有對外連線權限。`);let i=await G(e,U(t)),a=JSON.stringify({MerchantID:i.merchantId,LogisticsID:n,LogisticsSubType:t,PrintMode:r}),o=JSON.stringify({MerchantID:i.merchantId,RqHeader:{Timestamp:Math.floor(Date.now()/1e3),Revision:`1.0.0`},Data:ke(await On(i.hashKey,i.hashIv,Rr(a),`pkcs7`))}),s;try{s=await e.http.fetch(`${vr(i.stage)}/Express/v2/PrintTradeDocument`,{method:`POST`,headers:{"content-type":`application/json`},body:o})}catch(e){throw new A(`無法連線綠界物流:${e instanceof Error?e.message:String(e)}`)}let c=await s.text();if(s.status!==200)throw new A(`ECPay HTTP ${s.status}: ${c.slice(0,200)}`);return{html:c}}const Gr={pluginId:er,owns:e=>!!sr(e),multi:!0,usable:K,create:Ir,sync:Lr,print:Hr};async function Kr(e,t){let n=An(t.body),r=await M(e).shipments.get(n.MerchantTradeNo??``);if(!r)return{...Y(404,`Order not found`),passThrough:!0};let i=await G(e,U(r.details.子類型||n.LogisticsSubType||`UNIMARTC2C`));if(!i.hashKey||!i.hashIv||(n.MerchantID??``)!==i.merchantId||!await Zn(n,i.hashKey,i.hashIv,`md5`))return Y(400,`CheckMacValue mismatch`);let a=n.RtnCode??``,o=await jt(e,r.shipmentId,{status:fr(a),statusText:n.RtnMsg||`狀態 ${a}`,statusCode:a,trackingNo:Pr(n),providerRef:n.AllPayLogisticsID||null});return{ok:!0,outcome:o.result,response:{kind:`text`,status:200,body:`1|OK`},reports:o.result!==`unknown`&&o.report?[o.report]:[]}}const qr={merchantId:`2000132`,hashKey:`ejCk326UnaZWKisg`,hashIv:`q9jcZX8Ib9LM8wYk`},Jr={stage:`einvoice-stage.ecpay.com.tw`,production:`einvoice.ecpay.com.tw`};async function Yr(e,t,n){return ke(await On(t,n,jn(JSON.stringify(e)),`pkcs7`))}async function Xr(e,t,n){let r=Ae(e);if(!r)return null;let i=await kn(t,n,r,`pkcs7`);if(!i)return null;try{return JSON.parse(decodeURIComponent(Oe(i).replace(/\+/g,` `)))}catch{return null}}async function Zr(e){let t=String(await e.settings.get(`invoiceMerchantId`)??``).trim(),n=await e.settings.get(`invoiceHashKey`)??``,r=await e.settings.get(`invoiceHashIv`)??``;return t&&n&&r?{merchantId:t,hashKey:n,hashIv:r,stage:await e.settings.get(`invoiceEnvironment`)!==`production`}:{...qr,stage:!0}}async function Qr(e){return await e.settings.get(`invoiceEnabled`)===!0}async function X(e,t,n){if(!e.http)throw new A(`沒有對外連線權限。`);let r=await Zr(e),i={MerchantID:r.merchantId,RqHeader:{Timestamp:Math.floor(Date.now()/1e3),Revision:`3.0.0`},Data:await Yr({MerchantID:r.merchantId,...n},r.hashKey,r.hashIv)},a;try{a=await e.http.fetch(`https://${r.stage?Jr.stage:Jr.production}/B2CInvoice/${t}`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify(i)})}catch(e){throw new A(`無法連線綠界電子發票:${e instanceof Error?e.message:String(e)}`)}let o=await a.json().catch(()=>null);if(!o||Number(o.TransCode)!==1||!o.Data)throw new A(`綠界發票 API 傳輸失敗:${o?.TransMsg??`HTTP ${a.status}`}`);let s=await Xr(o.Data,r.hashKey,r.hashIv);if(!s)throw new A(`綠界發票回應無法解密,請確認 HashKey/HashIV。`);return s}function $r(e,t){if(Number(e.RtnCode)!==1)throw new A(`${t}:${e.RtnMsg}(${e.RtnCode})`)}function ei(e,t,n){let r=[],i=0,a=(e,t,n,i,a)=>r.push({ItemSeq:r.length+1,ItemName:[...e].slice(0,100).join(``),ItemCount:t,ItemWord:n,ItemPrice:i,ItemTaxType:`1`,ItemAmount:a});for(let e of t)i+=e.amount,a(e.name,e.quantity,`批`,e.quantity>0?Math.round(e.amount/e.quantity):0,e.amount);return e.shippingFee>0&&(i+=e.shippingFee,a(e.shipping?.label||`運費`,1,`式`,e.shippingFee,e.shippingFee)),i!==n&&a(`其他`,1,`式`,n-i,n-i),r}function ti(e,t){return e===`company`||e===`donate`||e===`paper`?{CarrierType:``,CarrierNum:``}:e===`mobile`?{CarrierType:`3`,CarrierNum:t??``}:e===`certificate`?{CarrierType:`2`,CarrierNum:t??``}:{CarrierType:`1`,CarrierNum:``}}function ni(e,t){let n=e.invoice,r=n.kind===`company`&&!!n.taxId,i=n.kind===`donate`&&!!n.loveCode,a=r||n.kind===`paper`;if(a&&t.buyerAddress===`無`)throw new A(`列印發票(紙本或統編)需要買受人地址,這筆訂單沒有收件或門市地址。請補上地址後重新開立。`);let o=t.buyerAddress===`無`?``:t.buyerAddress;return{RelateNumber:t.relateNumber,CustomerID:``,CustomerIdentifier:r?n.taxId??``:``,CustomerName:[...(r?n.companyTitle:e.customer.name)||`消費者`].slice(0,60).join(``),CustomerAddr:[...o].slice(0,100).join(``),CustomerPhone:e.customer.phone,CustomerEmail:e.email,Print:a?`1`:`0`,Donation:i?`1`:`0`,LoveCode:i?n.loveCode??``:``,...ti(r||i?`company`:n.kind,n.carrierNum),TaxType:`1`,SalesAmount:t.amount,InvoiceRemark:``,Items:ei(e,t.discounted,t.amount),InvType:`07`,vat:`1`}}const ri=e=>(e.meta.invoiceDate??e.invoiceDate??``).slice(0,10);async function ii(e,t){let n=String(await e.settings.get(`invoicePrefix`)??``).replace(/[^A-Za-z0-9]/g,``).slice(0,5);return`${n?`${n}INV`:``}${t.number}R${w(Pe(4))}`.slice(0,30)}const Z={pluginId:er,kinds:[`member`,`mobile`,`certificate`,`paper`,`donate`,`company`],checkoutOptions:e=>Yt(e,Z.kinds),relateNumber:ii,async issue(e,t,n){let r=await X(e,`Issue`,ni(t,n));$r(r,`電子發票開立失敗`);let i=String(r.InvoiceDate??``);return{invoiceNo:String(r.InvoiceNo),invoiceDate:St(i),randomNumber:String(r.RandomNumber??``),meta:{invoiceDate:i}}},async void(e,t,n){$r(await X(e,`Invalid`,{InvoiceNo:t.invoiceNo,InvoiceDate:ri(t),Reason:n.slice(0,20)}),`作廢失敗`)},async allowance(e,t,n,r){let i=await X(e,`Allowance`,{InvoiceNo:t.invoiceNo,InvoiceDate:ri(t),AllowanceNotify:`E`,CustomerName:n.customer.name,NotifyMail:n.email,AllowanceAmount:r,Items:[{ItemSeq:1,ItemName:[...n.lines[0]?.name||`其他`].slice(0,100).join(``),ItemCount:1,ItemWord:`式`,ItemPrice:r,ItemTaxType:`1`,ItemAmount:r}]});return $r(i,`折讓開立失敗`),{allowanceNo:String(i.IA_Allow_No??i.AllowanceNo??``)}},async voidAllowance(e,t,n,r){$r(await X(e,`AllowanceInvalid`,{InvoiceNo:t.invoiceNo,AllowanceNo:n.allowanceNo,Reason:r.slice(0,20)}),`折讓作廢失敗`)},async query(e,t){return X(e,`GetIssue`,{RelateNumber:t.relateNumber})},async verifyOnline(e,t){if(t.kind===`donate`){let n=await X(e,`CheckLoveCode`,{LoveCode:t.loveCode});if(Number(n.RtnCode)===1&&n.IsExist!==`Y`)return`查無此捐贈碼。`}else if(t.kind===`mobile`){let n=await X(e,`CheckBarcode`,{BarCode:t.carrierNum});if(Number(n.RtnCode)===1&&n.IsExist!==`Y`)return`查無此手機條碼,請確認後再輸入。`}return null}},Q={Credit:`信用卡`,Credit3:`信用卡分 3 期`,Credit6:`信用卡分 6 期`,Credit12:`信用卡分 12 期`,Credit18:`信用卡分 18 期`,Credit24:`信用卡分 24 期`,Credit30N:`信用卡分 30 期(永豐)`,UnionPay:`銀聯卡`,ATM:`ATM 轉帳`,CVS:`超商代碼繳費`,BARCODE:`超商條碼繳費`,WebATM:`網路 ATM`,ApplePay:`Apple Pay`,TWQR:`TWQR 行動支付`,BNPL:`無卡分期(裕富/中租)`,WeiXin:`微信支付`,Jkopay:`街口支付`,iPASS:`一卡通 iPASS MONEY`,ALL:`綠界付款頁自選`},ai=Object.keys(Q),oi={Credit:{choose:`Credit`,card:!0},Credit3:{choose:`Credit`,installment:3,card:!0},Credit6:{choose:`Credit`,installment:6,card:!0},Credit12:{choose:`Credit`,installment:12,card:!0},Credit18:{choose:`Credit`,installment:18,card:!0},Credit24:{choose:`Credit`,installment:24,card:!0},Credit30N:{choose:`Credit`,installment:`30N`,card:!0},UnionPay:{choose:`Credit`,unionPay:!0,card:!0},ATM:{choose:`ATM`,offline:!0,max:49999},CVS:{choose:`CVS`,offline:!0,max:2e4},BARCODE:{choose:`BARCODE`,offline:!0,max:2e4},WebATM:{choose:`WebATM`},ApplePay:{choose:`ApplePay`,card:!0},TWQR:{choose:`TWQR`,min:6,max:49999},BNPL:{choose:`BNPL`,min:50,max:5e5},WeiXin:{choose:`WeiXin`,min:6,max:5e5},Jkopay:{choose:`DigitalPayment`,sub:`Jkopay`},iPASS:{choose:`DigitalPayment`,sub:`iPASS`},ALL:{choose:`ALL`}};function si(e){if(!e)return``;let t={Credit_CreditCard:`信用卡`,Flexible_Installment:`信用卡分期(永豐 30 期)`,BARCODE_BARCODE:`超商條碼`,CVS_CVS:`超商代碼`,TWQR_OPAY:`TWQR 行動支付`,WeiXin_OPAY:`微信支付`,BNPL_URICH:`無卡分期(裕富)`,BNPL_ZINGALA:`無卡分期(中租)`,DigitalPayment_Jkopay:`街口支付`,DigitalPayment_IPASS:`一卡通 iPASS MONEY`,ApplePay:`Apple Pay`};if(t[e])return t[e];let n=[[`Credit_`,`信用卡`],[`WebATM_`,`網路 ATM`],[`ATM_`,`ATM 轉帳`],[`CVS_`,`超商代碼`],[`BNPL_`,`無卡分期`],[`DigitalPayment_`,`電子支付`],[`ApplePay`,`Apple Pay`]].find(([t])=>e.startsWith(t));return n?`${n[1]}(${e})`:e}const ci=`/api/shop/ecpay/notify`,li=new Set([`2`,`10100073`]);async function $(e){let t=await e.settings.get(`environment`)===`production`?`production`:`stage`,n=(await e.settings.get(`merchantId`)??``).trim(),r=await e.settings.get(`hashKey`)??``,i=await e.settings.get(`hashIv`)??``;return n&&r&&i?{environment:t,merchantId:n,hashKey:r,hashIv:i}:t===`stage`?{environment:t,...qn}:null}async function ui(e,t){let n=[];for(let r of ai){let i=await e.settings.get(`method${r}`);if(!(i===!0||i==null&&r===`Credit`))continue;let a=oi[r];if(!(t!=null&&(a.min!=null&&t<a.min||a.max!=null&&t>a.max))){if(r===`Credit30N`){let n=await di(e);if(t==null?n===1/0:!(t>n))continue}n.push(r)}}return n}async function di(e){let t=Number(await e.settings.get(`credit30NMinAmount`));return Number.isFinite(t)&&t>0?t:1/0}const fi=ai.map(e=>({code:e,label:Q[e]}));async function pi(e,t,n,r){let i=Math.floor(Number(await e.settings.get(t)));return Number.isFinite(i)&&i>=1?Math.min(i,r):n}async function mi(e){return String(await e.settings.get(`orderPrefix`)??``).replace(/[^A-Za-z0-9]/g,``).slice(0,5)}async function hi(e,t,n){let r=oi[n.method],i=String(await e.settings.get(`itemName`)??``).trim(),a={MerchantID:t.merchantId,MerchantTradeNo:n.merchantTradeNo,MerchantTradeDate:Qn(n.now),PaymentType:`aio`,TotalAmount:String(Math.round(n.amount)),TradeDesc:`線上購物`,ItemName:$n(i?[i]:n.itemNames),ReturnURL:`${n.origin}${ci}`,ChoosePayment:r.choose,EncryptType:`1`,ClientBackURL:n.orderUrl,NeedExtraPaidInfo:`Y`};return r.sub&&(a.ChooseSubPayment=r.sub),r.installment&&(a.CreditInstallment=String(r.installment)),r.unionPay&&(a.UnionPay=`1`),n.method===`Credit`&&await e.settings.get(`creditRedeem`)===!0&&(a.Redeem=`Y`),r.card||(a.PaymentInfoURL=`${n.origin}${ci}`),(n.method===`ATM`||n.method===`ALL`)&&(a.ExpireDate=String(await pi(e,`atmExpireDays`,3,60))),n.method===`CVS`&&(a.StoreExpireDate=String(Math.min(43200,await pi(e,`cvsExpireDays`,7,30)*1440))),n.method===`BARCODE`&&(a.StoreExpireDate=String(await pi(e,`barcodeExpireDays`,7,30))),r.offline||(a.OrderResultURL=`${n.origin}/api/shop/gw/ecpay/return`),a.CheckMacValue=await B(a,t.hashKey,t.hashIv),{action:`https://${Jn[t.environment]}/Cashier/AioCheckOut/V5`,params:a}}const gi=[`BankCode`,`vAccount`,`PaymentNo`,`ExpireDate`,`Barcode1`,`Barcode2`,`Barcode3`,`BNPLTradeNo`,`BNPLInstallment`],_i=[`card4no`,`card6no`,`gwsr`,`auth_code`,`stage`,`process_date`];async function vi(e,t){let n=An(t),r=await $(e);if(!r)return et(`not_configured`,`0|Not configured`,503);if(!await Zn(n,r.hashKey,r.hashIv))return et(`bad_checkmac`,`0|CheckMacValue Error`,400);if(n.MerchantID!==r.merchantId)return et(`bad_merchant`,`0|MerchantID Error`,400);let i=n.MerchantTradeNo??``,a=await O(e).trades.get(i);if(!a)return et(`unknown_trade`,`0|Unknown MerchantTradeNo`,404);let o={ok:!0,reply:`1|OK`,httpStatus:200,orderNumber:a.orderNumber},s=n.SimulatePaid===`1`;if(s&&r.environment===`production`)return{...o,outcome:`ignored_simulated`};let c=n.RtnCode??``,l=n.TradeNo??``;if(c===`1`){let t=Number(n.TradeAmt)===a.amount,r={};for(let e of _i)n[e]&&(r[e]=n[e]);return await rt(e,i,{status:t?`paid`:`failed`,tradeNo:l,paymentType:n.PaymentType,resultCode:c,resultMessage:t?n.RtnMsg:`amount_mismatch:${n.TradeAmt}`,simulated:s,...Object.keys(r).length?{info:r}:{}}),{...o,outcome:t?`paid`:`amount_mismatch`,event:{eventId:`ecpay:${l||i}:paid`,provider:`ecpay`,orderNumber:a.orderNumber,amount:Number(n.TradeAmt),status:`paid`,tradeNo:l,simulated:s}}}if(li.has(c)){let t={PaymentType:n.PaymentType??``};for(let e of gi)n[e]&&(t[e]=n[e]);if((await rt(e,i,{status:`awaiting`,tradeNo:l,paymentType:n.PaymentType,resultCode:c,resultMessage:n.RtnMsg,info:t}))?.status===`paid`)return{...o,outcome:`already_paid`};let r=(n.PaymentType??``).startsWith(`BNPL`)||a.method===`BNPL`;return{...o,outcome:`payment_info`,paymentInfo:{eventId:`ecpay:${l||i}:info`,provider:`ecpay`,orderNumber:a.orderNumber,merchantTradeNo:i,tradeNo:l,info:t,expiresAt:r?new Date(Date.now()+6048e5).toISOString():ut(n.ExpireDate)}}}return(await rt(e,i,{status:`failed`,tradeNo:l,paymentType:n.PaymentType,resultCode:c,resultMessage:n.RtnMsg}))?.status===`paid`?{...o,outcome:`already_paid`}:{...o,outcome:`failed`,event:{eventId:`ecpay:${l||i}:rtn${c}`,provider:`ecpay`,orderNumber:a.orderNumber,amount:Number(n.TradeAmt)||0,status:`failed`,tradeNo:l,simulated:s}}}async function yi(e,t,n){if(!e.http)throw Error(`NO_NETWORK`);let r=await e.http.fetch(t,{method:`POST`,headers:{"content-type":`application/x-www-form-urlencoded`},body:new URLSearchParams(n).toString()});return{status:r.status,reply:An((await r.text()).trim())}}async function bi(e,t,n){let r={MerchantID:t.merchantId,MerchantTradeNo:n,TimeStamp:String(Math.floor(Date.now()/1e3))};r.CheckMacValue=await B(r,t.hashKey,t.hashIv);let{status:i,reply:a}=await yi(e,`https://${Jn[t.environment]}/Cashier/QueryTradeInfo/V5`,r),o=await Zn(a,t.hashKey,t.hashIv);return{ok:i>=200&&i<300,status:i,verified:o,tradeStatus:a.TradeStatus??null,tradeAmt:a.TradeAmt??null,merchantTradeNo:a.MerchantTradeNo??null,paymentType:a.PaymentType??null}}const xi=e=>/^(Credit_|ApplePay)/.test(e.paymentType??``)||!!(oi[e.method]?.card&&!e.paymentType),Si=e=>!!oi[e.method]?.installment||Number(e.info?.stage??0)>0||e.paymentType===`Flexible_Installment`;async function Ci(e,t,n,r,i){let a={MerchantID:t.merchantId,MerchantTradeNo:n.merchantTradeNo,TradeNo:n.tradeNo??``,Action:r,TotalAmount:String(i)};a.CheckMacValue=await B(a,t.hashKey,t.hashIv);let{status:o,reply:s}=await yi(e,`https://${Jn[t.environment]}/CreditDetail/DoAction`,a);return{ok:s.RtnCode===`1`,code:s.RtnCode??``,message:s.RtnMsg??`HTTP ${o}`}}async function wi(e,t,n){if(!xi(t))return{ok:!1,manual:!0,message:`這筆以 ${si(t.paymentType)||`非信用卡`} 付款,綠界沒有退款 API,請在綠界(或該支付的)廠商後台退款,或以轉帳等方式人工退款。`};let r=t.amount-(t.refunded??0);if(n>r)return{ok:!1,message:`退款金額超過可退餘額 NT$${r}。`};if(Si(t)&&n!==r)return{ok:!1,message:`分期付款的交易只能全額退刷(NT$${r})。`};let i=await $(e);if(!i)return{ok:!1,message:`綠界金流未設定。`};let a=await Ci(e,i,t,`R`,n);if(a.ok)return{ok:!0,action:`refund`,message:`綠界信用卡退刷成功(NT$${n})`};if(n===r){let r=await Ci(e,i,t,`N`,n);return r.ok?{ok:!0,action:`release`,message:`綠界信用卡授權已取消(尚未請款)`}:{ok:!1,message:`綠界退刷失敗:${a.message};取消授權也失敗:${r.message}`}}return{ok:!1,message:`綠界退刷失敗:${a.message}(${a.code})。尚未請款的交易只能全額取消授權。`}}async function Ti(e,t){if(t.status!==`paid`||!xi(t))return{ok:!1,message:`只有已付款的信用卡交易可以請款。`};let n=await $(e);if(!n)return{ok:!1,message:`綠界金流未設定。`};let r=t.amount-(t.refunded??0),i=await Ci(e,n,t,`C`,r);return i.ok?{ok:!0,message:`綠界信用卡請款(關帳)成功(NT$${r})`}:{ok:!1,code:i.code,message:`綠界請款失敗:${i.message}(${i.code})`}}const Ei={stage:`ecpayment-stage.ecpay.com.tw`,production:`ecpayment.ecpay.com.tw`},Di={Authorized:`已授權(尚未請款)`,"To be captured":`請款中`,Captured:`已請款`,Refunded:`已退刷`,Cancelled:`已取消授權`,Failed:`失敗`};async function Oi(e,t){if(!xi(t))return{ok:!1,error:`NOT_CARD`,message:`這筆不是信用卡交易。`};let n=await $(e);if(!n)return{ok:!1,error:`NOT_CONFIGURED`,message:`綠界金流未設定。`};if(!e.http)return{ok:!1,error:`NO_NETWORK`,message:`沒有網路權限。`};let r={MerchantID:n.merchantId,RqHeader:{Timestamp:Math.floor(Date.now()/1e3)},Data:await Yr({MerchantID:n.merchantId,MerchantTradeNo:t.merchantTradeNo},n.hashKey,n.hashIv)},i=await e.http.fetch(`https://${Ei[n.environment]}/1.0.0/CreditDetail/QueryTrade`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify(r)}),a={};try{a=await i.json()}catch{return{ok:!1,error:`BAD_REPLY`,message:`綠界回應無法解析(HTTP ${i.status})`}}if(Number(a.TransCode)!==1)return{ok:!1,error:`ECPAY`,message:`綠界查詢失敗:${String(a.TransMsg??`HTTP ${i.status}`)}`};let o=await Xr(String(a.Data??``),n.hashKey,n.hashIv),s=o?.RtnValue;if(!s)return{ok:!1,error:`ECPAY`,message:`綠界查詢失敗:${o?.RtnMsg||`沒有資料`}`};let c=String(s.Status??``);return{ok:!0,status:c,statusLabel:Di[c]??c,amount:Number(s.Amount)||0,captured:Number(s.ClsAmt)||0,authorizedAt:String(s.AuthTime??``),history:(o?.CloseData??[]).map(e=>({status:String(e.Status??``),amount:Number(e.Amount)||0,at:String(e.DateTime??``)}))}}const ki=`logistics-reconcile`,Ai=p({shipmentIds:f(i({min:1,max:64}),{min:1,max:50}),mode:h(u([`A4`,`A6`]))}),ji=i({min:1,max:18,pattern:/^[A-Z0-9]+$/}),Mi=i({min:1,max:20,pattern:/^[A-Za-z0-9]+$/}),Ni=p({amount:h(o({min:0}))}),Pi=p({orderNumber:ji,amount:o({min:1,max:99999999}),itemNames:f(i({min:1,max:200}),{min:1,max:100}),method:g(u(ai),`Credit`),origin:i({max:200,pattern:dt}),orderUrl:i({min:8,max:400,pattern:ft}),email:h(i({max:120}))}),Fi=p({id:i({min:1,max:200}),type:i({min:1,max:60}),payload:m(c(),{maxKeys:50})}),Ii=p({orderNumber:ji,amount:o({min:1,max:99999999})}),Li=p({orderNumber:h(ji),limit:g(o({min:1,max:100,coerce:!0}),20)}),Ri=p({merchantTradeNo:Mi}),zi=p({merchantTradeNo:Mi,amount:o({min:1,max:99999999,coerce:!0})}),Bi={created:`待付款`,awaiting:`待繳費`,paid:`已付款`,failed:`未成功`};async function Vi(e){let{trades:t}=O(e),n=await $(e),[r,i,a]=await Promise.all([t.query({orderBy:{createdAt:`desc`},limit:30}),t.count({status:`paid`}),ui(e)]);return{blocks:[{type:`header`,text:`綠界金流`},{type:`stats`,items:[{label:`環境`,value:n?n.environment===`production`?`正式`:`測試`:`未設定`},{label:`特店編號`,value:n?.merchantId??`—`},{label:`開放的付款方式`,value:a.map(e=>Q[e]).join(`、`)||`無`},{label:`已付款交易`,value:String(i)}]},{type:`table`,columns:[{key:`merchantTradeNo`,label:`綠界訂單編號`},{key:`orderNumber`,label:`訂單`},{key:`method`,label:`付款方式`},{key:`amount`,label:`金額`},{key:`status`,label:`狀態`},{key:`tradeNo`,label:`綠界交易序號`}],rows:r.items.map(({data:e})=>({merchantTradeNo:e.merchantTradeNo,orderNumber:e.orderNumber,method:[Q[e.method]??e.method??``,e.paymentType?si(e.paymentType):``,e.info?.card4no?`末四碼 ${e.info.card4no}`:``].filter(Boolean).join(`/`),amount:`NT$${e.amount}${e.refunded?`(已退 NT$${e.refunded})`:``}`,status:e.status===`paid`&&e.simulated?`已付款(模擬)`:Bi[e.status]??e.status,tradeNo:e.tradeNo??``})),page_action_id:`browse_trades`,empty_text:`尚無交易。`}]}}async function Hi(e,t,n){let r=Vn(n);if(r){let t=await Ln(e,`綠界金流`,fi,r.actionId,r.values)??await Bn(e,J,xr,r.actionId,r.values);if(t)return t}if(t===`/payment-labels`)return In(`綠界金流`,fi,await L(e,ai));if(t===`/shipping-methods`)return z(J,xr,await R(e,J));if(t===`/shipments`){let t=await G(e,`C2C`);return vn(e,`綠界物流`,[{label:`物流`,value:await K(e)?`已啟用`:`未啟用`},{label:`環境`,value:t.stage?`測試`:`正式`},{label:`合約`,value:await e.settings.get(`logisticsContract`)===`B2C`?`B2C 大宗寄倉`:`C2C 店到店`}])}if(t===`/invoices`){let t=await Zr(e);return yn(e,`綠界電子發票`,[{label:`電子發票`,value:await Qr(e)?`已啟用`:`未啟用`},{label:`環境`,value:t.stage?`測試`:`正式`},{label:`特店編號`,value:t.merchantId}])}return Vi(e)}async function Ui(e,t,n){let r=await it(e,t);return r?ot(r.merchantTradeNo,await wi(e,r,n),()=>at(e,r.merchantTradeNo,n)):{ok:!1,error:`NOT_PAID`,detail:`這筆訂單沒有已付款的綠界交易。`}}const Wi={hooks:{"plugin:activate":async(e,t)=>{await t.cron?.schedule(ki,{schedule:`*/30 * * * *`})},cron:async(e,t)=>{if(e.name!==ki)return;let n=await Qt(t,Z);if(n.length&&t.log.info(`issued delayed ECPay invoices`,{n:n.length}),!await K(t))return;let r=await zt(t,Gr);r&&t.log.info(`synced ECPay shipments`,{n:r})}},routes:{...xn([`trades`,`orders`,`shipments`,`invoices`]),"bus/methods":D(`bus/methods`,Ni,async(e,t)=>{if(!await $(t))return{ok:!0,provider:`ecpay`,methods:[]};let n=await ui(t,e.amount),r=await L(t,ai);return{ok:!0,provider:`ecpay`,label:`綠界金流`,methods:n.map(e=>Pn(r,e,Q[e]))}}),"bus/checkout":D(`bus/checkout`,Pi,async(e,t)=>{let n=await $(t);if(!n)return{ok:!1,error:`NOT_CONFIGURED`};if(!(await ui(t,e.amount)).includes(e.method))return{ok:!1,error:`METHOD_DISABLED`};let r=await mi(t);if(r.length+e.orderNumber.length+2>20)return{ok:!1,error:`TRADE_NO_TOO_LONG`,detail:`訂單編號加上前綴超過綠界的 20 字上限,請縮短「訂單編號前綴」。`};let i;try{i=await nt(t,e.orderNumber,e.amount,e.method,r)}catch(e){return{ok:!1,error:e.message}}return{ok:!0,...await hi(t,n,{merchantTradeNo:i.merchantTradeNo,amount:e.amount,itemNames:e.itemNames,method:e.method,origin:e.origin,orderUrl:e.orderUrl}),merchantTradeNo:i.merchantTradeNo}}),"bus/notify":Ge(`bus/notify`,async(e,t)=>vi(t,e)),[x.gateway(`return`)]:D(x.gateway(`return`),we,async(e,t)=>tt(await vi(t,e.body))),"bus/events":D(`bus/events`,Fi,async(e,t)=>st(t,e,(e,t)=>$t(e,t,{logistics:Gr,invoice:Z}),{provider:`ecpay`,refund:Ui})),...$e(),"bus/shipping/methods":D(`bus/shipping/methods`,rn,async(e,t)=>({ok:!0,provider:`moksa-ecpay`,methods:await Tr(t,e.temperatures)})),...hn(Z,`綠界電子發票`,Qr),[x.gateway(`logistics-map`)]:D(x.gateway(`logistics-map`),we,async(e,t)=>(await nn(t,e.origin),Dr(t,e))),[x.gateway(`logistics-map-reply`)]:D(x.gateway(`logistics-map-reply`),we,async(e,t)=>Or(t,e)),[x.gateway(`logistics-notify`)]:D(x.gateway(`logistics-notify`),we,async(e,t)=>(await nn(t,e.origin),Kr(t,e))),"bus/refund":D(`bus/refund`,Ii,async(e,t)=>Ui(t,e.orderNumber,e.amount)),"trades/list":k(Li,async(e,t)=>({ok:!0,trades:(await O(t).trades.query({where:e.orderNumber?{orderNumber:e.orderNumber}:void 0,orderBy:{createdAt:`desc`},limit:e.limit})).items.map(e=>e.data)})),"trades/query":k(Ri,async(e,t)=>{let n=await $(t);return n?t.http?bi(t,n,e.merchantTradeNo):{ok:!1,error:`NO_NETWORK`}:{ok:!1,error:`NOT_CONFIGURED`}}),"trades/refund":k(zi,async(e,t)=>{let n=await O(t).trades.get(e.merchantTradeNo);if(!n||n.status!==`paid`)return{ok:!1,error:`NOT_PAID`};let r=await Ui(t,n.orderNumber,e.amount);if(!r.ok)return r;let i=await ct(t,{provider:`ecpay`,refundId:`${n.merchantTradeNo}:${(n.refunded??0)+e.amount}`,orderNumber:n.orderNumber,amount:e.amount,message:r.message});return{...r,reports:[i]}},[`POST`]),"trades/capture":k(Ri,async(e,t)=>{let n=await O(t).trades.get(e.merchantTradeNo);if(!n)return{ok:!1,error:`NOT_FOUND`};let r=await Ti(t,n);return r.ok?{ok:!0,message:r.message}:{ok:!1,error:`CAPTURE_FAILED`,detail:r.message}},[`POST`]),"trades/card-status":k(Ri,async(e,t)=>{let n=await O(t).trades.get(e.merchantTradeNo);if(!n)return{ok:!1,error:`NOT_FOUND`};let r=await Oi(t,n);return r.ok?r:{ok:!1,error:r.error,detail:r.message}}),...fn(Gr),"shipments/print-batch":k(Ai,async(e,t)=>{let n=await Promise.all([...new Set(e.shipmentIds)].map(async e=>[e,await M(t).shipments.get(e)])),r=n.filter(([,e])=>!e).map(([e])=>`${e}:找不到物流單`),i=await Ur(t,n.flatMap(([,e])=>e?[e]:[]),e.mode),a=[...r,...i.errors];return i.labels.length?{ok:!0,labels:i.labels,errors:a}:{ok:!1,error:`NOTHING_PRINTED`,detail:a.join(` / `)}}),...pn(Z),admin:{permission:`plugins:manage`,handler:async(e,t)=>Hi(t,String(e.input?.page??``),e.input)}}};export{Wi as default};
|
|
@@ -0,0 +1,194 @@
|
|
|
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
|
+
"description": "綠界:信用卡、ATM、超商代碼與條碼付款及退款,超商取貨與宅配,以及 B2C 電子發票,需搭配 @moksa/emdash-shop 網站整合套件。",
|
|
11
|
+
|
|
12
|
+
// Only ECPay's payment, logistics and e-invoice hosts; credentials stay in this plugin's encrypted settings.
|
|
13
|
+
"capabilities": ["network:request"],
|
|
14
|
+
"allowedHosts": [
|
|
15
|
+
"payment-stage.ecpay.com.tw",
|
|
16
|
+
"payment.ecpay.com.tw",
|
|
17
|
+
"ecpayment-stage.ecpay.com.tw",
|
|
18
|
+
"ecpayment.ecpay.com.tw",
|
|
19
|
+
"logistics-stage.ecpay.com.tw",
|
|
20
|
+
"logistics.ecpay.com.tw",
|
|
21
|
+
"einvoice-stage.ecpay.com.tw",
|
|
22
|
+
"einvoice.ecpay.com.tw"
|
|
23
|
+
],
|
|
24
|
+
|
|
25
|
+
"storage": {
|
|
26
|
+
"trades": { "indexes": ["orderNumber", "status", "createdAt", ["orderNumber", "createdAt"]] },
|
|
27
|
+
"events": { "indexes": ["createdAt"] },
|
|
28
|
+
// Logistics / e-invoice (@moksa/sandbox-kit/fulfillment): snapshots of orders this plugin ships or invoices.
|
|
29
|
+
"orders": { "indexes": ["updatedAt", "invoiceDueAt"] },
|
|
30
|
+
"shipments": { "indexes": ["orderNumber", "status", "updatedAt", ["open", "updatedAt"], ["orderNumber", "updatedAt"]] },
|
|
31
|
+
"invoices": { "indexes": ["status", "updatedAt"] },
|
|
32
|
+
"reports": { "indexes": ["status", "nextAt", ["status", "nextAt"]] }
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
"admin": {
|
|
36
|
+
"pages": [
|
|
37
|
+
{ "path": "/trades", "label": "綠界金流", "icon": "credit-card" },
|
|
38
|
+
{ "path": "/payment-labels", "label": "付款方式名稱與說明", "icon": "list" },
|
|
39
|
+
{ "path": "/shipments", "label": "綠界物流", "icon": "truck" },
|
|
40
|
+
{ "path": "/shipping-methods", "label": "物流方式設定", "icon": "list" },
|
|
41
|
+
{ "path": "/invoices", "label": "綠界電子發票", "icon": "receipt" }
|
|
42
|
+
],
|
|
43
|
+
"settingsSchema": {
|
|
44
|
+
"environment": {
|
|
45
|
+
"label": "環境",
|
|
46
|
+
"type": "select",
|
|
47
|
+
"options": [
|
|
48
|
+
{ "value": "stage", "label": "測試環境(stage)" },
|
|
49
|
+
{ "value": "production", "label": "正式環境" }
|
|
50
|
+
],
|
|
51
|
+
"default": "stage"
|
|
52
|
+
},
|
|
53
|
+
"merchantId": {
|
|
54
|
+
"label": "特店編號(MerchantID)",
|
|
55
|
+
"type": "string",
|
|
56
|
+
"default": "",
|
|
57
|
+
"description": "測試環境留空時使用綠界公開測試帳號 3002607。"
|
|
58
|
+
},
|
|
59
|
+
"hashKey": { "label": "HashKey", "type": "secret" },
|
|
60
|
+
"hashIv": { "label": "HashIV", "type": "secret" },
|
|
61
|
+
"methodCredit": { "label": "信用卡", "type": "boolean", "default": true },
|
|
62
|
+
"methodCredit3": { "label": "信用卡分 3 期(需向綠界申請)", "type": "boolean", "default": false },
|
|
63
|
+
"methodCredit6": { "label": "信用卡分 6 期(需向綠界申請)", "type": "boolean", "default": false },
|
|
64
|
+
"methodCredit12": { "label": "信用卡分 12 期(需向綠界申請)", "type": "boolean", "default": false },
|
|
65
|
+
"methodCredit18": { "label": "信用卡分 18 期(需向綠界申請)", "type": "boolean", "default": false },
|
|
66
|
+
"methodCredit24": { "label": "信用卡分 24 期(需向綠界申請)", "type": "boolean", "default": false },
|
|
67
|
+
"methodCredit30N": { "label": "信用卡分 30 期(永豐,需向綠界申請)", "type": "boolean", "default": false },
|
|
68
|
+
"credit30NMinAmount": { "label": "永豐 30 期最低交易金額", "type": "number", "default": 0, "min": 0, "description": "綠界廠商後台可查;訂單金額大於這個金額才提供 30 期。0 表示不提供。" },
|
|
69
|
+
"methodUnionPay": { "label": "銀聯卡(需向綠界申請)", "type": "boolean", "default": false },
|
|
70
|
+
"creditRedeem": { "label": "信用卡紅利折抵(需向綠界申請)", "type": "boolean", "default": false, "description": "一次付清的信用卡付款頁提供紅利折抵;分期與銀聯卡不適用。" },
|
|
71
|
+
"methodATM": { "label": "ATM 轉帳", "type": "boolean", "default": false },
|
|
72
|
+
"methodCVS": { "label": "超商代碼繳費", "type": "boolean", "default": false },
|
|
73
|
+
"methodBARCODE": { "label": "超商條碼繳費", "type": "boolean", "default": false },
|
|
74
|
+
"methodWebATM": { "label": "網路 ATM(手機版不支援)", "type": "boolean", "default": false },
|
|
75
|
+
"methodApplePay": { "label": "Apple Pay", "type": "boolean", "default": false },
|
|
76
|
+
"methodTWQR": { "label": "TWQR 行動支付(需申請,6–49,999 元)", "type": "boolean", "default": false },
|
|
77
|
+
"methodBNPL": { "label": "無卡分期:裕富/中租(需申請,50–500,000 元)", "type": "boolean", "default": false },
|
|
78
|
+
"methodWeiXin": { "label": "微信支付(需申請,6–500,000 元)", "type": "boolean", "default": false },
|
|
79
|
+
"methodJkopay": { "label": "街口支付(需申請)", "type": "boolean", "default": false },
|
|
80
|
+
"methodiPASS": { "label": "一卡通 iPASS MONEY(需申請)", "type": "boolean", "default": false },
|
|
81
|
+
"methodALL": { "label": "綠界付款頁自選", "type": "boolean", "default": false },
|
|
82
|
+
"atmExpireDays": { "label": "ATM 繳費期限(天)", "type": "number", "default": 3, "min": 1, "max": 60 },
|
|
83
|
+
"cvsExpireDays": { "label": "超商代碼繳費期限(天,最多 30)", "type": "number", "default": 7, "min": 1, "max": 30 },
|
|
84
|
+
"barcodeExpireDays": { "label": "超商條碼繳費期限(天,最多 30)", "type": "number", "default": 7, "min": 1, "max": 30 },
|
|
85
|
+
"orderPrefix": { "label": "訂單編號前綴(最多 5 個英數字)", "type": "string", "default": "", "description": "多個商店共用同一個綠界帳號時,用前綴區分訂單編號。" },
|
|
86
|
+
"itemName": { "label": "付款頁顯示的商品名稱", "type": "string", "default": "", "description": "留空時列出訂單的商品;填寫固定文字(例如「網路商品」)時每筆訂單都顯示這段文字。" },
|
|
87
|
+
"siteUrl": { "label": "網站網址", "type": "string", "default": "", "description": "例:https://shop.example.com。物流狀態回呼使用;留空時沿用網站最近一次轉來的網址。" },
|
|
88
|
+
|
|
89
|
+
"logisticsEnabled": { "label": "啟用綠界物流", "type": "boolean", "default": false },
|
|
90
|
+
"logisticsEnvironment": {
|
|
91
|
+
"label": "物流環境",
|
|
92
|
+
"type": "select",
|
|
93
|
+
"options": [
|
|
94
|
+
{ "value": "stage", "label": "測試環境(stage)" },
|
|
95
|
+
{ "value": "production", "label": "正式環境" }
|
|
96
|
+
],
|
|
97
|
+
"default": "stage"
|
|
98
|
+
},
|
|
99
|
+
"logisticsContract": {
|
|
100
|
+
"label": "超商合約類型",
|
|
101
|
+
"type": "select",
|
|
102
|
+
"options": [
|
|
103
|
+
{ "value": "C2C", "label": "C2C 店到店" },
|
|
104
|
+
{ "value": "B2C", "label": "B2C 大宗寄倉" }
|
|
105
|
+
],
|
|
106
|
+
"default": "C2C"
|
|
107
|
+
},
|
|
108
|
+
"logisticsC2cMerchantId": { "label": "C2C 物流特店編號", "type": "string", "default": "", "description": "店到店(交貨便)用。測試環境未填時使用綠界公開測試帳號 2000933。" },
|
|
109
|
+
"logisticsC2cHashKey": { "label": "C2C 物流 HashKey", "type": "secret" },
|
|
110
|
+
"logisticsC2cHashIv": { "label": "C2C 物流 HashIV", "type": "secret" },
|
|
111
|
+
"logisticsB2cMerchantId": { "label": "B2C/宅配物流特店編號", "type": "string", "default": "", "description": "大宗寄倉、黑貓、郵局用。測試環境未填時使用綠界公開測試帳號 2000132。" },
|
|
112
|
+
"logisticsB2cHashKey": { "label": "B2C/宅配物流 HashKey", "type": "secret" },
|
|
113
|
+
"logisticsB2cHashIv": { "label": "B2C/宅配物流 HashIV", "type": "secret" },
|
|
114
|
+
"cvsUnimart": { "label": "7-ELEVEN 取貨(B2C 可含冷凍,另拆一張冷凍單)", "type": "boolean", "default": true },
|
|
115
|
+
"cvsFami": { "label": "全家取貨", "type": "boolean", "default": true },
|
|
116
|
+
"cvsHilife": { "label": "萊爾富取貨", "type": "boolean", "default": false },
|
|
117
|
+
"cvsOkmart": { "label": "OK 取貨", "type": "boolean", "default": false },
|
|
118
|
+
"cvsUnimartFreeze": { "label": "7-ELEVEN 冷凍取貨(僅 B2C)", "type": "boolean", "default": false },
|
|
119
|
+
"homeTcat": { "label": "黑貓宅急便(常溫/冷藏/冷凍,依溫層拆單)", "type": "boolean", "default": true },
|
|
120
|
+
"homePost": { "label": "中華郵政(僅常溫)", "type": "boolean", "default": false },
|
|
121
|
+
"cvsFee": { "label": "超商取貨運費", "type": "number", "default": 60, "min": 0 },
|
|
122
|
+
"homeFee": { "label": "宅配運費(常溫)", "type": "number", "default": 100, "min": 0 },
|
|
123
|
+
"coldFee": { "label": "低溫配送運費", "type": "number", "default": 160, "min": 0 },
|
|
124
|
+
"senderName": { "label": "寄件人姓名(最多 10 字)", "type": "string", "default": "" },
|
|
125
|
+
"senderPhone": { "label": "寄件人電話", "type": "string", "default": "" },
|
|
126
|
+
"senderCellphone": { "label": "寄件人手機", "type": "string", "default": "" },
|
|
127
|
+
"senderZipcode": { "label": "寄件人郵遞區號(宅配用)", "type": "string", "default": "", "description": "3、5 或 6 碼數字。留空時只採用寄件地址開頭的郵遞區號。" },
|
|
128
|
+
"senderAddress": { "label": "寄件地址(宅配用)", "type": "string", "default": "", "description": "沒填郵遞區號時,地址開頭的 3–6 位數字當作郵遞區號。" },
|
|
129
|
+
"labelMode": {
|
|
130
|
+
"label": "託運單格式",
|
|
131
|
+
"type": "select",
|
|
132
|
+
"options": [
|
|
133
|
+
{ "value": "1", "label": "A4" },
|
|
134
|
+
{ "value": "2", "label": "A6" }
|
|
135
|
+
],
|
|
136
|
+
"default": "1",
|
|
137
|
+
"description": "A6 只用於 7-ELEVEN(含冷凍)與中華郵政;全家、萊爾富、OK、黑貓沒有 A6,一律印 A4。"
|
|
138
|
+
},
|
|
139
|
+
"logisticsAutoShip": { "label": "付款後(貨到付款為下單後)自動建立物流單", "type": "boolean", "default": false },
|
|
140
|
+
|
|
141
|
+
"invoiceEnabled": { "label": "啟用綠界電子發票", "type": "boolean", "default": false },
|
|
142
|
+
"invoiceEnvironment": {
|
|
143
|
+
"label": "發票環境",
|
|
144
|
+
"type": "select",
|
|
145
|
+
"options": [
|
|
146
|
+
{ "value": "stage", "label": "測試環境(stage)" },
|
|
147
|
+
{ "value": "production", "label": "正式環境" }
|
|
148
|
+
],
|
|
149
|
+
"default": "stage"
|
|
150
|
+
},
|
|
151
|
+
"invoiceMerchantId": { "label": "發票特店編號", "type": "string", "default": "", "description": "未填時使用綠界公開測試帳號 2000132(測試環境開立的發票不會上傳財政部)。" },
|
|
152
|
+
"invoiceHashKey": { "label": "發票 HashKey", "type": "secret" },
|
|
153
|
+
"invoiceHashIv": { "label": "發票 HashIV", "type": "secret" },
|
|
154
|
+
"invoicePrefix": { "label": "發票關聯編號前綴(最多 5 個英數字)", "type": "string", "default": "", "description": "多個商店共用同一個綠界發票帳號時,用前綴區分。" },
|
|
155
|
+
"invoiceAuto": { "label": "自動開立", "type": "boolean", "default": true },
|
|
156
|
+
"invoiceIssueWhen": {
|
|
157
|
+
"label": "開立時機",
|
|
158
|
+
"type": "select",
|
|
159
|
+
"options": [
|
|
160
|
+
{ "value": "paid", "label": "付款完成(貨到付款於下單後)" },
|
|
161
|
+
{ "value": "completed", "label": "訂單完成(取貨完成)" }
|
|
162
|
+
],
|
|
163
|
+
"default": "paid"
|
|
164
|
+
},
|
|
165
|
+
"invoiceDelayDays": { "label": "延後開立天數(0–30)", "type": "number", "default": 0, "min": 0, "max": 30 },
|
|
166
|
+
"invoiceAutoVoid": { "label": "訂單取消或全額退款時自動作廢", "type": "boolean", "default": false },
|
|
167
|
+
"invoiceAutoAllowance": { "label": "部分退款時自動開立折讓", "type": "boolean", "default": false },
|
|
168
|
+
"invoiceVerifyOnline": { "label": "結帳時向綠界驗證捐贈碼與手機條碼", "type": "boolean", "default": true },
|
|
169
|
+
"invoiceAllowDonate": { "label": "結帳可選捐贈發票", "type": "boolean", "default": true },
|
|
170
|
+
"invoiceAllowB2b": { "label": "結帳可打統編(三聯式)", "type": "boolean", "default": true },
|
|
171
|
+
"invoiceChannelMember": { "label": "個人載具:綠界會員載具", "type": "boolean", "default": true },
|
|
172
|
+
"invoiceChannelMobile": { "label": "個人載具:手機條碼", "type": "boolean", "default": true },
|
|
173
|
+
"invoiceChannelCert": { "label": "個人載具:自然人憑證", "type": "boolean", "default": true },
|
|
174
|
+
"invoiceChannelPaper": { "label": "個人載具:紙本發票", "type": "boolean", "default": true, "description": "至少保留一種;全部取消時自動保留紙本。紙本發票請到綠界後台列印寄送。" },
|
|
175
|
+
"invoiceDefaultCarrier": {
|
|
176
|
+
"label": "預設的個人載具",
|
|
177
|
+
"type": "select",
|
|
178
|
+
"options": [
|
|
179
|
+
{ "value": "member", "label": "綠界會員載具" },
|
|
180
|
+
{ "value": "mobile", "label": "手機條碼" },
|
|
181
|
+
{ "value": "cert", "label": "自然人憑證" },
|
|
182
|
+
{ "value": "paper", "label": "紙本發票" }
|
|
183
|
+
],
|
|
184
|
+
"default": "member"
|
|
185
|
+
},
|
|
186
|
+
"invoiceDonateOrgs": { "label": "捐贈單位(每行「名稱|愛心碼」)", "type": "string", "default": "", "description": "例:伊甸社會福利基金會|25885。填寫後結帳改為下拉選單。" },
|
|
187
|
+
"busKey": {
|
|
188
|
+
"label": "匯流排金鑰",
|
|
189
|
+
"type": "secret",
|
|
190
|
+
"description": "由網站部署腳本寫入(scripts/provision-bus.mjs),請勿手動修改。"
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,44 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"name": "@moksa/plugin-ecpay",
|
|
3
|
+
"version": "0.1.0",
|
|
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