recur-tw 0.8.5 → 0.8.6
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.cjs +34 -2
- package/dist/index.js +34 -2
- package/dist/recur.umd.js +24 -24
- package/dist/server.d.cts +194 -0
- package/dist/server.d.ts +194 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3031,6 +3031,37 @@ function RecurProvider({ children, config: initialConfig = {} }) {
|
|
|
3031
3031
|
},
|
|
3032
3032
|
[fetchProducts]
|
|
3033
3033
|
);
|
|
3034
|
+
const getCheckoutStatus = React.useCallback(
|
|
3035
|
+
async (checkoutId, clientSecret) => {
|
|
3036
|
+
if (!config.publishableKey) {
|
|
3037
|
+
throw new Error("publishableKey is required");
|
|
3038
|
+
}
|
|
3039
|
+
const baseUrl = config.baseUrl || "https://api.recur.tw";
|
|
3040
|
+
const url = `${baseUrl}/v1/checkouts/${checkoutId}?client_secret=${encodeURIComponent(clientSecret)}`;
|
|
3041
|
+
const response = await fetch(url, {
|
|
3042
|
+
method: "GET",
|
|
3043
|
+
headers: {
|
|
3044
|
+
"X-Recur-Publishable-Key": config.publishableKey,
|
|
3045
|
+
"X-Recur-SDK-Type": SDK_TYPE,
|
|
3046
|
+
"X-Recur-SDK-Version": SDK_VERSION
|
|
3047
|
+
}
|
|
3048
|
+
});
|
|
3049
|
+
if (!response.ok) {
|
|
3050
|
+
const errorData = await response.json().catch(() => ({}));
|
|
3051
|
+
throw new Error(errorData.error || "Failed to get checkout status");
|
|
3052
|
+
}
|
|
3053
|
+
const rawResult = await response.json();
|
|
3054
|
+
const result = toCamelCase(rawResult);
|
|
3055
|
+
return {
|
|
3056
|
+
id: result.checkout.id,
|
|
3057
|
+
status: result.checkout.status,
|
|
3058
|
+
amount: result.checkout.amount,
|
|
3059
|
+
currency: result.checkout.currency,
|
|
3060
|
+
lastCharge: result.lastCharge
|
|
3061
|
+
};
|
|
3062
|
+
},
|
|
3063
|
+
[config]
|
|
3064
|
+
);
|
|
3034
3065
|
const value = React.useMemo(
|
|
3035
3066
|
() => ({
|
|
3036
3067
|
config,
|
|
@@ -3038,9 +3069,10 @@ function RecurProvider({ children, config: initialConfig = {} }) {
|
|
|
3038
3069
|
fetchProducts,
|
|
3039
3070
|
fetchPlans,
|
|
3040
3071
|
isCheckingOut,
|
|
3041
|
-
updateConfig
|
|
3072
|
+
updateConfig,
|
|
3073
|
+
getCheckoutStatus
|
|
3042
3074
|
}),
|
|
3043
|
-
[config, checkout, fetchProducts, fetchPlans, isCheckingOut, updateConfig]
|
|
3075
|
+
[config, checkout, fetchProducts, fetchPlans, isCheckingOut, updateConfig, getCheckoutStatus]
|
|
3044
3076
|
);
|
|
3045
3077
|
return /* @__PURE__ */ jsxRuntime.jsx(RecurContext.Provider, { value, children });
|
|
3046
3078
|
}
|
package/dist/index.js
CHANGED
|
@@ -3025,6 +3025,37 @@ function RecurProvider({ children, config: initialConfig = {} }) {
|
|
|
3025
3025
|
},
|
|
3026
3026
|
[fetchProducts]
|
|
3027
3027
|
);
|
|
3028
|
+
const getCheckoutStatus = useCallback(
|
|
3029
|
+
async (checkoutId, clientSecret) => {
|
|
3030
|
+
if (!config.publishableKey) {
|
|
3031
|
+
throw new Error("publishableKey is required");
|
|
3032
|
+
}
|
|
3033
|
+
const baseUrl = config.baseUrl || "https://api.recur.tw";
|
|
3034
|
+
const url = `${baseUrl}/v1/checkouts/${checkoutId}?client_secret=${encodeURIComponent(clientSecret)}`;
|
|
3035
|
+
const response = await fetch(url, {
|
|
3036
|
+
method: "GET",
|
|
3037
|
+
headers: {
|
|
3038
|
+
"X-Recur-Publishable-Key": config.publishableKey,
|
|
3039
|
+
"X-Recur-SDK-Type": SDK_TYPE,
|
|
3040
|
+
"X-Recur-SDK-Version": SDK_VERSION
|
|
3041
|
+
}
|
|
3042
|
+
});
|
|
3043
|
+
if (!response.ok) {
|
|
3044
|
+
const errorData = await response.json().catch(() => ({}));
|
|
3045
|
+
throw new Error(errorData.error || "Failed to get checkout status");
|
|
3046
|
+
}
|
|
3047
|
+
const rawResult = await response.json();
|
|
3048
|
+
const result = toCamelCase(rawResult);
|
|
3049
|
+
return {
|
|
3050
|
+
id: result.checkout.id,
|
|
3051
|
+
status: result.checkout.status,
|
|
3052
|
+
amount: result.checkout.amount,
|
|
3053
|
+
currency: result.checkout.currency,
|
|
3054
|
+
lastCharge: result.lastCharge
|
|
3055
|
+
};
|
|
3056
|
+
},
|
|
3057
|
+
[config]
|
|
3058
|
+
);
|
|
3028
3059
|
const value = useMemo(
|
|
3029
3060
|
() => ({
|
|
3030
3061
|
config,
|
|
@@ -3032,9 +3063,10 @@ function RecurProvider({ children, config: initialConfig = {} }) {
|
|
|
3032
3063
|
fetchProducts,
|
|
3033
3064
|
fetchPlans,
|
|
3034
3065
|
isCheckingOut,
|
|
3035
|
-
updateConfig
|
|
3066
|
+
updateConfig,
|
|
3067
|
+
getCheckoutStatus
|
|
3036
3068
|
}),
|
|
3037
|
-
[config, checkout, fetchProducts, fetchPlans, isCheckingOut, updateConfig]
|
|
3069
|
+
[config, checkout, fetchProducts, fetchPlans, isCheckingOut, updateConfig, getCheckoutStatus]
|
|
3038
3070
|
);
|
|
3039
3071
|
return /* @__PURE__ */ jsx(RecurContext.Provider, { value, children });
|
|
3040
3072
|
}
|
package/dist/recur.umd.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var RecurCheckout=(()=>{var P=Object.defineProperty;var ge=Object.getOwnPropertyDescriptor;var be=Object.getOwnPropertyNames;var ye=Object.prototype.hasOwnProperty;var
|
|
1
|
+
"use strict";var RecurCheckout=(()=>{var P=Object.defineProperty;var ge=Object.getOwnPropertyDescriptor;var be=Object.getOwnPropertyNames;var ye=Object.prototype.hasOwnProperty;var ke=(n,e,t)=>e in n?P(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t;var y=(n,e)=>()=>(n&&(e=n(n=0)),e);var b=(n,e)=>{for(var t in e)P(n,t,{get:e[t],enumerable:!0})},ve=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of be(e))!ye.call(n,s)&&s!==t&&P(n,s,{get:()=>e[s],enumerable:!(r=ge(e,s))||r.enumerable});return n};var xe=n=>ve(P({},"__esModule",{value:!0}),n);var c=(n,e,t)=>ke(n,typeof e!="symbol"?e+"":e,t);var X={};b(X,{RecurLoadingSpinner:()=>L});var L,J=y(()=>{"use strict";L=class extends HTMLElement{static get observedAttributes(){return["message","size"]}constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){this.render()}attributeChangedCallback(e,t,r){t!==r&&this.render()}get message(){return this.getAttribute("message")||"\u6B63\u5728\u8655\u7406\u8A02\u95B1..."}get size(){let e=this.getAttribute("size");return e==="small"||e==="large"?e:"medium"}getSizeValue(){return{small:24,medium:40,large:56}[this.size]}render(){let e=this.getSizeValue();this.shadowRoot.innerHTML=`
|
|
2
2
|
<style>
|
|
3
3
|
:host {
|
|
4
4
|
display: block;
|
|
@@ -377,7 +377,7 @@
|
|
|
377
377
|
</style>
|
|
378
378
|
|
|
379
379
|
${e}
|
|
380
|
-
`}};typeof window<"u"&&!customElements.get("recur-skeleton-loader")&&customElements.define("recur-skeleton-loader",_)});var re={};b(re,{RecurPaymentFormSkeleton:()=>D});var D,
|
|
380
|
+
`}};typeof window<"u"&&!customElements.get("recur-skeleton-loader")&&customElements.define("recur-skeleton-loader",_)});var re={};b(re,{RecurPaymentFormSkeleton:()=>D});var D,se=y(()=>{"use strict";D=class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){this.render()}render(){this.shadowRoot.innerHTML=`
|
|
381
381
|
<style>
|
|
382
382
|
:host {
|
|
383
383
|
display: block;
|
|
@@ -644,7 +644,7 @@
|
|
|
644
644
|
<p class="security-text">\u60A8\u7684\u4ED8\u6B3E\u8CC7\u8A0A\u7D93\u904E\u52A0\u5BC6\u4FDD\u8B77</p>
|
|
645
645
|
</div>
|
|
646
646
|
</div>
|
|
647
|
-
`}};typeof window<"u"&&!customElements.get("recur-payment-form-skeleton")&&customElements.define("recur-payment-form-skeleton",D)});var
|
|
647
|
+
`}};typeof window<"u"&&!customElements.get("recur-payment-form-skeleton")&&customElements.define("recur-payment-form-skeleton",D)});var ie={};b(ie,{RecurToast:()=>A,RecurToastContainer:()=>C});var A,k,C,oe=y(()=>{"use strict";A=class extends HTMLElement{static get observedAttributes(){return["message","type","duration"]}constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){this.render(),this.setupAutoDismiss()}get message(){return this.getAttribute("message")||"Notification"}get type(){let e=this.getAttribute("type");return e==="success"||e==="error"?e:"info"}get duration(){let e=this.getAttribute("duration");return e?parseInt(e,10):5e3}setupAutoDismiss(){let e=this.duration;e>0&&setTimeout(()=>this.dismiss(),e)}dismiss(){this.style.animation="recur-toast-slide-out 0.3s ease-in-out",setTimeout(()=>this.remove(),300)}getTypeIcon(){switch(this.type){case"success":return`<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
648
648
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
|
649
649
|
</svg>`;case"error":return`<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
650
650
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
@@ -764,7 +764,7 @@
|
|
|
764
764
|
</svg>
|
|
765
765
|
</button>
|
|
766
766
|
</div>
|
|
767
|
-
`,this.shadowRoot.querySelector(".toast-close")?.addEventListener("click",
|
|
767
|
+
`,this.shadowRoot.querySelector(".toast-close")?.addEventListener("click",s=>{s.stopPropagation(),this.dismiss()}),this.shadowRoot.querySelector(".toast")?.addEventListener("click",()=>this.dismiss())}static show(e,t="info",r=5e3){let s=C.getInstance(),i=document.createElement("recur-toast");return i.setAttribute("message",e),i.setAttribute("type",t),i.setAttribute("duration",r.toString()),s.appendChild(i),i}},k=class k extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){this.render()}render(){this.shadowRoot.innerHTML=`
|
|
768
768
|
<style>
|
|
769
769
|
:host {
|
|
770
770
|
position: fixed;
|
|
@@ -791,7 +791,7 @@
|
|
|
791
791
|
</style>
|
|
792
792
|
|
|
793
793
|
<slot></slot>
|
|
794
|
-
`}static getInstance(){return
|
|
794
|
+
`}static getInstance(){return k.instance||(k.instance=document.querySelector("recur-toast-container"),k.instance||(k.instance=document.createElement("recur-toast-container"),document.body.appendChild(k.instance))),k.instance}};c(k,"instance",null);C=k;typeof window<"u"&&!customElements.get("recur-toast")&&customElements.define("recur-toast",A);typeof window<"u"&&!customElements.get("recur-toast-container")&&customElements.define("recur-toast-container",C)});var ne={};b(ne,{RecurPaymentForm:()=>z});var z,ae=y(()=>{"use strict";z=class extends HTMLElement{constructor(){super();c(this,"containerId");c(this,"customStyles");c(this,"_isInitializing",!1);c(this,"_initializationAborted",!1);this.containerId=this.getAttribute("container-id")||`recur-${Date.now()}`,this.customStyles=this.getAttribute("custom-styles")||"",this.attachShadow({mode:"open"})}connectedCallback(){this.render()}disconnectedCallback(){console.log("[PaymentForm] Component disconnected, cleaning up..."),this._initializationAborted=!0,this._paymentSession=null;let t=document.getElementById(`${this.containerId}-submit-btn`);if(t){let r=t.cloneNode(!0);t.parentNode?.replaceChild(r,t)}}static get observedAttributes(){return["custom-styles","customer-name","customer-email","plan-name","amount","billing-period"]}attributeChangedCallback(t,r,s){t==="custom-styles"&&r!==s?(this.customStyles=s||"",this.updateCustomStyles()):r!==s&&this.updateCustomerInfoSection()}render(){this.shadowRoot.innerHTML=`
|
|
795
795
|
<style>
|
|
796
796
|
:host {
|
|
797
797
|
display: block;
|
|
@@ -899,7 +899,7 @@
|
|
|
899
899
|
height: 36px !important;
|
|
900
900
|
}
|
|
901
901
|
`;t.textContent=this.customStyles+`
|
|
902
|
-
`+r,this.appendChild(t);let
|
|
902
|
+
`+r,this.appendChild(t);let s=this.createOrderSummarySection();s.slot="order-summary",this.appendChild(s);let i=this.createCustomerInfoSection();i.slot="customer-info",this.appendChild(i);let a=this.createCardFieldsSection();a.slot="card-fields",this.appendChild(a);let o=this.createActionsSection();o.slot="actions",this.appendChild(o)}updateCustomerInfoSection(){let t=this.querySelector('[slot="customer-info"]');if(t){let r=this.createCustomerInfoSection();r.slot="customer-info",t.replaceWith(r)}}updateCustomStyles(){let t=this.querySelector(`#${this.containerId}-custom-styles`);if(t){let r=`
|
|
903
903
|
/* PAYUNi iframe \u5BB9\u5668\u9AD8\u5EA6\uFF08\u4F7F\u7528\u5BE6\u969B\u7684 container ID\uFF09 */
|
|
904
904
|
#${this.containerId}-card-no,
|
|
905
905
|
#${this.containerId}-card-exp,
|
|
@@ -907,7 +907,7 @@
|
|
|
907
907
|
height: 36px !important;
|
|
908
908
|
}
|
|
909
909
|
`;t.textContent=this.customStyles+`
|
|
910
|
-
`+r}}createOrderSummarySection(){let t=document.createElement("div");t.className="order-summary-section";let r=this.getAttribute("plan-name"),
|
|
910
|
+
`+r}}createOrderSummarySection(){let t=document.createElement("div");t.className="order-summary-section";let r=this.getAttribute("plan-name"),s=this.getAttribute("amount"),i=this.getAttribute("billing-period");if(!r||!s)return t;let o=i?{MONTHLY:"\u6708",QUARTERLY:"\u5B63",YEARLY:"\u5E74",WEEKLY:"\u9031"}[i]||i:"";return t.innerHTML=`
|
|
911
911
|
<style>
|
|
912
912
|
.order-summary-section {
|
|
913
913
|
background: #f7fafc;
|
|
@@ -962,17 +962,17 @@
|
|
|
962
962
|
<span class="order-summary-label">\u8A02\u95B1\u65B9\u6848</span>
|
|
963
963
|
<span class="order-summary-value">${r}</span>
|
|
964
964
|
</div>
|
|
965
|
-
${
|
|
965
|
+
${o?`
|
|
966
966
|
<div class="order-summary-item">
|
|
967
967
|
<span class="order-summary-label">\u8A08\u8CBB\u9031\u671F</span>
|
|
968
|
-
<span class="order-summary-value">\u6BCF${
|
|
968
|
+
<span class="order-summary-value">\u6BCF${o}</span>
|
|
969
969
|
</div>
|
|
970
970
|
`:""}
|
|
971
971
|
<div class="order-summary-item">
|
|
972
972
|
<span class="order-summary-label">\u7E3D\u8A08</span>
|
|
973
|
-
<span class="order-summary-total">NT$ ${Number(
|
|
973
|
+
<span class="order-summary-total">NT$ ${Number(s).toLocaleString()}</span>
|
|
974
974
|
</div>
|
|
975
|
-
`,t}createCustomerInfoSection(){let t=document.createElement("div");t.className="customer-info-section";let r=this.getAttribute("customer-name"),
|
|
975
|
+
`,t}createCustomerInfoSection(){let t=document.createElement("div");t.className="customer-info-section";let r=this.getAttribute("customer-name"),s=this.getAttribute("customer-email");return s?t.innerHTML=`
|
|
976
976
|
<style>
|
|
977
977
|
.recur-info-display {
|
|
978
978
|
background: #f7fafc;
|
|
@@ -1015,7 +1015,7 @@
|
|
|
1015
1015
|
`:""}
|
|
1016
1016
|
<div class="recur-info-row">
|
|
1017
1017
|
<span class="recur-info-label">\u96FB\u5B50\u90F5\u4EF6\uFF1A</span>
|
|
1018
|
-
<span class="recur-info-value">${
|
|
1018
|
+
<span class="recur-info-value">${s}</span>
|
|
1019
1019
|
</div>
|
|
1020
1020
|
</div>
|
|
1021
1021
|
`:t.innerHTML=`
|
|
@@ -1165,7 +1165,7 @@
|
|
|
1165
1165
|
</div>
|
|
1166
1166
|
</div>
|
|
1167
1167
|
</div>
|
|
1168
|
-
`,t}hideCardSkeletons(){[`${this.containerId}-card-no-skeleton`,`${this.containerId}-card-exp-skeleton`,`${this.containerId}-card-cvc-skeleton`].forEach(r=>{let
|
|
1168
|
+
`,t}hideCardSkeletons(){[`${this.containerId}-card-no-skeleton`,`${this.containerId}-card-exp-skeleton`,`${this.containerId}-card-cvc-skeleton`].forEach(r=>{let s=document.getElementById(r);s&&s.classList.add("hidden")})}createActionsSection(){let t=document.createElement("div");return t.className="actions-section",t.innerHTML=`
|
|
1169
1169
|
<style>
|
|
1170
1170
|
.recur-submit-button {
|
|
1171
1171
|
width: 100%;
|
|
@@ -1220,10 +1220,10 @@
|
|
|
1220
1220
|
>
|
|
1221
1221
|
<span id="${this.containerId}-submit-text">\u78BA\u8A8D\u4ED8\u6B3E</span>
|
|
1222
1222
|
</button>
|
|
1223
|
-
`,t}async initializePayment(t,r){if(this._initializationAborted)return console.log("[PaymentForm] Initialization aborted before start"),null;if(this._isInitializing=!0,await this.updateComplete(),this._initializationAborted)return console.log("[PaymentForm] Initialization aborted after updateComplete"),this._isInitializing=!1,null;if(!window.UniPayment)throw this._isInitializing=!1,new Error("PAYUNi SDK not loaded");try{let
|
|
1223
|
+
`,t}async initializePayment(t,r){if(this._initializationAborted)return console.log("[PaymentForm] Initialization aborted before start"),null;if(this._isInitializing=!0,await this.updateComplete(),this._initializationAborted)return console.log("[PaymentForm] Initialization aborted after updateComplete"),this._isInitializing=!1,null;if(!window.UniPayment)throw this._isInitializing=!1,new Error("PAYUNi SDK not loaded");try{let s=document.getElementById(`${this.containerId}-card-no`),i=document.getElementById(`${this.containerId}-card-exp`),a=document.getElementById(`${this.containerId}-card-cvc`);if(!s||!i||!a)return console.log("[PaymentForm] DOM elements not found, likely disconnected"),this._isInitializing=!1,null;if(this._initializationAborted)return console.log("[PaymentForm] Initialization aborted before createSession"),this._isInitializing=!1,null;let o=window.UniPayment.createSession(t,{env:r==="SANDBOX"?"S":"P",elements:{CardNo:`${this.containerId}-card-no`,CardExp:`${this.containerId}-card-exp`,CardCvc:`${this.containerId}-card-cvc`}});if(this._initializationAborted)return console.log("[PaymentForm] Initialization aborted before paymentSession.start()"),this._isInitializing=!1,null;try{await o.start()}catch(l){if((l?.message?.includes("1008")||l?.message?.includes("timeout")||l?.code===1008)&&this._initializationAborted)return console.log("[PaymentForm] Caught 1008 timeout error after component disconnect - ignoring"),this._isInitializing=!1,null;throw l}return this._initializationAborted?(console.log("[PaymentForm] Initialization aborted after paymentSession.start()"),this._isInitializing=!1,null):(this.hideCardSkeletons(),o.onUpdate?.(l=>{if(this._initializationAborted){console.log("[PaymentForm] Ignoring onUpdate event - component disconnected");return}console.log("[PaymentForm] PAYUNi update:",l);let d=l.status&&l.status.CardNo===!0&&l.status.CardExp===!0&&l.status.CardCvc===!0,v=document.getElementById(`${this.containerId}-submit-btn`);v&&(v.disabled=!d,console.log("[PaymentForm] Submit button disabled:",!d))}),this._initializationAborted?(console.log("[PaymentForm] Initialization aborted before saving session"),this._isInitializing=!1,null):(this._paymentSession=o,this.setupFormSubmission(),this._isInitializing=!1,o))}catch(s){throw this._isInitializing=!1,console.error("[PaymentForm] Failed to initialize payment:",s),s}}setupFormSubmission(){let t=document.getElementById(`${this.containerId}-submit-btn`);t&&t.addEventListener("click",async r=>{if(r.preventDefault(),t.classList.contains("loading"))return;let s,i,a=document.getElementById(`${this.containerId}-email`),o=document.getElementById(`${this.containerId}-name`);if(a&&o){if(s=a.value,i=o.value,!s||!i){this.showError("\u8ACB\u586B\u5BEB\u59D3\u540D\u548C\u96FB\u5B50\u90F5\u4EF6");return}}else if(s=this.getAttribute("customer-email")||void 0,i=this.getAttribute("customer-name")||void 0,!s||!i){this.showError("\u5BA2\u6236\u8CC7\u8A0A\u4E0D\u5B8C\u6574");return}this.clearError(),this.setButtonLoading(!0),this.dispatchEvent(new CustomEvent("submit",{detail:{customerEmail:s,customerName:i,paymentSession:this._paymentSession},bubbles:!0,composed:!0}))})}setButtonLoading(t){let r=document.getElementById(`${this.containerId}-submit-btn`);r&&(t?(r.classList.add("loading"),r.disabled=!0,r.innerHTML=`
|
|
1224
1224
|
<span class="recur-loading-spinner"></span>
|
|
1225
1225
|
<span>\u8655\u7406\u4E2D...</span>
|
|
1226
|
-
`):(r.classList.remove("loading"),r.disabled=!1,r.innerHTML='<span id="'+this.containerId+'-submit-text">\u78BA\u8A8D\u4ED8\u6B3E</span>'))}resetButton(){this.setButtonLoading(!1)}showError(t){let r=this.querySelector(".recur-sdk__error-container");r||(r=document.createElement("div"),r.className="recur-sdk__error-container",r.style.cssText="margin-bottom: 16px;",this.insertBefore(r,this.firstChild));let
|
|
1226
|
+
`):(r.classList.remove("loading"),r.disabled=!1,r.innerHTML='<span id="'+this.containerId+'-submit-text">\u78BA\u8A8D\u4ED8\u6B3E</span>'))}resetButton(){this.setButtonLoading(!1)}showError(t){let r=this.querySelector(".recur-sdk__error-container");r||(r=document.createElement("div"),r.className="recur-sdk__error-container",r.style.cssText="margin-bottom: 16px;",this.insertBefore(r,this.firstChild));let s=document.createElement("recur-error-display");s.setAttribute("error",t),s.setAttribute("dismissible","true"),r.innerHTML="",r.appendChild(s),s.scrollIntoView({behavior:"smooth",block:"center"})}clearError(){let t=this.querySelector(".recur-sdk__error-container");t&&(t.innerHTML="")}updateComplete(){return new Promise(t=>{if(this.isConnected)setTimeout(t,0);else{let r=new MutationObserver(()=>{this.isConnected&&(r.disconnect(),setTimeout(t,0))});r.observe(document.body,{childList:!0,subtree:!0})}})}getFormData(){return{email:document.getElementById(`${this.containerId}-email`)?.value,name:document.getElementById(`${this.containerId}-name`)?.value}}setFormData(t){if(t.email){let r=document.getElementById(`${this.containerId}-email`);r&&(r.value=t.email)}if(t.name){let r=document.getElementById(`${this.containerId}-name`);r&&(r.value=t.name)}}};customElements.get("recur-payment-form")||customElements.define("recur-payment-form",z)});var ce={};b(ce,{RecurCheckoutButton:()=>H});var H,le=y(()=>{"use strict";H=class extends HTMLElement{constructor(){super();c(this,"_isLoading",!1);c(this,"handleClick",async t=>{if(t.preventDefault(),this._isLoading||this.hasAttribute("disabled"))return;let r=this.getAttribute("publishable-key"),s=this.getAttribute("product-id"),i=this.getAttribute("success-url"),a=this.getAttribute("cancel-url");if(!r){this.dispatchError("Missing required attribute: publishable-key");return}if(!s){this.dispatchError("Missing required attribute: product-id");return}if(!i){this.dispatchError("Missing required attribute: success-url");return}if(!a){this.dispatchError("Missing required attribute: cancel-url");return}this._isLoading=!0,this.render(),this.setupEventListeners();try{let o=await this.createCheckoutSession({publishableKey:r,productId:s,successUrl:this.resolveUrl(i),cancelUrl:this.resolveUrl(a),customerEmail:this.getAttribute("customer-email")||void 0,mode:this.getAttribute("mode")});this.dispatchEvent(new CustomEvent("checkout-started",{detail:{sessionId:o.id,url:o.url},bubbles:!0,composed:!0})),window.location.href=o.url}catch(o){this._isLoading=!1,this.render(),this.setupEventListeners(),this.dispatchError(o.message||"Failed to create checkout session")}});this.attachShadow({mode:"open"})}static get observedAttributes(){return["publishable-key","product-id","success-url","cancel-url","customer-email","mode","button-text","button-style","disabled"]}connectedCallback(){this.render(),this.setupEventListeners()}disconnectedCallback(){let t=this.shadowRoot?.querySelector("button");t&&t.removeEventListener("click",this.handleClick)}attributeChangedCallback(t,r,s){r!==s&&this.render()}render(){let t=this.getAttribute("button-text")||this.textContent?.trim()||"\u8A02\u95B1",r=this.getAttribute("button-style")||"primary",s=this.hasAttribute("disabled")||this._isLoading;this.shadowRoot.innerHTML=`
|
|
1227
1227
|
<style>
|
|
1228
1228
|
:host {
|
|
1229
1229
|
display: inline-block;
|
|
@@ -1316,13 +1316,13 @@
|
|
|
1316
1316
|
|
|
1317
1317
|
<button
|
|
1318
1318
|
class="${r}"
|
|
1319
|
-
${
|
|
1319
|
+
${s?"disabled":""}
|
|
1320
1320
|
aria-busy="${this._isLoading}"
|
|
1321
1321
|
>
|
|
1322
1322
|
${this._isLoading?'<span class="spinner"></span>':""}
|
|
1323
1323
|
<span>${this._isLoading?"\u8655\u7406\u4E2D...":t}</span>
|
|
1324
1324
|
</button>
|
|
1325
|
-
`}setupEventListeners(){let t=this.shadowRoot?.querySelector("button");t&&t.addEventListener("click",this.handleClick)}async createCheckoutSession(t){let r=this.getApiBaseUrl(),
|
|
1325
|
+
`}setupEventListeners(){let t=this.shadowRoot?.querySelector("button");t&&t.addEventListener("click",this.handleClick)}async createCheckoutSession(t){let r=this.getApiBaseUrl(),s={productId:t.productId,successUrl:t.successUrl,cancelUrl:t.cancelUrl};t.mode&&(s.mode=t.mode),t.customerEmail&&(s.customerEmail=t.customerEmail);let i=await fetch(`${r}/v1/checkout/sessions`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":t.publishableKey},body:JSON.stringify(s)});if(!i.ok){let a=await i.json().catch(()=>({}));throw new Error(a.error?.message||`HTTP ${i.status}: Failed to create checkout session`)}return i.json()}getApiBaseUrl(){let t=this.getAttribute("api-base-url");if(t)return t;if(typeof window<"u"){let r=window.location.hostname;if(r==="localhost"||r.includes(".test")||r.includes(".local")||r==="127.0.0.1")return`${window.location.protocol}//${window.location.host}/api`}return"https://api.recur.tw"}resolveUrl(t){return t.startsWith("/")?`${window.location.origin}${t}`:t}dispatchError(t){console.error("[recur-checkout]",t),this.dispatchEvent(new CustomEvent("checkout-error",{detail:{message:t},bubbles:!0,composed:!0}))}};typeof window<"u"&&!customElements.get("recur-checkout")&&customElements.define("recur-checkout",H)});var de={};b(de,{RecurPortalButton:()=>$});var $,ue=y(()=>{"use strict";$=class extends HTMLElement{constructor(){super();c(this,"_isLoading",!1);c(this,"handleClick",async t=>{if(t.preventDefault(),this._isLoading||this.hasAttribute("disabled"))return;let r=this.getAttribute("portal-url"),s=this.getAttribute("api-endpoint");if(r){this.redirectToPortal(r);return}if(s){await this.fetchAndRedirect(s);return}this.dispatchError("Missing required attribute: either portal-url or api-endpoint must be provided")});this.attachShadow({mode:"open"})}static get observedAttributes(){return["portal-url","api-endpoint","customer-id","return-url","button-text","button-style","disabled","target"]}connectedCallback(){this.render(),this.setupEventListeners()}disconnectedCallback(){let t=this.shadowRoot?.querySelector("button");t&&t.removeEventListener("click",this.handleClick)}attributeChangedCallback(t,r,s){r!==s&&this.render()}render(){let t=this.getAttribute("button-text")||this.textContent?.trim()||"\u7BA1\u7406\u8A02\u95B1",r=this.getAttribute("button-style")||"primary",s=this.hasAttribute("disabled")||this._isLoading;this.shadowRoot.innerHTML=`
|
|
1326
1326
|
<style>
|
|
1327
1327
|
:host {
|
|
1328
1328
|
display: inline-block;
|
|
@@ -1438,7 +1438,7 @@
|
|
|
1438
1438
|
|
|
1439
1439
|
<button
|
|
1440
1440
|
class="${r}"
|
|
1441
|
-
${
|
|
1441
|
+
${s?"disabled":""}
|
|
1442
1442
|
aria-busy="${this._isLoading}"
|
|
1443
1443
|
>
|
|
1444
1444
|
${this._isLoading?'<span class="spinner"></span>':this.getPortalIcon()}
|
|
@@ -1449,7 +1449,7 @@
|
|
|
1449
1449
|
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/>
|
|
1450
1450
|
<circle cx="12" cy="7" r="4"/>
|
|
1451
1451
|
</svg>
|
|
1452
|
-
`}setupEventListeners(){let t=this.shadowRoot?.querySelector("button");t&&t.addEventListener("click",this.handleClick)}redirectToPortal(t){let r=this.getAttribute("target");this.dispatchEvent(new CustomEvent("portal-redirect",{detail:{url:t},bubbles:!0,composed:!0})),r==="_blank"?window.open(t,"_blank","noopener,noreferrer"):window.location.href=t}async fetchAndRedirect(t){let r=this.getAttribute("customer-id"),
|
|
1452
|
+
`}setupEventListeners(){let t=this.shadowRoot?.querySelector("button");t&&t.addEventListener("click",this.handleClick)}redirectToPortal(t){let r=this.getAttribute("target");this.dispatchEvent(new CustomEvent("portal-redirect",{detail:{url:t},bubbles:!0,composed:!0})),r==="_blank"?window.open(t,"_blank","noopener,noreferrer"):window.location.href=t}async fetchAndRedirect(t){let r=this.getAttribute("customer-id"),s=this.getAttribute("return-url");this._isLoading=!0,this.render(),this.setupEventListeners();try{let i={};r&&(i.customerId=r),s&&(i.returnUrl=s);let a=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});if(!a.ok){let d=await a.json().catch(()=>({}));throw new Error(d.error?.message||d.message||`HTTP ${a.status}: Failed to create portal session`)}let o=await a.json(),l=o.url||o.portalUrl;if(!l)throw new Error("Portal URL not found in response");this._isLoading=!1,this.render(),this.setupEventListeners(),this.redirectToPortal(l)}catch(i){this._isLoading=!1,this.render(),this.setupEventListeners(),this.dispatchError(i.message||"Failed to create portal session")}}dispatchError(t){console.error("[recur-portal]",t),this.dispatchEvent(new CustomEvent("portal-error",{detail:{message:t},bubbles:!0,composed:!0}))}};typeof window<"u"&&!customElements.get("recur-portal")&&customElements.define("recur-portal",$)});var Pe={};b(Pe,{RecurCheckout:()=>I,RecurElements:()=>S,createElements:()=>q,default:()=>Re,init:()=>he});async function we(){if(typeof window>"u"){console.warn("[Recur SDK] Cannot register Web Components: window is not defined");return}await Promise.all([Promise.resolve().then(()=>(J(),X)),Promise.resolve().then(()=>(G(),W)),Promise.resolve().then(()=>(Q(),Z)),Promise.resolve().then(()=>(te(),ee)),Promise.resolve().then(()=>(se(),re)),Promise.resolve().then(()=>(oe(),ie)),Promise.resolve().then(()=>(ae(),ne)),Promise.resolve().then(()=>(le(),ce)),Promise.resolve().then(()=>(ue(),de))]);let e=["recur-loading-spinner","recur-success-message","recur-error-display","recur-skeleton-loader","recur-payment-form-skeleton","recur-toast","recur-toast-container","recur-payment-form","recur-checkout","recur-portal"].filter(t=>!customElements.get(t));e.length>0&&console.warn("[Recur SDK] The following components are not registered:",e)}typeof window<"u"&&we();function Ee(n){return n.replace(/_([a-z])/g,(e,t)=>t.toUpperCase())}function f(n){if(n==null)return n;if(Array.isArray(n))return n.map(e=>f(e));if(n instanceof Date)return n;if(typeof n=="object"){let e={};for(let[t,r]of Object.entries(n)){let s=Ee(t);e[s]=f(r)}return e}return n}var Se="0.8.1",Ce="vanilla",N=class{constructor(e){c(this,"config");if(!e.publishableKey)throw new Error("publishableKey is required");this.config={baseUrl:"https://api.recur.tw",...e}}getHeaders(){return{"Content-Type":"application/json","X-Recur-Publishable-Key":this.config.publishableKey,"X-Recur-SDK-Type":Ce,"X-Recur-SDK-Version":Se,"X-Recur-Source":typeof window<"u"?window.location.origin:"server"}}async createSubscription(e){let{customerName:t,customerEmail:r}=e,s=e.productId||e.planId,i=e.productSlug;if(!s&&!i)throw new Error("Either productId or productSlug is required");let a={customerName:t,customerEmail:r};s&&(a.productId=s),i&&(a.productSlug=i);let o=await fetch(`${this.config.baseUrl}/v1/subscriptions`,{method:"POST",headers:this.getHeaders(),body:JSON.stringify(a)});if(!o.ok){let d=await o.json().catch(()=>({}));throw{code:d.error||"CHECKOUT_FAILED",message:d.message||"Failed to initiate checkout",details:d}}let l=await o.json();return f(l)}async fetchProducts(e){let t=new URL(`${this.config.baseUrl}/v1/products`);e?.type&&t.searchParams.set("type",e.type);let r=await fetch(t.toString(),{method:"GET",headers:this.getHeaders()});if(!r.ok){let i=await r.json().catch(()=>({}));throw{code:i.error||"FETCH_PRODUCTS_FAILED",message:i.message||"Failed to fetch products",details:i}}let s=await r.json();return f(s)}async fetchPlans(){return{plans:(await this.fetchProducts({type:"SUBSCRIPTION"})).products}}getConfig(){return{...this.config}}};var B=class{constructor(e,t){c(this,"config");c(this,"options");c(this,"container");c(this,"checkoutId",null);c(this,"sdkToken",null);c(this,"sdkTimestamp",null);c(this,"creditToken",null);c(this,"sdkEnv","S");c(this,"payuniSDK",null);c(this,"isFormValid",!1);if(this.config=e,this.options=t,typeof t.container=="string"){let r=document.querySelector(t.container);if(!r)throw new Error(`Container not found: ${t.container}`);this.container=r}else this.container=t.container}async render(){try{await this.initCheckout(),this.renderHTML(),await this.loadPayUniSDK(),await this.initPayUniSDK(),this.setupFormSubmit()}catch(e){this.handleError(e)}}getBaseUrl(){if(this.config.baseUrl)return this.config.baseUrl;if(typeof window<"u"){let e=window.location.hostname;if(e==="localhost"||e.includes(".test")||e.includes(".local")||e==="127.0.0.1")return`${window.location.protocol}//${window.location.host}/api`}return"https://api.recur.tw"}async initCheckout(){let e=this.getBaseUrl(),t=await fetch(`${e}/v1/checkouts`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":this.config.publishableKey},body:JSON.stringify({productId:this.options.planId,customerEmail:this.options.customerEmail,customerName:this.options.customerName})});if(!t.ok){let i=await t.json().catch(()=>({}));throw new Error(i.error||"Failed to initialize checkout")}let r=await t.json(),s=f(r);this.checkoutId=s.checkout.id,this.sdkToken=s.sdkToken,this.sdkTimestamp=s.sdkTimestamp||null,this.creditToken=s.creditToken||null,this.sdkEnv=this.config.publishableKey.startsWith("pk_test_")?"S":"P"}renderHTML(){this.container.innerHTML=`
|
|
1453
1453
|
<div class="recur-embedded-checkout" style="max-width: 400px; margin: 0 auto; padding: 20px; font-family: system-ui, -apple-system, sans-serif;">
|
|
1454
1454
|
<div class="recur-checkout-header" style="margin-bottom: 24px;">
|
|
1455
1455
|
<h2 style="margin: 0 0 8px 0; font-size: 20px; font-weight: 600; color: #111;">Subscribe</h2>
|
|
@@ -1519,21 +1519,21 @@
|
|
|
1519
1519
|
</p>
|
|
1520
1520
|
</form>
|
|
1521
1521
|
</div>
|
|
1522
|
-
`}async loadPayUniSDK(){return new Promise((e,t)=>{if(window.UniPayment){e();return}let r=document.createElement("script");r.src=this.sdkEnv==="P"?"https://vendor.payuni.com.tw/sdk/uni-payment.js":"https://sandbox-vendor.payuni.com.tw/sdk/uni-payment.js",r.async=!0,r.onload=()=>e(),r.onerror=()=>t(new Error("Failed to load PAYUNi SDK")),document.head.appendChild(r)})}async initPayUniSDK(){if(!this.sdkToken)throw new Error("SDK token not initialized");let e=window.UniPayment;if(!e)throw new Error("PAYUNi SDK not loaded");let t={env:this.sdkEnv,useInst:!1,elements:{CardNo:"recur-card-number",CardExp:"recur-card-exp",CardCvc:"recur-card-cvc"},style:{color:"#111827",errorColor:"#dc2626",fontSize:"14px",fontWeight:"400",lineHeight:"24px"}};this.payuniSDK=e.createSession(this.sdkToken,t),await this.payuniSDK.start(),this.payuniSDK.onUpdate(r=>{let
|
|
1522
|
+
`}async loadPayUniSDK(){return new Promise((e,t)=>{if(window.UniPayment){e();return}let r=document.createElement("script");r.src=this.sdkEnv==="P"?"https://vendor.payuni.com.tw/sdk/uni-payment.js":"https://sandbox-vendor.payuni.com.tw/sdk/uni-payment.js",r.async=!0,r.onload=()=>e(),r.onerror=()=>t(new Error("Failed to load PAYUNi SDK")),document.head.appendChild(r)})}async initPayUniSDK(){if(!this.sdkToken)throw new Error("SDK token not initialized");let e=window.UniPayment;if(!e)throw new Error("PAYUNi SDK not loaded");let t={env:this.sdkEnv,useInst:!1,elements:{CardNo:"recur-card-number",CardExp:"recur-card-exp",CardCvc:"recur-card-cvc"},style:{color:"#111827",errorColor:"#dc2626",fontSize:"14px",fontWeight:"400",lineHeight:"24px"}};this.payuniSDK=e.createSession(this.sdkToken,t),await this.payuniSDK.start(),this.payuniSDK.onUpdate(r=>{let s=r?.status;s&&(this.isFormValid=s.CardNo===!0&&s.CardExp===!0&&s.CardCvc===!0,this.updateSubmitButton())})}updateSubmitButton(){let e=document.getElementById("recur-submit-btn");e&&(e.disabled=!this.isFormValid,e.style.opacity=this.isFormValid?"1":"0.5",e.style.cursor=this.isFormValid?"pointer":"not-allowed")}setupFormSubmit(){let e=document.getElementById("recur-checkout-form");e&&e.addEventListener("submit",async t=>{t.preventDefault(),await this.handleSubmit()})}async handleSubmit(){try{this.showLoading(!0),this.hideError();let e=document.getElementById("recur-email").value,t=document.getElementById("recur-name").value;if(!e||!t)throw new Error("Please fill in all required fields");if(!this.checkoutId)throw new Error("Checkout session not initialized");if(!this.payuniSDK)throw new Error("Payment system not initialized");let r=await this.payuniSDK.getTradeResult();if(r.Status!=="SUCCESS")throw new Error(r.Message||"Card validation failed");let s=r.EncryptInfo||r.creditToken;if(!s)throw console.error("[Recur SDK] Missing payment token. Available fields:",Object.keys(r)),new Error("Missing payment token");let i=this.getBaseUrl(),a=await fetch(`${i}/v1/checkouts/${this.checkoutId}/pay`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":this.config.publishableKey},body:JSON.stringify({creditToken:s,timestamp:this.sdkTimestamp||r.HashTimestamp||r.timestamp})});if(!a.ok){let d=await a.json().catch(()=>({}));throw new Error(d.error||"Failed to process payment")}let o=await a.json(),l={subscription:{id:o.subscription?.id||o.charge?.id||"",status:o.success?"active":"failed",planId:this.options.planId,planName:"",amount:o.charge?.amount||0,billingPeriod:o.subscription?.billingPeriod||"MONTHLY",trialDays:null},subscriber:{id:"",email:document.getElementById("recur-email")?.value||"",name:document.getElementById("recur-name")?.value||""},nextSteps:{getSdkToken:"",completeSubscription:""}};this.options.onSuccess&&this.options.onSuccess(l),this.showSuccess()}catch(e){this.handleError(e)}finally{this.showLoading(!1)}}showLoading(e){let t=document.getElementById("recur-submit-btn");t&&(t.disabled=e,t.textContent=e?"Processing...":"Subscribe Now")}showError(e){let t=document.getElementById("recur-error");t&&(t.textContent=e,t.style.display="block")}hideError(){let e=document.getElementById("recur-error");e&&(e.style.display="none")}showSuccess(){this.container.innerHTML="";let e=document.createElement("recur-success-message");e.setAttribute("title","Subscription Complete!"),e.setAttribute("message","Thank you for subscribing. You will receive a confirmation email shortly."),this.container.appendChild(e)}handleError(e){let t=e?.message||"An error occurred";this.showError(t);let r={code:"CHECKOUT_ERROR",message:t};this.options.onError&&this.options.onError(r)}};var S=class{constructor(e){c(this,"publishableKey");c(this,"baseUrl");c(this,"embedUrl");c(this,"iframe",null);c(this,"container",null);c(this,"sessionId",null);c(this,"timestamp",null);c(this,"creditToken",null);c(this,"sdkToken",null);c(this,"cardToken",null);c(this,"cardTimestamp",null);c(this,"eventHandlers",new Map);typeof e=="string"?(this.publishableKey=e,this.baseUrl=this.getDefaultBaseUrl(),this.embedUrl=this.getDefaultEmbedUrl()):(this.publishableKey=e.publishableKey,this.baseUrl=e.baseUrl||this.getDefaultBaseUrl(),this.embedUrl=e.embedUrl||this.getDefaultEmbedUrl()),window.addEventListener("message",this.handleMessage.bind(this))}async mount(e){let t=typeof e=="string"?document.querySelector(e):e;if(!t)throw new Error(`Container not found: ${e}`);return this.container=t,this.iframe=document.createElement("iframe"),this.iframe.src=`${this.embedUrl}/elements?key=${encodeURIComponent(this.publishableKey)}`,this.iframe.style.cssText=`
|
|
1523
1523
|
width: 100%;
|
|
1524
1524
|
border: none;
|
|
1525
1525
|
min-height: 200px;
|
|
1526
1526
|
display: block;
|
|
1527
1527
|
user-select: none;
|
|
1528
1528
|
transition: height 0.35s ease, opacity 0.4s ease 0.1s;
|
|
1529
|
-
`.replace(/\s+/g," ").trim(),this.iframe.allow="payment *",this.iframe.setAttribute("title","Secure payment input frame"),this.iframe.setAttribute("role","presentation"),this.iframe.setAttribute("scrolling","no"),this.iframe.setAttribute("frameBorder","0"),this.container.innerHTML="",this.container.appendChild(this.iframe),new Promise((r,
|
|
1529
|
+
`.replace(/\s+/g," ").trim(),this.iframe.allow="payment *",this.iframe.setAttribute("title","Secure payment input frame"),this.iframe.setAttribute("role","presentation"),this.iframe.setAttribute("scrolling","no"),this.iframe.setAttribute("frameBorder","0"),this.container.innerHTML="",this.container.appendChild(this.iframe),new Promise((r,s)=>{let i=setTimeout(()=>{s(new Error("Elements initialization timeout"))},3e4),a=()=>{clearTimeout(i),this.off("ready",a),r()},o=l=>{clearTimeout(i),this.off("error",o),s(new Error(l.message||"Elements initialization failed"))};this.on("ready",a),this.on("error",o)})}on(e,t){this.eventHandlers.has(e)||this.eventHandlers.set(e,new Set),this.eventHandlers.get(e).add(t)}off(e,t){let r=this.eventHandlers.get(e);r&&r.delete(t)}emit(e,t){let r=this.eventHandlers.get(e);r&&r.forEach(s=>s(t))}handleMessage(e){if(!this.isValidOrigin(e.origin))return;let{type:t,data:r}=e.data||{};switch(t){case"RECUR_ELEMENTS_READY":this.sessionId=r.sessionId,this.timestamp=r.timestamp,this.creditToken=r.creditToken,this.sdkToken=r.sdkToken,this.emit("ready",r);break;case"RECUR_CARD_VALID":this.emit("change",{valid:r.valid});break;case"RECUR_CARD_TOKENIZED":this.cardToken=r.cardToken,this.cardTimestamp=r.timestamp,this.emit("tokenized",r);break;case"RECUR_PAYMENT_ERROR":this.emit("error",r);break;case"RECUR_RESIZE":this.iframe&&r?.height&&(this.iframe.style.height=`${r.height}px`);break}}isValidOrigin(e){try{let t=new URL(this.embedUrl).origin;return e===t}catch{return!1}}async tokenize(){if(!this.iframe||!this.iframe.contentWindow)throw new Error("Elements not mounted");let e=new URL(this.embedUrl).origin;return this.iframe.contentWindow.postMessage({type:"RECUR_TOKENIZE"},e),new Promise((t,r)=>{let s=setTimeout(()=>{r(new Error("Tokenization timeout"))},6e4),i=o=>{clearTimeout(s),this.off("tokenized",i),t(o)},a=o=>{clearTimeout(s),this.off("error",a),r(new Error(o.message||"Tokenization failed"))};this.on("tokenized",i),this.on("error",a)})}async confirmPayment(e){if(!this.sessionId||!this.cardToken)throw new Error("Card not tokenized. Call tokenize() first.");let t=await fetch(`${this.baseUrl}/api/v1/elements/confirm`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":this.publishableKey},body:JSON.stringify({sessionId:this.sessionId,productId:e.productId,email:e.email,name:e.name,phone:e.phone,externalCustomerId:e.externalCustomerId,metadata:e.metadata,successUrl:e.successUrl,cancelUrl:e.cancelUrl})}),r=await t.json();return t.ok?r:{status:"failed",message:r.error||r.message||"Payment failed",canRetry:r.canRetry??!0}}getDefaultBaseUrl(){if(typeof window>"u")return"https://app.recur.tw";let e=window.location.hostname;return e==="localhost"||e.includes(".test")||e.includes(".local")||e==="127.0.0.1"?`${window.location.protocol}//${e}:3000`:"https://app.recur.tw"}getDefaultEmbedUrl(){if(typeof window>"u")return"https://embed.recur.tw";let e=window.location.hostname;return e==="localhost"||e.includes(".test")||e.includes(".local")||e==="127.0.0.1"?`${window.location.protocol}//${e}:3002`:"https://embed.recur.tw"}destroy(){this.iframe&&this.iframe.parentNode&&this.iframe.parentNode.removeChild(this.iframe),this.iframe=null,this.container=null,this.sessionId=null,this.timestamp=null,this.creditToken=null,this.sdkToken=null,this.cardToken=null,this.cardTimestamp=null,this.eventHandlers.clear(),window.removeEventListener("message",this.handleMessage.bind(this))}};function q(n){return new S(n)}var Te="https://vendor.payuni.com.tw/sdk/uni-payment.js",Ie="https://sandbox-vendor.payuni.com.tw/sdk/uni-payment.js",me=!1,K=!1,T=null;async function pe(n=!1){return me&&window.UniPayment?Promise.resolve():(K&&T||(K=!0,T=new Promise((e,t)=>{let r=document.createElement("script");r.src=n?Ie:Te,r.async=!0,r.onload=()=>{me=!0,K=!1,e()},r.onerror=()=>{K=!1,T=null,t(new Error("Failed to load PAYUNi SDK"))},document.head.appendChild(r)})),T)}var I=class{constructor(e){c(this,"core");c(this,"currentModal",null);c(this,"currentIframe",null);c(this,"currentModalOverlay",null);this.core=new N(e)}async fetchProducts(e){return await this.core.fetchProducts(e)}async fetchPlans(){return await this.core.fetchPlans()}async createEmbeddedCheckout(e){let t=this.core.getConfig();await new B(t,e).render()}async redirectToCheckout(e){let t=await this.createCheckoutSession(e);window.location.href=t.url}async createCheckoutSession(e){let t=this.getBaseUrl();if(!e.productId&&!e.productSlug)throw new Error("Either productId or productSlug is required");if(!e.successUrl)throw new Error("successUrl is required for hosted checkout");if(!e.cancelUrl)throw new Error("cancelUrl is required for hosted checkout");let r={successUrl:e.successUrl,cancelUrl:e.cancelUrl};e.productId&&(r.productId=e.productId),e.productSlug&&(r.productSlug=e.productSlug),e.customerEmail&&(r.customerEmail=e.customerEmail),e.customerName&&(r.customerName=e.customerName),e.externalCustomerId&&(r.externalCustomerId=e.externalCustomerId);let s=this.core.getConfig(),i=await fetch(`${t}/v1/checkout/sessions`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":s.publishableKey},body:JSON.stringify(r)});if(!i.ok){let l=await i.json().catch(()=>({}));throw new Error(l.error?.message||l.error||"Failed to create checkout session")}let a=await i.json(),o=f(a);return{id:o.id,url:o.url,expiresAt:o.expiresAt,clientSecret:o.clientSecret}}async getCheckoutStatus(e,t){let r=this.getBaseUrl(),s=this.core.getConfig(),i=await fetch(`${r}/v1/checkouts/${e}?client_secret=${encodeURIComponent(t)}`,{method:"GET",headers:{"X-Recur-Publishable-Key":s.publishableKey}});if(!i.ok){let l=await i.json().catch(()=>({}));throw new Error(l.error||"Failed to get checkout status")}let a=await i.json(),o=f(a);return{id:o.checkout.id,status:o.checkout.status,amount:o.checkout.amount,currency:o.checkout.currency,lastCharge:o.lastCharge}}async checkout(e){let t=this.core.getConfig(),r=null,s=e.productId||e.planId,i=e.productSlug;try{if(console.log("[Recur SDK] Starting checkout flow...",{productId:s,productSlug:i,mode:e.mode}),!s&&!i)throw new Error("Either productId or productSlug is required");if(!t.publishableKey)throw new Error("publishableKey is required");let a=this.getBaseUrl();console.log("[Recur SDK] Base URL:",a);let o={"Content-Type":"application/json","X-Recur-Publishable-Key":t.publishableKey},l=e.mode||"modal";if(l==="redirect"){if(!e.successUrl)throw new Error("successUrl is required for redirect mode");if(!e.cancelUrl)throw new Error("cancelUrl is required for redirect mode");console.log("[Recur SDK] Creating hosted checkout session...");let m={successUrl:e.successUrl,cancelUrl:e.cancelUrl};s&&(m.productId=s),i&&(m.productSlug=i),e.customerEmail&&(m.customerEmail=e.customerEmail),e.customerName&&(m.customerName=e.customerName),e.externalCustomerId&&(m.externalCustomerId=e.externalCustomerId);let x=await fetch(`${a}/v1/checkout/sessions`,{method:"POST",headers:o,body:JSON.stringify(m)});if(!x.ok){let w=await x.json().catch(()=>({}));throw console.error("[Recur SDK] Failed to create checkout session:",w),new Error(w.error?.message||w.error||"Failed to create checkout session")}let F=await x.json(),g=f(F);console.log("[Recur SDK] Checkout session created:",g),console.log("[Recur SDK] Redirecting to hosted checkout:",g.url),window.location.href=g.url;return}let d=null;if(l==="modal"){let m=this.createModalWithSkeleton(e.onClose);r=m.overlay,d=m.container}else if(l==="iframe"){if(d=this.getEmbeddedContainer(e.container),!d)throw new Error("Container is required for iframe mode");d.innerHTML="";let m=document.createElement("recur-payment-form-skeleton");d.appendChild(m)}console.log("[Recur SDK] Step 1: Creating embedded checkout...");let v={customerName:e.customerName,customerEmail:e.customerEmail};s&&(v.productId=s),i&&(v.productSlug=i),e.externalCustomerId&&(v.externalCustomerId=e.externalCustomerId);let O=await fetch(`${a}/v1/checkouts`,{method:"POST",headers:o,body:JSON.stringify(v)});if(!O.ok){let m=await O.json().catch(()=>({}));console.error("[Recur SDK] Failed to create checkout:",m);let x=m.details||m.error||"Failed to create checkout";throw new Error(x)}let fe=await O.json(),u=f(fe);if(console.log("[Recur SDK] Checkout created successfully:",u),e.onSuccess?.(u),console.log("[Recur SDK] Step 2: Extracting SDK token..."),!u.sdkToken)throw new Error("SDK token missing from checkout response");console.log("[Recur SDK] Step 3: Loading PAYUNi SDK...");let Y=!0;if(await pe(Y),console.log("[Recur SDK] PAYUNi SDK loaded successfully"),!window.UniPayment)throw new Error("PAYUNi SDK failed to load");if(console.log("[Recur SDK] Step 4: Rendering payment form..."),!d)throw new Error("Payment container not available");d.innerHTML="";let h=document.createElement("recur-payment-form");if(h.setAttribute("container-id",d.id||"recur-payment-container"),e.customerName&&h.setAttribute("customer-name",e.customerName),e.customerEmail&&h.setAttribute("customer-email",e.customerEmail),u.plan?.name&&h.setAttribute("plan-name",u.plan.name),u.checkout?.amount&&h.setAttribute("amount",u.checkout.amount.toString()),u.plan?.billingPeriod&&h.setAttribute("billing-period",u.plan.billingPeriod),h.setAttribute("custom-styles",`
|
|
1530
1530
|
.form-input-focus {
|
|
1531
1531
|
border-color: var(--ring, hsl(215 16% 47%)) !important;
|
|
1532
1532
|
outline: 0 !important;
|
|
1533
1533
|
box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring, hsl(215 16% 47%)) 50%, transparent) !important;
|
|
1534
1534
|
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out !important;
|
|
1535
1535
|
}
|
|
1536
|
-
`),d.appendChild(h),await new Promise(m=>setTimeout(m,100)),console.log("[Recur SDK] Step 5: Initializing PAYUNi SDK..."),await h.initializePayment(u.sdkToken,Y?"SANDBOX":"PRODUCTION")===null){console.log("[Recur SDK] Initialization aborted");return}console.log("[Recur SDK] PAYUNi SDK initialized successfully"),console.log("[Recur SDK] Step 6: Setting up submit handler..."),h.addEventListener("submit",(async m=>{console.log("[Recur SDK] Form submitted");let x=m,{paymentSession:F}=x.detail;try{let g=await F.getTradeResult();console.log("[Recur SDK] Trade result received"),console.log("[Recur SDK] Executing payment...");let w=g.HashTimestamp||g.timestamp,V={};if(u.checkout.productType==="SUBSCRIPTION"){let E=u.creditToken,R=u.sdkTimestamp;if(!E)throw console.error("[Recur SDK] Missing creditToken from checkout for subscription"),new Error("Missing creditToken for subscription payment");V={creditToken:E,timestamp:R||w},console.log("[Recur SDK] Using creditToken from checkout:",E.substring(0,30)+"..."),console.log("[Recur SDK] Using timestamp:",R?"from checkout (sdkTimestamp)":"from tradeResult")}let j=await fetch(`${a}/v1/checkouts/${u.checkout.id}/pay`,{method:"POST",headers:
|
|
1536
|
+
`),d.appendChild(h),await new Promise(m=>setTimeout(m,100)),console.log("[Recur SDK] Step 5: Initializing PAYUNi SDK..."),await h.initializePayment(u.sdkToken,Y?"SANDBOX":"PRODUCTION")===null){console.log("[Recur SDK] Initialization aborted");return}console.log("[Recur SDK] PAYUNi SDK initialized successfully"),console.log("[Recur SDK] Step 6: Setting up submit handler..."),h.addEventListener("submit",(async m=>{console.log("[Recur SDK] Form submitted");let x=m,{paymentSession:F}=x.detail;try{let g=await F.getTradeResult();console.log("[Recur SDK] Trade result received"),console.log("[Recur SDK] Executing payment...");let w=g.HashTimestamp||g.timestamp,V={};if(u.checkout.productType==="SUBSCRIPTION"){let E=u.creditToken,R=u.sdkTimestamp;if(!E)throw console.error("[Recur SDK] Missing creditToken from checkout for subscription"),new Error("Missing creditToken for subscription payment");V={creditToken:E,timestamp:R||w},console.log("[Recur SDK] Using creditToken from checkout:",E.substring(0,30)+"..."),console.log("[Recur SDK] Using timestamp:",R?"from checkout (sdkTimestamp)":"from tradeResult")}let j=await fetch(`${a}/v1/checkouts/${u.checkout.id}/pay`,{method:"POST",headers:o,body:JSON.stringify(V)});if(!j.ok){let E=await j.json().catch(()=>({}));throw new Error(E.error||"Failed to execute payment")}let p=await j.json();if(console.log("[Recur SDK] Payment executed:",p),p.requires3D&&p.redirectUrl){console.log("[Recur SDK] 3D verification required"),window.location.href=p.redirectUrl;return}if(e.onPaymentComplete)if(p.subscription)e.onPaymentComplete({id:p.subscription.id,status:p.subscription.status,planId:u.checkout.productId,amount:u.checkout.amount,billingPeriod:p.subscription.billingPeriod,currentPeriodStart:p.subscription.currentPeriodStart,currentPeriodEnd:p.subscription.currentPeriodEnd});else{let E=p.charge?.id||p.paymentIntent?.id,R=p.charge?.status||"SUCCEEDED";e.onPaymentComplete({id:E,status:R,planId:u.checkout.productId,amount:u.checkout.amount,billingPeriod:u.checkout.productType})}console.log("[Recur SDK] Checkout flow completed successfully!"),h.resetButton?.(),r&&r.remove()}catch(g){console.error("[Recur SDK] Payment error:",g);let w={code:"PAYMENT_FAILED",message:g instanceof Error?g.message:"Payment failed"};e.onError?.(w),h.resetButton?.()}})),console.log("[Recur SDK] Checkout flow initialized, waiting for user input...")}catch(a){console.error("[Recur SDK] Checkout error:",a),r&&r.remove();let o={code:"CHECKOUT_ERROR",message:a instanceof Error?a.message:"An unknown error occurred"};throw e.onError?.(o),a}}getBaseUrl(){let e=this.core.getConfig();if(e.baseUrl)return e.baseUrl;if(typeof window<"u"){let t=window.location.hostname;if(t==="localhost"||t.includes(".test")||t.includes(".local")||t==="127.0.0.1")return`${window.location.protocol}//${window.location.host}/api`}return"https://api.recur.tw"}createModalWithSkeleton(e){this.closeModal();let t=document.createElement("div");t.id="recur-modal-overlay",t.style.cssText=`
|
|
1537
1537
|
position: fixed;
|
|
1538
1538
|
top: 0;
|
|
1539
1539
|
left: 0;
|
|
@@ -1554,7 +1554,7 @@
|
|
|
1554
1554
|
border-radius: 12px;
|
|
1555
1555
|
background: white;
|
|
1556
1556
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
|
|
1557
|
-
`;let
|
|
1557
|
+
`;let s=document.createElement("button");s.innerHTML="\u2715",s.style.cssText=`
|
|
1558
1558
|
position: absolute;
|
|
1559
1559
|
top: 12px;
|
|
1560
1560
|
right: 12px;
|
|
@@ -1572,7 +1572,7 @@
|
|
|
1572
1572
|
border-radius: 50%;
|
|
1573
1573
|
z-index: 10;
|
|
1574
1574
|
transition: background 0.2s;
|
|
1575
|
-
`,
|
|
1575
|
+
`,s.onmouseover=()=>{s.style.background="rgba(0, 0, 0, 0.1)"},s.onmouseout=()=>{s.style.background="rgba(0, 0, 0, 0.05)"},s.onclick=()=>{t.remove(),e?.()};let i=document.createElement("div");i.id="recur-modal-payment-container";let a=document.createElement("recur-payment-form-skeleton");return i.appendChild(a),r.appendChild(s),r.appendChild(i),t.appendChild(r),document.body.appendChild(t),this.currentModalOverlay=t,{overlay:t,container:i}}getEmbeddedContainer(e){return e?typeof e=="string"?document.getElementById(e)||document.querySelector(e):e:null}closeModal(){this.currentModal&&(this.currentModal.close(),this.currentModal=null),this.currentModalOverlay&&(this.currentModalOverlay.remove(),this.currentModalOverlay=null)}removeIframe(){this.currentIframe&&(this.currentIframe.remove(),this.currentIframe=null)}close(){this.closeModal(),this.removeIframe()}async createPortalSession(e,t){let r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({customerId:t.customerId,returnUrl:t.returnUrl})});if(!r.ok){let i=await r.json().catch(()=>({}));throw new Error(i.error?.message||i.message||"Failed to create portal session")}let s=await r.json();return{id:s.id,url:s.url||s.portalUrl,expiresAt:s.expiresAt}}async redirectToPortal(e,t){let r=await this.createPortalSession(e,t);window.location.href=r.url}};function he(n){return new I(n)}var Re={init:he,RecurCheckout:I,RecurElements:S,createElements:q};return xe(Pe);})();
|
|
1576
1576
|
if (typeof window !== "undefined") {
|
|
1577
1577
|
window.RecurCheckout = RecurCheckout.default;
|
|
1578
1578
|
window.RecurElements = RecurCheckout.RecurElements;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recur Server SDK Types
|
|
3
|
+
*/
|
|
4
|
+
interface RecurConfig {
|
|
5
|
+
/**
|
|
6
|
+
* Secret API key for authentication
|
|
7
|
+
* Get this from your organization settings
|
|
8
|
+
*
|
|
9
|
+
* @example 'sk_test_abc123...' or 'sk_live_xyz789...'
|
|
10
|
+
*/
|
|
11
|
+
secretKey: string;
|
|
12
|
+
/**
|
|
13
|
+
* Base URL for API calls
|
|
14
|
+
* Defaults to 'https://api.recur.tw'
|
|
15
|
+
*/
|
|
16
|
+
baseUrl?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Parameters for creating a portal session
|
|
20
|
+
*
|
|
21
|
+
* Customer Identification (at least one required):
|
|
22
|
+
* - customer: Internal customer ID in Recur (highest priority)
|
|
23
|
+
* - externalId: Customer's external ID from your system
|
|
24
|
+
* - email: Customer's email address (lowest priority)
|
|
25
|
+
*/
|
|
26
|
+
interface PortalSessionCreateParams {
|
|
27
|
+
/**
|
|
28
|
+
* The internal ID of the customer to create a portal session for
|
|
29
|
+
* Takes highest priority when multiple identifiers are provided
|
|
30
|
+
*/
|
|
31
|
+
customer?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Customer's email address
|
|
34
|
+
* Used to identify the customer if `customer` and `externalId` are not provided
|
|
35
|
+
*/
|
|
36
|
+
email?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Customer's external ID from your system
|
|
39
|
+
* Used to identify the customer if `customer` is not provided
|
|
40
|
+
*/
|
|
41
|
+
externalId?: string;
|
|
42
|
+
/**
|
|
43
|
+
* The URL to redirect the customer to when they exit the portal
|
|
44
|
+
* If not provided, uses the default return URL from portal configuration
|
|
45
|
+
*/
|
|
46
|
+
returnUrl?: string;
|
|
47
|
+
/**
|
|
48
|
+
* The ID of a specific portal configuration to use
|
|
49
|
+
* If not provided, uses the default configuration
|
|
50
|
+
*/
|
|
51
|
+
configuration?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Preferred locale for the portal
|
|
54
|
+
*/
|
|
55
|
+
locale?: 'zh-TW' | 'en';
|
|
56
|
+
}
|
|
57
|
+
interface PortalSession {
|
|
58
|
+
/**
|
|
59
|
+
* Unique identifier for the portal session
|
|
60
|
+
*/
|
|
61
|
+
id: string;
|
|
62
|
+
/**
|
|
63
|
+
* String representing the object's type
|
|
64
|
+
*/
|
|
65
|
+
object: 'portal.session';
|
|
66
|
+
/**
|
|
67
|
+
* The URL to redirect the customer to access the portal
|
|
68
|
+
*/
|
|
69
|
+
url: string;
|
|
70
|
+
/**
|
|
71
|
+
* The ID of the customer for this session
|
|
72
|
+
*/
|
|
73
|
+
customer: string;
|
|
74
|
+
/**
|
|
75
|
+
* The URL to redirect the customer when they exit the portal
|
|
76
|
+
*/
|
|
77
|
+
returnUrl: string;
|
|
78
|
+
/**
|
|
79
|
+
* The status of the session
|
|
80
|
+
*/
|
|
81
|
+
status: 'active' | 'expired';
|
|
82
|
+
/**
|
|
83
|
+
* Time at which the session expires (ISO 8601)
|
|
84
|
+
*/
|
|
85
|
+
expiresAt: string;
|
|
86
|
+
/**
|
|
87
|
+
* Time at which the session was last accessed (ISO 8601), or null
|
|
88
|
+
*/
|
|
89
|
+
accessedAt: string | null;
|
|
90
|
+
/**
|
|
91
|
+
* Time at which the session was created (ISO 8601)
|
|
92
|
+
*/
|
|
93
|
+
createdAt: string;
|
|
94
|
+
}
|
|
95
|
+
interface RecurError {
|
|
96
|
+
type: 'authentication_error' | 'invalid_request_error' | 'api_error';
|
|
97
|
+
code: string;
|
|
98
|
+
message: string;
|
|
99
|
+
details?: unknown;
|
|
100
|
+
}
|
|
101
|
+
declare class RecurAPIError extends Error {
|
|
102
|
+
readonly type: string;
|
|
103
|
+
readonly code: string;
|
|
104
|
+
readonly statusCode: number;
|
|
105
|
+
constructor(error: RecurError, statusCode: number);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Portal Sessions Resource
|
|
110
|
+
*
|
|
111
|
+
* Create portal sessions to allow customers to manage their subscriptions
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
declare class PortalSessions {
|
|
115
|
+
private config;
|
|
116
|
+
constructor(config: RecurConfig);
|
|
117
|
+
/**
|
|
118
|
+
* Create a portal session for a customer
|
|
119
|
+
*
|
|
120
|
+
* Customer can be identified using one of:
|
|
121
|
+
* - `customer`: Internal customer ID (highest priority)
|
|
122
|
+
* - `externalId`: External customer ID from your system
|
|
123
|
+
* - `email`: Customer's email address (lowest priority)
|
|
124
|
+
*
|
|
125
|
+
* @param params - Portal session creation parameters
|
|
126
|
+
* @returns The created portal session with URL
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```typescript
|
|
130
|
+
* // By customer ID
|
|
131
|
+
* const session = await recur.portal.sessions.create({
|
|
132
|
+
* customer: 'cus_xxx',
|
|
133
|
+
* returnUrl: 'https://myapp.com/account',
|
|
134
|
+
* });
|
|
135
|
+
*
|
|
136
|
+
* // By email
|
|
137
|
+
* const session = await recur.portal.sessions.create({
|
|
138
|
+
* email: 'customer@example.com',
|
|
139
|
+
* returnUrl: 'https://myapp.com/account',
|
|
140
|
+
* });
|
|
141
|
+
*
|
|
142
|
+
* // By external ID
|
|
143
|
+
* const session = await recur.portal.sessions.create({
|
|
144
|
+
* externalId: 'user_123',
|
|
145
|
+
* returnUrl: 'https://myapp.com/account',
|
|
146
|
+
* });
|
|
147
|
+
*
|
|
148
|
+
* // Redirect the customer to the portal
|
|
149
|
+
* redirect(session.url);
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
create(params: PortalSessionCreateParams): Promise<PortalSession>;
|
|
153
|
+
}
|
|
154
|
+
declare class Portal {
|
|
155
|
+
readonly sessions: PortalSessions;
|
|
156
|
+
constructor(config: RecurConfig);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Recur Server SDK
|
|
161
|
+
*
|
|
162
|
+
* Server-side SDK for interacting with the Recur API
|
|
163
|
+
* Use this in your backend (API routes, server actions, etc.)
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```typescript
|
|
167
|
+
* import { Recur } from 'recur-tw/server';
|
|
168
|
+
*
|
|
169
|
+
* const recur = new Recur(process.env.RECUR_SECRET_KEY!);
|
|
170
|
+
*
|
|
171
|
+
* // Create a portal session
|
|
172
|
+
* const session = await recur.portal.sessions.create({
|
|
173
|
+
* customer: 'cus_xxx',
|
|
174
|
+
* returnUrl: 'https://myapp.com/account',
|
|
175
|
+
* });
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
|
|
179
|
+
declare class Recur {
|
|
180
|
+
private config;
|
|
181
|
+
/**
|
|
182
|
+
* Portal resource for managing customer portal sessions
|
|
183
|
+
*/
|
|
184
|
+
readonly portal: Portal;
|
|
185
|
+
/**
|
|
186
|
+
* Create a new Recur client
|
|
187
|
+
*
|
|
188
|
+
* @param secretKey - Your Recur secret API key (sk_test_xxx or sk_live_xxx)
|
|
189
|
+
* @param options - Additional configuration options
|
|
190
|
+
*/
|
|
191
|
+
constructor(secretKey: string, options?: Omit<RecurConfig, 'secretKey'>);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export { type PortalSession, type PortalSessionCreateParams, Recur, RecurAPIError, type RecurConfig, type RecurError };
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recur Server SDK Types
|
|
3
|
+
*/
|
|
4
|
+
interface RecurConfig {
|
|
5
|
+
/**
|
|
6
|
+
* Secret API key for authentication
|
|
7
|
+
* Get this from your organization settings
|
|
8
|
+
*
|
|
9
|
+
* @example 'sk_test_abc123...' or 'sk_live_xyz789...'
|
|
10
|
+
*/
|
|
11
|
+
secretKey: string;
|
|
12
|
+
/**
|
|
13
|
+
* Base URL for API calls
|
|
14
|
+
* Defaults to 'https://api.recur.tw'
|
|
15
|
+
*/
|
|
16
|
+
baseUrl?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Parameters for creating a portal session
|
|
20
|
+
*
|
|
21
|
+
* Customer Identification (at least one required):
|
|
22
|
+
* - customer: Internal customer ID in Recur (highest priority)
|
|
23
|
+
* - externalId: Customer's external ID from your system
|
|
24
|
+
* - email: Customer's email address (lowest priority)
|
|
25
|
+
*/
|
|
26
|
+
interface PortalSessionCreateParams {
|
|
27
|
+
/**
|
|
28
|
+
* The internal ID of the customer to create a portal session for
|
|
29
|
+
* Takes highest priority when multiple identifiers are provided
|
|
30
|
+
*/
|
|
31
|
+
customer?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Customer's email address
|
|
34
|
+
* Used to identify the customer if `customer` and `externalId` are not provided
|
|
35
|
+
*/
|
|
36
|
+
email?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Customer's external ID from your system
|
|
39
|
+
* Used to identify the customer if `customer` is not provided
|
|
40
|
+
*/
|
|
41
|
+
externalId?: string;
|
|
42
|
+
/**
|
|
43
|
+
* The URL to redirect the customer to when they exit the portal
|
|
44
|
+
* If not provided, uses the default return URL from portal configuration
|
|
45
|
+
*/
|
|
46
|
+
returnUrl?: string;
|
|
47
|
+
/**
|
|
48
|
+
* The ID of a specific portal configuration to use
|
|
49
|
+
* If not provided, uses the default configuration
|
|
50
|
+
*/
|
|
51
|
+
configuration?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Preferred locale for the portal
|
|
54
|
+
*/
|
|
55
|
+
locale?: 'zh-TW' | 'en';
|
|
56
|
+
}
|
|
57
|
+
interface PortalSession {
|
|
58
|
+
/**
|
|
59
|
+
* Unique identifier for the portal session
|
|
60
|
+
*/
|
|
61
|
+
id: string;
|
|
62
|
+
/**
|
|
63
|
+
* String representing the object's type
|
|
64
|
+
*/
|
|
65
|
+
object: 'portal.session';
|
|
66
|
+
/**
|
|
67
|
+
* The URL to redirect the customer to access the portal
|
|
68
|
+
*/
|
|
69
|
+
url: string;
|
|
70
|
+
/**
|
|
71
|
+
* The ID of the customer for this session
|
|
72
|
+
*/
|
|
73
|
+
customer: string;
|
|
74
|
+
/**
|
|
75
|
+
* The URL to redirect the customer when they exit the portal
|
|
76
|
+
*/
|
|
77
|
+
returnUrl: string;
|
|
78
|
+
/**
|
|
79
|
+
* The status of the session
|
|
80
|
+
*/
|
|
81
|
+
status: 'active' | 'expired';
|
|
82
|
+
/**
|
|
83
|
+
* Time at which the session expires (ISO 8601)
|
|
84
|
+
*/
|
|
85
|
+
expiresAt: string;
|
|
86
|
+
/**
|
|
87
|
+
* Time at which the session was last accessed (ISO 8601), or null
|
|
88
|
+
*/
|
|
89
|
+
accessedAt: string | null;
|
|
90
|
+
/**
|
|
91
|
+
* Time at which the session was created (ISO 8601)
|
|
92
|
+
*/
|
|
93
|
+
createdAt: string;
|
|
94
|
+
}
|
|
95
|
+
interface RecurError {
|
|
96
|
+
type: 'authentication_error' | 'invalid_request_error' | 'api_error';
|
|
97
|
+
code: string;
|
|
98
|
+
message: string;
|
|
99
|
+
details?: unknown;
|
|
100
|
+
}
|
|
101
|
+
declare class RecurAPIError extends Error {
|
|
102
|
+
readonly type: string;
|
|
103
|
+
readonly code: string;
|
|
104
|
+
readonly statusCode: number;
|
|
105
|
+
constructor(error: RecurError, statusCode: number);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Portal Sessions Resource
|
|
110
|
+
*
|
|
111
|
+
* Create portal sessions to allow customers to manage their subscriptions
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
declare class PortalSessions {
|
|
115
|
+
private config;
|
|
116
|
+
constructor(config: RecurConfig);
|
|
117
|
+
/**
|
|
118
|
+
* Create a portal session for a customer
|
|
119
|
+
*
|
|
120
|
+
* Customer can be identified using one of:
|
|
121
|
+
* - `customer`: Internal customer ID (highest priority)
|
|
122
|
+
* - `externalId`: External customer ID from your system
|
|
123
|
+
* - `email`: Customer's email address (lowest priority)
|
|
124
|
+
*
|
|
125
|
+
* @param params - Portal session creation parameters
|
|
126
|
+
* @returns The created portal session with URL
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```typescript
|
|
130
|
+
* // By customer ID
|
|
131
|
+
* const session = await recur.portal.sessions.create({
|
|
132
|
+
* customer: 'cus_xxx',
|
|
133
|
+
* returnUrl: 'https://myapp.com/account',
|
|
134
|
+
* });
|
|
135
|
+
*
|
|
136
|
+
* // By email
|
|
137
|
+
* const session = await recur.portal.sessions.create({
|
|
138
|
+
* email: 'customer@example.com',
|
|
139
|
+
* returnUrl: 'https://myapp.com/account',
|
|
140
|
+
* });
|
|
141
|
+
*
|
|
142
|
+
* // By external ID
|
|
143
|
+
* const session = await recur.portal.sessions.create({
|
|
144
|
+
* externalId: 'user_123',
|
|
145
|
+
* returnUrl: 'https://myapp.com/account',
|
|
146
|
+
* });
|
|
147
|
+
*
|
|
148
|
+
* // Redirect the customer to the portal
|
|
149
|
+
* redirect(session.url);
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
create(params: PortalSessionCreateParams): Promise<PortalSession>;
|
|
153
|
+
}
|
|
154
|
+
declare class Portal {
|
|
155
|
+
readonly sessions: PortalSessions;
|
|
156
|
+
constructor(config: RecurConfig);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Recur Server SDK
|
|
161
|
+
*
|
|
162
|
+
* Server-side SDK for interacting with the Recur API
|
|
163
|
+
* Use this in your backend (API routes, server actions, etc.)
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```typescript
|
|
167
|
+
* import { Recur } from 'recur-tw/server';
|
|
168
|
+
*
|
|
169
|
+
* const recur = new Recur(process.env.RECUR_SECRET_KEY!);
|
|
170
|
+
*
|
|
171
|
+
* // Create a portal session
|
|
172
|
+
* const session = await recur.portal.sessions.create({
|
|
173
|
+
* customer: 'cus_xxx',
|
|
174
|
+
* returnUrl: 'https://myapp.com/account',
|
|
175
|
+
* });
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
|
|
179
|
+
declare class Recur {
|
|
180
|
+
private config;
|
|
181
|
+
/**
|
|
182
|
+
* Portal resource for managing customer portal sessions
|
|
183
|
+
*/
|
|
184
|
+
readonly portal: Portal;
|
|
185
|
+
/**
|
|
186
|
+
* Create a new Recur client
|
|
187
|
+
*
|
|
188
|
+
* @param secretKey - Your Recur secret API key (sk_test_xxx or sk_live_xxx)
|
|
189
|
+
* @param options - Additional configuration options
|
|
190
|
+
*/
|
|
191
|
+
constructor(secretKey: string, options?: Omit<RecurConfig, 'secretKey'>);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export { type PortalSession, type PortalSessionCreateParams, Recur, RecurAPIError, type RecurConfig, type RecurError };
|