recur-tw 0.5.0 → 0.7.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/README.md +84 -49
- package/dist/index.cjs +27 -14
- package/dist/index.d.cts +52 -24
- package/dist/index.d.ts +52 -24
- package/dist/index.js +26 -14
- package/dist/recur.umd.js +7 -7
- package/dist/server.cjs +117 -0
- package/dist/server.d.cts +157 -0
- package/dist/server.d.ts +157 -0
- package/dist/server.js +114 -0
- package/package.json +8 -3
package/dist/index.js
CHANGED
|
@@ -2689,13 +2689,17 @@ function RecurProvider({ children, config: initialConfig = {} }) {
|
|
|
2689
2689
|
},
|
|
2690
2690
|
[config]
|
|
2691
2691
|
);
|
|
2692
|
-
const
|
|
2693
|
-
async () => {
|
|
2692
|
+
const fetchProducts = useCallback(
|
|
2693
|
+
async (options) => {
|
|
2694
2694
|
if (!config.publishableKey) {
|
|
2695
2695
|
throw new Error("publishableKey is required");
|
|
2696
2696
|
}
|
|
2697
2697
|
const baseUrl = config.baseUrl || "https://api.recur.tw";
|
|
2698
|
-
const
|
|
2698
|
+
const url = new URL(`${baseUrl}/v1/products`);
|
|
2699
|
+
if (options?.type) {
|
|
2700
|
+
url.searchParams.set("type", options.type);
|
|
2701
|
+
}
|
|
2702
|
+
const response = await fetch(url.toString(), {
|
|
2699
2703
|
method: "GET",
|
|
2700
2704
|
headers: {
|
|
2701
2705
|
"Content-Type": "application/json",
|
|
@@ -2704,21 +2708,29 @@ function RecurProvider({ children, config: initialConfig = {} }) {
|
|
|
2704
2708
|
});
|
|
2705
2709
|
if (!response.ok) {
|
|
2706
2710
|
const errorData = await response.json().catch(() => ({}));
|
|
2707
|
-
throw new Error(errorData.error || "Failed to fetch
|
|
2711
|
+
throw new Error(errorData.error || "Failed to fetch products");
|
|
2708
2712
|
}
|
|
2709
2713
|
return await response.json();
|
|
2710
2714
|
},
|
|
2711
2715
|
[config]
|
|
2712
2716
|
);
|
|
2717
|
+
const fetchPlans = useCallback(
|
|
2718
|
+
async () => {
|
|
2719
|
+
const result = await fetchProducts({ type: "SUBSCRIPTION" });
|
|
2720
|
+
return { plans: result.products };
|
|
2721
|
+
},
|
|
2722
|
+
[fetchProducts]
|
|
2723
|
+
);
|
|
2713
2724
|
const value = useMemo(
|
|
2714
2725
|
() => ({
|
|
2715
2726
|
config,
|
|
2716
2727
|
checkout,
|
|
2728
|
+
fetchProducts,
|
|
2717
2729
|
fetchPlans,
|
|
2718
2730
|
isCheckingOut,
|
|
2719
2731
|
updateConfig
|
|
2720
2732
|
}),
|
|
2721
|
-
[config, checkout, fetchPlans, isCheckingOut, updateConfig]
|
|
2733
|
+
[config, checkout, fetchProducts, fetchPlans, isCheckingOut, updateConfig]
|
|
2722
2734
|
);
|
|
2723
2735
|
return /* @__PURE__ */ jsx(RecurContext.Provider, { value, children });
|
|
2724
2736
|
}
|
|
@@ -2729,9 +2741,9 @@ function useRecur() {
|
|
|
2729
2741
|
}
|
|
2730
2742
|
return context;
|
|
2731
2743
|
}
|
|
2732
|
-
function
|
|
2733
|
-
const { enabled = true, onSuccess, onError } = options;
|
|
2734
|
-
const {
|
|
2744
|
+
function useProducts(options = {}) {
|
|
2745
|
+
const { enabled = true, type, onSuccess, onError } = options;
|
|
2746
|
+
const { fetchProducts } = useRecur();
|
|
2735
2747
|
const [data, setData] = useState(void 0);
|
|
2736
2748
|
const [isLoading2, setIsLoading] = useState(false);
|
|
2737
2749
|
const [error, setError] = useState(null);
|
|
@@ -2740,17 +2752,17 @@ function usePlans(options = {}) {
|
|
|
2740
2752
|
setIsLoading(true);
|
|
2741
2753
|
setError(null);
|
|
2742
2754
|
try {
|
|
2743
|
-
const result = await
|
|
2744
|
-
setData(result.
|
|
2745
|
-
onSuccess?.(result.
|
|
2755
|
+
const result = await fetchProducts({ type });
|
|
2756
|
+
setData(result.products);
|
|
2757
|
+
onSuccess?.(result.products);
|
|
2746
2758
|
} catch (err) {
|
|
2747
|
-
const error2 = err instanceof Error ? err : new Error("Failed to fetch
|
|
2759
|
+
const error2 = err instanceof Error ? err : new Error("Failed to fetch products");
|
|
2748
2760
|
setError(error2);
|
|
2749
2761
|
onError?.(error2);
|
|
2750
2762
|
} finally {
|
|
2751
2763
|
setIsLoading(false);
|
|
2752
2764
|
}
|
|
2753
|
-
}, [enabled,
|
|
2765
|
+
}, [enabled, type, fetchProducts, onSuccess, onError]);
|
|
2754
2766
|
useEffect(() => {
|
|
2755
2767
|
fetchData();
|
|
2756
2768
|
}, [fetchData]);
|
|
@@ -2808,4 +2820,4 @@ function useSubscribe(options = {}) {
|
|
|
2808
2820
|
};
|
|
2809
2821
|
}
|
|
2810
2822
|
|
|
2811
|
-
export { RecurProvider, usePlans, useRecur, useSubscribe };
|
|
2823
|
+
export { RecurProvider, useProducts as usePlans, useProducts, useRecur, useSubscribe };
|
package/dist/recur.umd.js
CHANGED
|
@@ -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",R)});var J={};h(J,{RecurPaymentFormSkeleton:()=>
|
|
380
|
+
`}};typeof window<"u"&&!customElements.get("recur-skeleton-loader")&&customElements.define("recur-skeleton-loader",R)});var J={};h(J,{RecurPaymentFormSkeleton:()=>P});var P,Z=b(()=>{"use strict";P=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",
|
|
647
|
+
`}};typeof window<"u"&&!customElements.get("recur-payment-form-skeleton")&&customElements.define("recur-payment-form-skeleton",P)});var G={};h(G,{RecurToast:()=>M,RecurToastContainer:()=>x});var M,f,x,Q=b(()=>{"use strict";M=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" />
|
|
@@ -791,7 +791,7 @@
|
|
|
791
791
|
</style>
|
|
792
792
|
|
|
793
793
|
<slot></slot>
|
|
794
|
-
`}static getInstance(){return f.instance||(f.instance=document.querySelector("recur-toast-container"),f.instance||(f.instance=document.createElement("recur-toast-container"),document.body.appendChild(f.instance))),f.instance}};a(f,"instance",null);x=f;typeof window<"u"&&!customElements.get("recur-toast")&&customElements.define("recur-toast",
|
|
794
|
+
`}static getInstance(){return f.instance||(f.instance=document.querySelector("recur-toast-container"),f.instance||(f.instance=document.createElement("recur-toast-container"),document.body.appendChild(f.instance))),f.instance}};a(f,"instance",null);x=f;typeof window<"u"&&!customElements.get("recur-toast")&&customElements.define("recur-toast",M);typeof window<"u"&&!customElements.get("recur-toast-container")&&customElements.define("recur-toast-container",x)});var ee={};h(ee,{RecurPaymentForm:()=>L});var L,te=b(()=>{"use strict";L=class extends HTMLElement{constructor(){super();a(this,"containerId");a(this,"customStyles");a(this,"_isInitializing",!1);a(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,i){t==="custom-styles"&&r!==i?(this.customStyles=i||"",this.updateCustomStyles()):r!==i&&this.updateCustomerInfoSection()}render(){this.shadowRoot.innerHTML=`
|
|
795
795
|
<style>
|
|
796
796
|
/* \u9019\u4E9B\u6A23\u5F0F\u88AB\u9694\u96E2\u5728 Shadow DOM \u5167\uFF0C\u4E0D\u6703\u5F71\u97FF\u5916\u90E8 */
|
|
797
797
|
:host {
|
|
@@ -1319,7 +1319,7 @@
|
|
|
1319
1319
|
${this._isLoading?'<span class="spinner"></span>':""}
|
|
1320
1320
|
<span>${this._isLoading?"\u8655\u7406\u4E2D...":t}</span>
|
|
1321
1321
|
</button>
|
|
1322
|
-
`}setupEventListeners(){let t=this.shadowRoot?.querySelector("button");t&&t.addEventListener("click",this.handleClick)}async createCheckoutSession(t){let r=this.getApiBaseUrl(),i={productId:t.productId,successUrl:t.successUrl,cancelUrl:t.cancelUrl};t.mode&&(i.mode=t.mode),t.customerEmail&&(i.customerEmail=t.customerEmail);let s=await fetch(`${r}/v1/checkout/sessions`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":t.publishableKey},body:JSON.stringify(i)});if(!s.ok){let n=await s.json().catch(()=>({}));throw new Error(n.error?.message||`HTTP ${s.status}: Failed to create checkout session`)}return s.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",U)});var ye={};h(ye,{RecurCheckout:()=>E,RecurElements:()=>y,createElements:()=>
|
|
1322
|
+
`}setupEventListeners(){let t=this.shadowRoot?.querySelector("button");t&&t.addEventListener("click",this.handleClick)}async createCheckoutSession(t){let r=this.getApiBaseUrl(),i={productId:t.productId,successUrl:t.successUrl,cancelUrl:t.cancelUrl};t.mode&&(i.mode=t.mode),t.customerEmail&&(i.customerEmail=t.customerEmail);let s=await fetch(`${r}/v1/checkout/sessions`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":t.publishableKey},body:JSON.stringify(i)});if(!s.ok){let n=await s.json().catch(()=>({}));throw new Error(n.error?.message||`HTTP ${s.status}: Failed to create checkout session`)}return s.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",U)});var ye={};h(ye,{RecurCheckout:()=>E,RecurElements:()=>y,createElements:()=>N,default:()=>ge,init:()=>ne});async function he(){if(typeof window>"u"){console.warn("[Recur SDK] Cannot register Web Components: window is not defined");return}await Promise.all([Promise.resolve().then(()=>(F(),O)),Promise.resolve().then(()=>(Y(),j)),Promise.resolve().then(()=>(V(),q)),Promise.resolve().then(()=>(W(),X)),Promise.resolve().then(()=>(Z(),J)),Promise.resolve().then(()=>(Q(),G)),Promise.resolve().then(()=>(te(),ee)),Promise.resolve().then(()=>(ie(),re))]);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"].filter(t=>!customElements.get(t));e.length>0&&console.warn("[Recur SDK] The following components are not registered:",e)}typeof window<"u"&&he();var D=class{constructor(e){a(this,"config");if(!e.publishableKey)throw new Error("publishableKey is required");this.config={baseUrl:"https://api.recur.tw",...e}}async createSubscription(e){let{planId:t,customerName:r,customerEmail:i}=e;if(!t)throw new Error("planId is required");let s=await fetch(`${this.config.baseUrl}/v1/subscriptions`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":this.config.publishableKey},body:JSON.stringify({planId:t,customerName:r,customerEmail:i})});if(!s.ok){let n=await s.json().catch(()=>({}));throw{code:n.error||"CHECKOUT_FAILED",message:n.message||"Failed to initiate checkout",details:n}}return await s.json()}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:{"Content-Type":"application/json","X-Recur-Publishable-Key":this.config.publishableKey}});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}}return await r.json()}async fetchPlans(){return{plans:(await this.fetchProducts({type:"SUBSCRIPTION"})).products}}getConfig(){return{...this.config}}};var _=class{constructor(e,t){a(this,"config");a(this,"options");a(this,"container");a(this,"checkoutId",null);a(this,"sdkToken",null);a(this,"sdkEnv","S");a(this,"payuniSDK",null);a(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();this.checkoutId=r.checkout.id,this.sdkToken=r.sdkToken,this.sdkEnv=this.config.publishableKey.startsWith("pk_test_")?"S":"P"}renderHTML(){this.container.innerHTML=`
|
|
1323
1323
|
<div class="recur-embedded-checkout" style="max-width: 400px; margin: 0 auto; padding: 20px; font-family: system-ui, -apple-system, sans-serif;">
|
|
1324
1324
|
<div class="recur-checkout-header" style="margin-bottom: 24px;">
|
|
1325
1325
|
<h2 style="margin: 0 0 8px 0; font-size: 20px; font-weight: 600; color: #111;">Subscribe</h2>
|
|
@@ -1396,14 +1396,14 @@
|
|
|
1396
1396
|
display: block;
|
|
1397
1397
|
user-select: none;
|
|
1398
1398
|
transition: height 0.35s ease, opacity 0.4s ease 0.1s;
|
|
1399
|
-
`.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,i)=>{let s=setTimeout(()=>{i(new Error("Elements initialization timeout"))},3e4),n=()=>{clearTimeout(s),this.off("ready",n),r()},o=d=>{clearTimeout(s),this.off("error",o),i(new Error(d.message||"Elements initialization failed"))};this.on("ready",n),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(i=>i(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.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 i=setTimeout(()=>{r(new Error("Tokenization timeout"))},6e4),s=o=>{clearTimeout(i),this.off("tokenized",s),t(o)},n=o=>{clearTimeout(i),this.off("error",n),r(new Error(o.message||"Tokenization failed"))};this.on("tokenized",s),this.on("error",n)})}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,sdkToken:this.cardToken,timestamp:this.cardTimestamp,creditToken:this.creditToken,productId:e.productId,email:e.email,name:e.name,metadata:e.metadata})}),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.cardToken=null,this.cardTimestamp=null,this.eventHandlers.clear(),window.removeEventListener("message",this.handleMessage.bind(this))}};function
|
|
1399
|
+
`.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,i)=>{let s=setTimeout(()=>{i(new Error("Elements initialization timeout"))},3e4),n=()=>{clearTimeout(s),this.off("ready",n),r()},o=d=>{clearTimeout(s),this.off("error",o),i(new Error(d.message||"Elements initialization failed"))};this.on("ready",n),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(i=>i(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.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 i=setTimeout(()=>{r(new Error("Tokenization timeout"))},6e4),s=o=>{clearTimeout(i),this.off("tokenized",s),t(o)},n=o=>{clearTimeout(i),this.off("error",n),r(new Error(o.message||"Tokenization failed"))};this.on("tokenized",s),this.on("error",n)})}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,sdkToken:this.cardToken,timestamp:this.cardTimestamp,creditToken:this.creditToken,productId:e.productId,email:e.email,name:e.name,metadata:e.metadata})}),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.cardToken=null,this.cardTimestamp=null,this.eventHandlers.clear(),window.removeEventListener("message",this.handleMessage.bind(this))}};function N(c){return new y(c)}var fe="https://vendor.payuni.com.tw/sdk/uni-payment.js",be="https://sandbox-vendor.payuni.com.tw/sdk/uni-payment.js",se=!1,z=!1,w=null;async function oe(c=!1){return se&&window.UniPayment?Promise.resolve():(z&&w||(z=!0,w=new Promise((e,t)=>{let r=document.createElement("script");r.src=c?be:fe,r.async=!0,r.onload=()=>{se=!0,z=!1,e()},r.onerror=()=>{z=!1,w=null,t(new Error("Failed to load PAYUNi SDK"))},document.head.appendChild(r)})),w)}var E=class{constructor(e){a(this,"core");a(this,"currentModal",null);a(this,"currentIframe",null);a(this,"currentModalOverlay",null);this.core=new D(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 _(t,e).render()}async redirectToCheckout(e){let t=this.core.getConfig(),r=()=>{if(typeof window<"u"){let d=window.location.hostname;if(d==="localhost"||d.includes(".test")||d.includes(".local")||d==="127.0.0.1")return`${window.location.protocol}//${window.location.host}/api`}return"https://api.recur.tw"},i=t.baseUrl||r(),s={productId:e.productId,successUrl:e.successUrl,cancelUrl:e.cancelUrl};e.mode&&(s.mode=e.mode),e.customerEmail&&(s.customerEmail=e.customerEmail),e.externalCustomerId&&(s.externalCustomerId=e.externalCustomerId);let n=await fetch(`${i}/v1/checkout/sessions`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":t.publishableKey},body:JSON.stringify(s)});if(!n.ok){let d=await n.json().catch(()=>({}));throw new Error(d.error?.message||"Failed to create checkout session")}let o=await n.json();window.location.href=o.url}async createCheckoutSession(e){let t=this.core.getConfig(),r=()=>{if(typeof window<"u"){let o=window.location.hostname;if(o==="localhost"||o.includes(".test")||o.includes(".local")||o==="127.0.0.1")return`${window.location.protocol}//${window.location.host}/api`}return"https://api.recur.tw"},i=t.baseUrl||r(),s={productId:e.productId,successUrl:e.successUrl,cancelUrl:e.cancelUrl};e.mode&&(s.mode=e.mode),e.customerEmail&&(s.customerEmail=e.customerEmail),e.externalCustomerId&&(s.externalCustomerId=e.externalCustomerId);let n=await fetch(`${i}/v1/checkout/sessions`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":t.publishableKey},body:JSON.stringify(s)});if(!n.ok){let o=await n.json().catch(()=>({}));throw new Error(o.error?.message||"Failed to create checkout session")}return n.json()}async checkout(e){let t=this.core.getConfig(),r=null;try{if(console.log("[Recur SDK] Starting checkout flow...",{planId:e.planId,mode:e.mode}),!e.planId)throw new Error("planId is required");if(!t.publishableKey)throw new Error("publishableKey is required");if(!e.customerName)throw new Error("customerName is required");if(!e.customerEmail)throw new Error("customerEmail is required");let i=this.getBaseUrl();console.log("[Recur SDK] Base URL:",i);let s={"Content-Type":"application/json","X-Recur-Publishable-Key":t.publishableKey},n=e.mode||"modal",o=null;if(n==="modal"){let u=this.createModalWithSkeleton(e.onClose);r=u.overlay,o=u.container}else if(n==="iframe"){if(o=this.getEmbeddedContainer(e.container),!o)throw new Error("Container is required for iframe mode");o.innerHTML="";let u=document.createElement("recur-payment-form-skeleton");o.appendChild(u)}console.log("[Recur SDK] Step 1: Creating checkout session...");let d=await fetch(`${i}/v1/checkouts`,{method:"POST",headers:s,body:JSON.stringify({productId:e.planId,customerName:e.customerName,customerEmail:e.customerEmail,externalCustomerId:e.externalCustomerId})});if(!d.ok){let u=await d.json().catch(()=>({}));console.error("[Recur SDK] Failed to create checkout:",u);let A=u.details||u.error||"Failed to create checkout";throw new Error(A)}let l=await d.json();if(console.log("[Recur SDK] Checkout created successfully:",l),e.onSuccess?.(l),n==="redirect"){let u=`https://checkout.recur.tw/${l.checkout.id}`;console.log("[Recur SDK] Redirecting to hosted checkout:",u),window.location.href=u;return}if(console.log("[Recur SDK] Step 2: Extracting SDK token..."),!l.sdkToken)throw new Error("SDK token missing from checkout response");console.log("[Recur SDK] Step 3: Loading PAYUNi SDK...");let v=!0;if(await oe(v),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..."),!o)throw new Error("Payment container not available");o.innerHTML="";let m=document.createElement("recur-payment-form");if(m.setAttribute("container-id",o.id||"recur-payment-container"),e.customerName&&m.setAttribute("customer-name",e.customerName),e.customerEmail&&m.setAttribute("customer-email",e.customerEmail),l.plan?.name&&m.setAttribute("plan-name",l.plan.name),l.checkout?.amount&&m.setAttribute("amount",l.checkout.amount.toString()),l.plan?.billingPeriod&&m.setAttribute("billing-period",l.plan.billingPeriod),m.setAttribute("custom-styles",`
|
|
1400
1400
|
.form-input-focus {
|
|
1401
1401
|
border-color: var(--ring, hsl(215 16% 47%)) !important;
|
|
1402
1402
|
outline: 0 !important;
|
|
1403
1403
|
box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring, hsl(215 16% 47%)) 50%, transparent) !important;
|
|
1404
1404
|
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out !important;
|
|
1405
1405
|
}
|
|
1406
|
-
`),o.appendChild(m),await new Promise(u=>setTimeout(u,100)),console.log("[Recur SDK] Step 5: Initializing PAYUNi SDK..."),await m.initializePayment(l.sdkToken,v?"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..."),m.addEventListener("submit",(async u=>{console.log("[Recur SDK] Form submitted");let A=u,{paymentSession:ae}=A.detail;try{let g=await ae.getTradeResult();console.log("[Recur SDK] Trade result received"),console.log("[Recur SDK] Executing payment...");let H=g.HashTimestamp||g.timestamp,
|
|
1406
|
+
`),o.appendChild(m),await new Promise(u=>setTimeout(u,100)),console.log("[Recur SDK] Step 5: Initializing PAYUNi SDK..."),await m.initializePayment(l.sdkToken,v?"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..."),m.addEventListener("submit",(async u=>{console.log("[Recur SDK] Form submitted");let A=u,{paymentSession:ae}=A.detail;try{let g=await ae.getTradeResult();console.log("[Recur SDK] Trade result received"),console.log("[Recur SDK] Executing payment...");let H=g.HashTimestamp||g.timestamp,B={};if(l.checkout.productType==="SUBSCRIPTION"){let k=l.creditToken,K=l.sdkTimestamp;if(!k)throw console.error("[Recur SDK] Missing creditToken from checkout for subscription"),new Error("Missing creditToken for subscription payment");B={creditToken:k,timestamp:K||H},console.log("[Recur SDK] Using creditToken from checkout:",k.substring(0,30)+"..."),console.log("[Recur SDK] Using timestamp:",K?"from checkout (sdkTimestamp)":"from tradeResult")}let $=await fetch(`${i}/v1/checkouts/${l.checkout.id}/pay`,{method:"POST",headers:s,body:JSON.stringify(B)});if(!$.ok){let k=await $.json().catch(()=>({}));throw new Error(k.error||"Failed to execute payment")}let p=await $.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}e.onPaymentComplete&&(p.subscription?e.onPaymentComplete({id:p.subscription.id,status:p.subscription.status,planId:l.checkout.productId,amount:l.checkout.amount,billingPeriod:p.subscription.billingPeriod,currentPeriodStart:p.subscription.currentPeriodStart,currentPeriodEnd:p.subscription.currentPeriodEnd}):e.onPaymentComplete({id:p.checkout.id,status:p.checkout.status,planId:l.checkout.productId,amount:l.checkout.amount,billingPeriod:l.checkout.productType})),console.log("[Recur SDK] Checkout flow completed successfully!"),m.resetButton?.(),r&&r.remove()}catch(g){console.error("[Recur SDK] Payment error:",g);let H={code:"PAYMENT_FAILED",message:g instanceof Error?g.message:"Payment failed"};e.onError?.(H),m.resetButton?.()}})),console.log("[Recur SDK] Checkout flow initialized, waiting for user input...")}catch(i){console.error("[Recur SDK] Checkout error:",i),r&&r.remove();let s={code:"CHECKOUT_ERROR",message:i instanceof Error?i.message:"An unknown error occurred"};throw e.onError?.(s),i}}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=`
|
|
1407
1407
|
position: fixed;
|
|
1408
1408
|
top: 0;
|
|
1409
1409
|
left: 0;
|
|
@@ -1442,7 +1442,7 @@
|
|
|
1442
1442
|
background: white;
|
|
1443
1443
|
border-radius: 12px;
|
|
1444
1444
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
1445
|
-
`;let n=document.createElement("recur-payment-form-skeleton");return s.appendChild(n),r.appendChild(i),r.appendChild(s),t.appendChild(r),document.body.appendChild(t),this.currentModalOverlay=t,{overlay:t,container:s}}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()}};function ne(c){return new E(c)}var ge={init:ne,RecurCheckout:E,RecurElements:y,createElements:
|
|
1445
|
+
`;let n=document.createElement("recur-payment-form-skeleton");return s.appendChild(n),r.appendChild(i),r.appendChild(s),t.appendChild(r),document.body.appendChild(t),this.currentModalOverlay=t,{overlay:t,container:s}}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()}};function ne(c){return new E(c)}var ge={init:ne,RecurCheckout:E,RecurElements:y,createElements:N};return pe(ye);})();
|
|
1446
1446
|
if (typeof window !== "undefined") {
|
|
1447
1447
|
window.RecurCheckout = RecurCheckout.default;
|
|
1448
1448
|
window.RecurElements = RecurCheckout.RecurElements;
|
package/dist/server.cjs
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
5
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
6
|
+
|
|
7
|
+
// src/server/types.ts
|
|
8
|
+
var RecurAPIError = class extends Error {
|
|
9
|
+
constructor(error, statusCode) {
|
|
10
|
+
super(error.message);
|
|
11
|
+
__publicField(this, "type");
|
|
12
|
+
__publicField(this, "code");
|
|
13
|
+
__publicField(this, "statusCode");
|
|
14
|
+
this.name = "RecurAPIError";
|
|
15
|
+
this.type = error.type;
|
|
16
|
+
this.code = error.code;
|
|
17
|
+
this.statusCode = statusCode;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// src/server/resources/portal.ts
|
|
22
|
+
var PortalSessions = class {
|
|
23
|
+
constructor(config) {
|
|
24
|
+
__publicField(this, "config");
|
|
25
|
+
this.config = config;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create a portal session for a customer
|
|
29
|
+
*
|
|
30
|
+
* @param params - Portal session creation parameters
|
|
31
|
+
* @returns The created portal session with URL
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* const session = await recur.portal.sessions.create({
|
|
36
|
+
* customer: 'cus_xxx',
|
|
37
|
+
* returnUrl: 'https://myapp.com/account',
|
|
38
|
+
* });
|
|
39
|
+
*
|
|
40
|
+
* // Redirect the customer to the portal
|
|
41
|
+
* redirect(session.url);
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
async create(params) {
|
|
45
|
+
const baseUrl = this.config.baseUrl || "https://api.recur.tw";
|
|
46
|
+
const response = await fetch(`${baseUrl}/v1/portal/sessions`, {
|
|
47
|
+
method: "POST",
|
|
48
|
+
headers: {
|
|
49
|
+
"Authorization": `Bearer ${this.config.secretKey}`,
|
|
50
|
+
"Content-Type": "application/json"
|
|
51
|
+
},
|
|
52
|
+
body: JSON.stringify({
|
|
53
|
+
customerId: params.customer,
|
|
54
|
+
returnUrl: params.returnUrl,
|
|
55
|
+
configurationId: params.configuration,
|
|
56
|
+
locale: params.locale
|
|
57
|
+
})
|
|
58
|
+
});
|
|
59
|
+
const data = await response.json();
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
const error = data.error;
|
|
62
|
+
throw new RecurAPIError(error, response.status);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
id: data.id,
|
|
66
|
+
object: "portal.session",
|
|
67
|
+
url: data.url,
|
|
68
|
+
customer: data.customerId,
|
|
69
|
+
returnUrl: data.returnUrl,
|
|
70
|
+
status: data.status,
|
|
71
|
+
expiresAt: data.expiresAt,
|
|
72
|
+
accessedAt: data.accessedAt,
|
|
73
|
+
createdAt: data.createdAt
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
var Portal = class {
|
|
78
|
+
constructor(config) {
|
|
79
|
+
__publicField(this, "sessions");
|
|
80
|
+
this.sessions = new PortalSessions(config);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// src/server/recur.ts
|
|
85
|
+
var Recur = class {
|
|
86
|
+
/**
|
|
87
|
+
* Create a new Recur client
|
|
88
|
+
*
|
|
89
|
+
* @param secretKey - Your Recur secret API key (sk_test_xxx or sk_live_xxx)
|
|
90
|
+
* @param options - Additional configuration options
|
|
91
|
+
*/
|
|
92
|
+
constructor(secretKey, options) {
|
|
93
|
+
__publicField(this, "config");
|
|
94
|
+
/**
|
|
95
|
+
* Portal resource for managing customer portal sessions
|
|
96
|
+
*/
|
|
97
|
+
__publicField(this, "portal");
|
|
98
|
+
if (!secretKey) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
"Recur: secretKey is required. Get your API key from your organization settings."
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
if (!secretKey.startsWith("sk_")) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
'Recur: Invalid API key format. Secret keys should start with "sk_". Use your secret key (sk_xxx), not your publishable key (pk_xxx).'
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
this.config = {
|
|
109
|
+
secretKey,
|
|
110
|
+
baseUrl: options?.baseUrl || "https://api.recur.tw"
|
|
111
|
+
};
|
|
112
|
+
this.portal = new Portal(this.config);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
exports.Recur = Recur;
|
|
117
|
+
exports.RecurAPIError = RecurAPIError;
|
|
@@ -0,0 +1,157 @@
|
|
|
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
|
+
interface PortalSessionCreateParams {
|
|
19
|
+
/**
|
|
20
|
+
* The ID of the customer to create a portal session for
|
|
21
|
+
*/
|
|
22
|
+
customer: string;
|
|
23
|
+
/**
|
|
24
|
+
* The URL to redirect the customer to when they exit the portal
|
|
25
|
+
* If not provided, uses the default return URL from portal configuration
|
|
26
|
+
*/
|
|
27
|
+
returnUrl?: string;
|
|
28
|
+
/**
|
|
29
|
+
* The ID of a specific portal configuration to use
|
|
30
|
+
* If not provided, uses the default configuration
|
|
31
|
+
*/
|
|
32
|
+
configuration?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Preferred locale for the portal
|
|
35
|
+
*/
|
|
36
|
+
locale?: 'zh-TW' | 'en';
|
|
37
|
+
}
|
|
38
|
+
interface PortalSession {
|
|
39
|
+
/**
|
|
40
|
+
* Unique identifier for the portal session
|
|
41
|
+
*/
|
|
42
|
+
id: string;
|
|
43
|
+
/**
|
|
44
|
+
* String representing the object's type
|
|
45
|
+
*/
|
|
46
|
+
object: 'portal.session';
|
|
47
|
+
/**
|
|
48
|
+
* The URL to redirect the customer to access the portal
|
|
49
|
+
*/
|
|
50
|
+
url: string;
|
|
51
|
+
/**
|
|
52
|
+
* The ID of the customer for this session
|
|
53
|
+
*/
|
|
54
|
+
customer: string;
|
|
55
|
+
/**
|
|
56
|
+
* The URL to redirect the customer when they exit the portal
|
|
57
|
+
*/
|
|
58
|
+
returnUrl: string;
|
|
59
|
+
/**
|
|
60
|
+
* The status of the session
|
|
61
|
+
*/
|
|
62
|
+
status: 'active' | 'expired';
|
|
63
|
+
/**
|
|
64
|
+
* Time at which the session expires (ISO 8601)
|
|
65
|
+
*/
|
|
66
|
+
expiresAt: string;
|
|
67
|
+
/**
|
|
68
|
+
* Time at which the session was last accessed (ISO 8601), or null
|
|
69
|
+
*/
|
|
70
|
+
accessedAt: string | null;
|
|
71
|
+
/**
|
|
72
|
+
* Time at which the session was created (ISO 8601)
|
|
73
|
+
*/
|
|
74
|
+
createdAt: string;
|
|
75
|
+
}
|
|
76
|
+
interface RecurError {
|
|
77
|
+
type: 'authentication_error' | 'invalid_request_error' | 'api_error';
|
|
78
|
+
code: string;
|
|
79
|
+
message: string;
|
|
80
|
+
details?: unknown;
|
|
81
|
+
}
|
|
82
|
+
declare class RecurAPIError extends Error {
|
|
83
|
+
readonly type: string;
|
|
84
|
+
readonly code: string;
|
|
85
|
+
readonly statusCode: number;
|
|
86
|
+
constructor(error: RecurError, statusCode: number);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Portal Sessions Resource
|
|
91
|
+
*
|
|
92
|
+
* Create portal sessions to allow customers to manage their subscriptions
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
declare class PortalSessions {
|
|
96
|
+
private config;
|
|
97
|
+
constructor(config: RecurConfig);
|
|
98
|
+
/**
|
|
99
|
+
* Create a portal session for a customer
|
|
100
|
+
*
|
|
101
|
+
* @param params - Portal session creation parameters
|
|
102
|
+
* @returns The created portal session with URL
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* const session = await recur.portal.sessions.create({
|
|
107
|
+
* customer: 'cus_xxx',
|
|
108
|
+
* returnUrl: 'https://myapp.com/account',
|
|
109
|
+
* });
|
|
110
|
+
*
|
|
111
|
+
* // Redirect the customer to the portal
|
|
112
|
+
* redirect(session.url);
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
create(params: PortalSessionCreateParams): Promise<PortalSession>;
|
|
116
|
+
}
|
|
117
|
+
declare class Portal {
|
|
118
|
+
readonly sessions: PortalSessions;
|
|
119
|
+
constructor(config: RecurConfig);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Recur Server SDK
|
|
124
|
+
*
|
|
125
|
+
* Server-side SDK for interacting with the Recur API
|
|
126
|
+
* Use this in your backend (API routes, server actions, etc.)
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```typescript
|
|
130
|
+
* import { Recur } from 'recur-tw/server';
|
|
131
|
+
*
|
|
132
|
+
* const recur = new Recur(process.env.RECUR_SECRET_KEY!);
|
|
133
|
+
*
|
|
134
|
+
* // Create a portal session
|
|
135
|
+
* const session = await recur.portal.sessions.create({
|
|
136
|
+
* customer: 'cus_xxx',
|
|
137
|
+
* returnUrl: 'https://myapp.com/account',
|
|
138
|
+
* });
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
|
|
142
|
+
declare class Recur {
|
|
143
|
+
private config;
|
|
144
|
+
/**
|
|
145
|
+
* Portal resource for managing customer portal sessions
|
|
146
|
+
*/
|
|
147
|
+
readonly portal: Portal;
|
|
148
|
+
/**
|
|
149
|
+
* Create a new Recur client
|
|
150
|
+
*
|
|
151
|
+
* @param secretKey - Your Recur secret API key (sk_test_xxx or sk_live_xxx)
|
|
152
|
+
* @param options - Additional configuration options
|
|
153
|
+
*/
|
|
154
|
+
constructor(secretKey: string, options?: Omit<RecurConfig, 'secretKey'>);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export { type PortalSession, type PortalSessionCreateParams, Recur, RecurAPIError, type RecurConfig, type RecurError };
|