recur-tw 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -455,9 +455,17 @@ await recur.redirectToCheckout({
455
455
  });
456
456
  ```
457
457
 
458
+ #### Required Fields
459
+
460
+ For customer identification, **at least one** of the following must be provided:
461
+ - `customerEmail` - Customer's email address
462
+ - `externalCustomerId` - Your system's user ID
463
+
464
+ The SDK will throw an error if neither is provided.
465
+
458
466
  #### Customer Resolution Priority
459
467
 
460
- When processing a checkout, the SDK uses the following priority to identify customers:
468
+ When processing a checkout, the system uses the following priority to identify customers:
461
469
 
462
470
  1. **externalCustomerId** - If provided, looks up customer by external ID first
463
471
  2. **customerEmail** - Falls back to email-based lookup
@@ -479,7 +487,7 @@ This allows you to query subscription status and details using your own user ide
479
487
 
480
488
  - **Use consistent IDs**: Use your database primary key or UUID as the external ID
481
489
  - **Set early**: Include `externalCustomerId` in the initial checkout request
482
- - **Combine with email**: Always provide both email and external ID for reliability
490
+ - **Provide both when possible**: Although only one is required, providing both email and external ID ensures maximum flexibility
483
491
 
484
492
  ---
485
493
 
package/dist/index.cjs CHANGED
@@ -2355,8 +2355,11 @@ function RecurProvider({ children, config: initialConfig = {} }) {
2355
2355
  if (!config.publishableKey) {
2356
2356
  throw new Error("publishableKey is required");
2357
2357
  }
2358
- if (!options.customerEmail || !options.customerName) {
2359
- throw new Error("customerEmail and customerName are required");
2358
+ if (!options.customerName) {
2359
+ throw new Error("customerName is required");
2360
+ }
2361
+ if (!options.customerEmail && !options.externalCustomerId) {
2362
+ throw new Error("Either customerEmail or externalCustomerId is required for customer identification");
2360
2363
  }
2361
2364
  const baseUrl = config.baseUrl || "https://api.recur.tw";
2362
2365
  console.log("[Recur SDK] Base URL:", baseUrl);
package/dist/index.js CHANGED
@@ -2349,8 +2349,11 @@ function RecurProvider({ children, config: initialConfig = {} }) {
2349
2349
  if (!config.publishableKey) {
2350
2350
  throw new Error("publishableKey is required");
2351
2351
  }
2352
- if (!options.customerEmail || !options.customerName) {
2353
- throw new Error("customerEmail and customerName are required");
2352
+ if (!options.customerName) {
2353
+ throw new Error("customerName is required");
2354
+ }
2355
+ if (!options.customerEmail && !options.externalCustomerId) {
2356
+ throw new Error("Either customerEmail or externalCustomerId is required for customer identification");
2354
2357
  }
2355
2358
  const baseUrl = config.baseUrl || "https://api.recur.tw";
2356
2359
  console.log("[Recur SDK] Base URL:", baseUrl);
package/dist/recur.umd.js CHANGED
@@ -1416,7 +1416,7 @@
1416
1416
  display: block;
1417
1417
  user-select: none;
1418
1418
  transition: height 0.35s ease, opacity 0.4s ease 0.1s;
1419
- `.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=l=>{clearTimeout(s),this.off("error",o),i(new Error(l.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,phone:e.phone,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 $(c){return new v(c)}var fe="https://vendor.payuni.com.tw/sdk/uni-payment.js",be="https://sandbox-vendor.payuni.com.tw/sdk/uni-payment.js",te=!1,D=!1,x=null;async function re(c=!1){return te&&window.UniPayment?Promise.resolve():(D&&x||(D=!0,x=new Promise((e,t)=>{let r=document.createElement("script");r.src=c?be:fe,r.async=!0,r.onload=()=>{te=!0,D=!1,e()},r.onerror=()=>{D=!1,x=null,t(new Error("Failed to load PAYUNi SDK"))},document.head.appendChild(r)})),x)}var w=class{constructor(e){a(this,"core");a(this,"currentModal",null);a(this,"currentIframe",null);a(this,"currentModalOverlay",null);this.core=new z(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 l=window.location.hostname;if(l==="localhost"||l.includes(".test")||l.includes(".local")||l==="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 l=await n.json().catch(()=>({}));throw new Error(l.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.customerEmail||!e.customerName)throw new Error("customerEmail and customerName are 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 l=await fetch(`${i}/v1/checkouts`,{method:"POST",headers:s,body:JSON.stringify({productId:e.planId,customerName:e.customerName,customerEmail:e.customerEmail,customerPhone:e.customerPhone,externalCustomerId:e.externalCustomerId})});if(!l.ok){let u=await l.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 d=await l.json();if(console.log("[Recur SDK] Checkout created successfully:",d),e.onSuccess?.(d),n==="redirect"){let u=`https://checkout.recur.tw/${d.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..."),!d.sdkToken)throw new Error("SDK token missing from checkout response");console.log("[Recur SDK] Step 3: Loading PAYUNi SDK...");let y=!0;if(await re(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..."),!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),d.plan?.name&&m.setAttribute("plan-name",d.plan.name),d.checkout?.amount&&m.setAttribute("amount",d.checkout.amount.toString()),d.plan?.billingPeriod&&m.setAttribute("billing-period",d.plan.billingPeriod),m.setAttribute("custom-styles",`
1419
+ `.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=l=>{clearTimeout(s),this.off("error",o),i(new Error(l.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,phone:e.phone,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 $(c){return new v(c)}var fe="https://vendor.payuni.com.tw/sdk/uni-payment.js",be="https://sandbox-vendor.payuni.com.tw/sdk/uni-payment.js",te=!1,D=!1,x=null;async function re(c=!1){return te&&window.UniPayment?Promise.resolve():(D&&x||(D=!0,x=new Promise((e,t)=>{let r=document.createElement("script");r.src=c?be:fe,r.async=!0,r.onload=()=>{te=!0,D=!1,e()},r.onerror=()=>{D=!1,x=null,t(new Error("Failed to load PAYUNi SDK"))},document.head.appendChild(r)})),x)}var w=class{constructor(e){a(this,"core");a(this,"currentModal",null);a(this,"currentIframe",null);a(this,"currentModalOverlay",null);this.core=new z(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 l=window.location.hostname;if(l==="localhost"||l.includes(".test")||l.includes(".local")||l==="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 l=await n.json().catch(()=>({}));throw new Error(l.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&&!e.externalCustomerId)throw new Error("Either customerEmail or externalCustomerId is required for customer identification");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 l=await fetch(`${i}/v1/checkouts`,{method:"POST",headers:s,body:JSON.stringify({productId:e.planId,customerName:e.customerName,customerEmail:e.customerEmail,customerPhone:e.customerPhone,externalCustomerId:e.externalCustomerId})});if(!l.ok){let u=await l.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 d=await l.json();if(console.log("[Recur SDK] Checkout created successfully:",d),e.onSuccess?.(d),n==="redirect"){let u=`https://checkout.recur.tw/${d.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..."),!d.sdkToken)throw new Error("SDK token missing from checkout response");console.log("[Recur SDK] Step 3: Loading PAYUNi SDK...");let y=!0;if(await re(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..."),!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),d.plan?.name&&m.setAttribute("plan-name",d.plan.name),d.checkout?.amount&&m.setAttribute("amount",d.checkout.amount.toString()),d.plan?.billingPeriod&&m.setAttribute("billing-period",d.plan.billingPeriod),m.setAttribute("custom-styles",`
1420
1420
  .form-input-focus {
1421
1421
  border-color: var(--ring, hsl(215 16% 47%)) !important;
1422
1422
  outline: 0 !important;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "recur-tw",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "React & Vanilla JS SDK for embedding subscription checkout flows (Taiwan / PAYUNi)",
5
5
  "type": "module",
6
6
  "private": false,