recur-tw 0.9.3 → 0.9.5

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 CHANGED
@@ -1232,6 +1232,8 @@ var init_payment_form = __esm({
1232
1232
  background: white;
1233
1233
  border-radius: 12px;
1234
1234
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
1235
+ /* Prevent double-tap zoom on iOS */
1236
+ touch-action: manipulation;
1235
1237
  }
1236
1238
 
1237
1239
  .form-header {
@@ -1527,7 +1529,7 @@ var init_payment_form = __esm({
1527
1529
  .recur-form-input {
1528
1530
  width: 100%;
1529
1531
  padding: 10px 12px;
1530
- font-size: 14px;
1532
+ font-size: 16px; /* 16px prevents iOS Safari auto-zoom on focus */
1531
1533
  border: 1px solid #d1d5db;
1532
1534
  border-radius: 6px;
1533
1535
  transition: border-color 0.2s;
@@ -1594,6 +1596,8 @@ var init_payment_form = __esm({
1594
1596
  width: 100%;
1595
1597
  max-width: 100%;
1596
1598
  box-sizing: border-box;
1599
+ /* Help reduce iOS zoom issues */
1600
+ touch-action: manipulation;
1597
1601
  }
1598
1602
 
1599
1603
  .recur-card-row {
@@ -1607,6 +1611,8 @@ var init_payment_form = __esm({
1607
1611
  /* Container needs relative positioning for skeleton overlay */
1608
1612
  .recur-card-field-container {
1609
1613
  position: relative;
1614
+ /* Help reduce iOS zoom issues */
1615
+ touch-action: manipulation;
1610
1616
  }
1611
1617
 
1612
1618
  /* Loading skeleton for card fields - shown until PAYUNi iframe loads */
@@ -2619,7 +2625,7 @@ function toCamelCase(obj) {
2619
2625
 
2620
2626
  // package.json
2621
2627
  var package_default = {
2622
- version: "0.9.3"};
2628
+ version: "0.9.5"};
2623
2629
  var SDK_VERSION = package_default.version;
2624
2630
  var SDK_TYPE = "react";
2625
2631
  var RecurContext = React.createContext(null);
@@ -2783,6 +2789,8 @@ function RecurProvider({ children, config: initialConfig = {} }) {
2783
2789
  if (productId) checkoutRequestBody.productId = productId;
2784
2790
  if (productSlug) checkoutRequestBody.productSlug = productSlug;
2785
2791
  if (options.externalCustomerId) checkoutRequestBody.externalCustomerId = options.externalCustomerId;
2792
+ if (options.successUrl) checkoutRequestBody.successUrl = options.successUrl;
2793
+ if (options.cancelUrl) checkoutRequestBody.cancelUrl = options.cancelUrl;
2786
2794
  const checkoutResponse = await fetch(`${baseUrl}/v1/checkouts`, {
2787
2795
  method: "POST",
2788
2796
  headers,
package/dist/index.d.cts CHANGED
@@ -244,6 +244,19 @@ interface CheckoutOptions {
244
244
  * @example 'user_123', 'cus_abc456'
245
245
  */
246
246
  externalCustomerId?: string;
247
+ /**
248
+ * URL to redirect after successful payment (recommended for mobile)
249
+ * Enables redirect after 3D verification in mobile environments.
250
+ *
251
+ * @example 'https://yoursite.com/success'
252
+ */
253
+ successUrl?: string;
254
+ /**
255
+ * URL to redirect if customer cancels (recommended for mobile)
256
+ *
257
+ * @example 'https://yoursite.com/cancel'
258
+ */
259
+ cancelUrl?: string;
247
260
  /**
248
261
  * Override organization ID
249
262
  */
package/dist/index.d.ts CHANGED
@@ -244,6 +244,19 @@ interface CheckoutOptions {
244
244
  * @example 'user_123', 'cus_abc456'
245
245
  */
246
246
  externalCustomerId?: string;
247
+ /**
248
+ * URL to redirect after successful payment (recommended for mobile)
249
+ * Enables redirect after 3D verification in mobile environments.
250
+ *
251
+ * @example 'https://yoursite.com/success'
252
+ */
253
+ successUrl?: string;
254
+ /**
255
+ * URL to redirect if customer cancels (recommended for mobile)
256
+ *
257
+ * @example 'https://yoursite.com/cancel'
258
+ */
259
+ cancelUrl?: string;
247
260
  /**
248
261
  * Override organization ID
249
262
  */
package/dist/index.js CHANGED
@@ -1226,6 +1226,8 @@ var init_payment_form = __esm({
1226
1226
  background: white;
1227
1227
  border-radius: 12px;
1228
1228
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
1229
+ /* Prevent double-tap zoom on iOS */
1230
+ touch-action: manipulation;
1229
1231
  }
1230
1232
 
1231
1233
  .form-header {
@@ -1521,7 +1523,7 @@ var init_payment_form = __esm({
1521
1523
  .recur-form-input {
1522
1524
  width: 100%;
1523
1525
  padding: 10px 12px;
1524
- font-size: 14px;
1526
+ font-size: 16px; /* 16px prevents iOS Safari auto-zoom on focus */
1525
1527
  border: 1px solid #d1d5db;
1526
1528
  border-radius: 6px;
1527
1529
  transition: border-color 0.2s;
@@ -1588,6 +1590,8 @@ var init_payment_form = __esm({
1588
1590
  width: 100%;
1589
1591
  max-width: 100%;
1590
1592
  box-sizing: border-box;
1593
+ /* Help reduce iOS zoom issues */
1594
+ touch-action: manipulation;
1591
1595
  }
1592
1596
 
1593
1597
  .recur-card-row {
@@ -1601,6 +1605,8 @@ var init_payment_form = __esm({
1601
1605
  /* Container needs relative positioning for skeleton overlay */
1602
1606
  .recur-card-field-container {
1603
1607
  position: relative;
1608
+ /* Help reduce iOS zoom issues */
1609
+ touch-action: manipulation;
1604
1610
  }
1605
1611
 
1606
1612
  /* Loading skeleton for card fields - shown until PAYUNi iframe loads */
@@ -2613,7 +2619,7 @@ function toCamelCase(obj) {
2613
2619
 
2614
2620
  // package.json
2615
2621
  var package_default = {
2616
- version: "0.9.3"};
2622
+ version: "0.9.5"};
2617
2623
  var SDK_VERSION = package_default.version;
2618
2624
  var SDK_TYPE = "react";
2619
2625
  var RecurContext = createContext(null);
@@ -2777,6 +2783,8 @@ function RecurProvider({ children, config: initialConfig = {} }) {
2777
2783
  if (productId) checkoutRequestBody.productId = productId;
2778
2784
  if (productSlug) checkoutRequestBody.productSlug = productSlug;
2779
2785
  if (options.externalCustomerId) checkoutRequestBody.externalCustomerId = options.externalCustomerId;
2786
+ if (options.successUrl) checkoutRequestBody.successUrl = options.successUrl;
2787
+ if (options.cancelUrl) checkoutRequestBody.cancelUrl = options.cancelUrl;
2780
2788
  const checkoutResponse = await fetch(`${baseUrl}/v1/checkouts`, {
2781
2789
  method: "POST",
2782
2790
  headers,
package/dist/recur.umd.js CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";var RecurCheckout=(()=>{var _=Object.defineProperty;var Pe=Object.getOwnPropertyDescriptor;var De=Object.getOwnPropertyNames;var Me=Object.prototype.hasOwnProperty;var Le=(a,e,t)=>e in a?_(a,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):a[e]=t;var v=(a,e)=>()=>(a&&(e=a(a=0)),e);var k=(a,e)=>{for(var t in e)_(a,t,{get:e[t],enumerable:!0})},Ue=(a,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of De(e))!Me.call(a,s)&&s!==t&&_(a,s,{get:()=>e[s],enumerable:!(r=Pe(e,s))||r.enumerable});return a};var Ae=a=>Ue(_({},"__esModule",{value:!0}),a);var c=(a,e,t)=>Le(a,typeof e!="symbol"?e+"":e,t);var se={};k(se,{RecurLoadingSpinner:()=>z});var z,ie=v(()=>{"use strict";z=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=`
1
+ "use strict";var RecurCheckout=(()=>{var _=Object.defineProperty;var Pe=Object.getOwnPropertyDescriptor;var Ue=Object.getOwnPropertyNames;var De=Object.prototype.hasOwnProperty;var Me=(a,e,t)=>e in a?_(a,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):a[e]=t;var w=(a,e)=>()=>(a&&(e=a(a=0)),e);var v=(a,e)=>{for(var t in e)_(a,t,{get:e[t],enumerable:!0})},Le=(a,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of Ue(e))!De.call(a,s)&&s!==t&&_(a,s,{get:()=>e[s],enumerable:!(r=Pe(e,s))||r.enumerable});return a};var Ae=a=>Le(_({},"__esModule",{value:!0}),a);var c=(a,e,t)=>Me(a,typeof e!="symbol"?e+"":e,t);var se={};v(se,{RecurLoadingSpinner:()=>z});var z,ie=w(()=>{"use strict";z=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;
@@ -40,7 +40,7 @@
40
40
 
41
41
  <div class="recur-sdk__spinner"></div>
42
42
  ${this.message?`<p class="recur-sdk__loading-text">${this.message}</p>`:""}
43
- `}};typeof window<"u"&&!customElements.get("recur-loading-spinner")&&customElements.define("recur-loading-spinner",z)});var oe={};k(oe,{RecurSuccessMessage:()=>$});var $,ne=v(()=>{"use strict";$=class extends HTMLElement{static get observedAttributes(){return["title","message","icon"]}constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){this.render()}attributeChangedCallback(e,t,r){t!==r&&this.render()}get successTitle(){return this.getAttribute("title")||"Subscription Complete!"}get successMessage(){return this.getAttribute("message")||"Thank you for subscribing. Your payment has been processed successfully."}get showIcon(){return this.getAttribute("icon")!=="false"}render(){this.shadowRoot.innerHTML=`
43
+ `}};typeof window<"u"&&!customElements.get("recur-loading-spinner")&&customElements.define("recur-loading-spinner",z)});var oe={};v(oe,{RecurSuccessMessage:()=>H});var H,ne=w(()=>{"use strict";H=class extends HTMLElement{static get observedAttributes(){return["title","message","icon"]}constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){this.render()}attributeChangedCallback(e,t,r){t!==r&&this.render()}get successTitle(){return this.getAttribute("title")||"Subscription Complete!"}get successMessage(){return this.getAttribute("message")||"Thank you for subscribing. Your payment has been processed successfully."}get showIcon(){return this.getAttribute("icon")!=="false"}render(){this.shadowRoot.innerHTML=`
44
44
  <style>
45
45
  :host {
46
46
  display: block;
@@ -121,7 +121,7 @@
121
121
  <p class="recur-sdk__success-message">${this.successMessage}</p>
122
122
  <slot></slot>
123
123
  </div>
124
- `}};typeof window<"u"&&!customElements.get("recur-success-message")&&customElements.define("recur-success-message",$)});var ae={};k(ae,{RecurErrorDisplay:()=>H});var H,ce=v(()=>{"use strict";H=class extends HTMLElement{static get observedAttributes(){return["error","dismissible"]}constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){this.render()}attributeChangedCallback(e,t,r){t!==r&&this.render()}get error(){return this.getAttribute("error")||""}get isDismissible(){return this.getAttribute("dismissible")==="true"}handleDismiss(){this.dispatchEvent(new CustomEvent("dismiss",{bubbles:!0,composed:!0})),this.remove()}render(){if(!this.error){this.shadowRoot.innerHTML="";return}this.shadowRoot.innerHTML=`
124
+ `}};typeof window<"u"&&!customElements.get("recur-success-message")&&customElements.define("recur-success-message",H)});var ae={};v(ae,{RecurErrorDisplay:()=>$});var $,ce=w(()=>{"use strict";$=class extends HTMLElement{static get observedAttributes(){return["error","dismissible"]}constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){this.render()}attributeChangedCallback(e,t,r){t!==r&&this.render()}get error(){return this.getAttribute("error")||""}get isDismissible(){return this.getAttribute("dismissible")==="true"}handleDismiss(){this.dispatchEvent(new CustomEvent("dismiss",{bubbles:!0,composed:!0})),this.remove()}render(){if(!this.error){this.shadowRoot.innerHTML="";return}this.shadowRoot.innerHTML=`
125
125
  <style>
126
126
  :host {
127
127
  display: block;
@@ -211,7 +211,7 @@
211
211
  </button>
212
212
  `:""}
213
213
  </div>
214
- `,this.isDismissible&&this.shadowRoot.querySelector(".recur-sdk__error-dismiss")?.addEventListener("click",()=>this.handleDismiss())}};typeof window<"u"&&!customElements.get("recur-error-display")&&customElements.define("recur-error-display",H)});var le={};k(le,{RecurSkeletonLoader:()=>K});var K,de=v(()=>{"use strict";K=class extends HTMLElement{static get observedAttributes(){return["type"]}constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){this.render()}attributeChangedCallback(e,t,r){t!==r&&this.render()}get type(){let e=this.getAttribute("type");return e==="list"||e==="card"?e:"payment-form"}renderPaymentFormSkeleton(){return`
214
+ `,this.isDismissible&&this.shadowRoot.querySelector(".recur-sdk__error-dismiss")?.addEventListener("click",()=>this.handleDismiss())}};typeof window<"u"&&!customElements.get("recur-error-display")&&customElements.define("recur-error-display",$)});var le={};v(le,{RecurSkeletonLoader:()=>K});var K,de=w(()=>{"use strict";K=class extends HTMLElement{static get observedAttributes(){return["type"]}constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){this.render()}attributeChangedCallback(e,t,r){t!==r&&this.render()}get type(){let e=this.getAttribute("type");return e==="list"||e==="card"?e:"payment-form"}renderPaymentFormSkeleton(){return`
215
215
  <div class="skeleton-field">
216
216
  <div class="skeleton-label"></div>
217
217
  <div class="skeleton-input"></div>
@@ -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",K)});var ue={};k(ue,{RecurPaymentFormSkeleton:()=>B});var B,me=v(()=>{"use strict";B=class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){this.render()}render(){this.shadowRoot.innerHTML=`
380
+ `}};typeof window<"u"&&!customElements.get("recur-skeleton-loader")&&customElements.define("recur-skeleton-loader",K)});var ue={};v(ue,{RecurPaymentFormSkeleton:()=>B});var B,me=w(()=>{"use strict";B=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",B)});var pe={};k(pe,{RecurToast:()=>N,RecurToastContainer:()=>D});var N,w,D,he=v(()=>{"use strict";N=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">
647
+ `}};typeof window<"u"&&!customElements.get("recur-payment-form-skeleton")&&customElements.define("recur-payment-form-skeleton",B)});var pe={};v(pe,{RecurToast:()=>N,RecurToastContainer:()=>U});var N,x,U,he=w(()=>{"use strict";N=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",s=>{s.stopPropagation(),this.dismiss()}),this.shadowRoot.querySelector(".toast")?.addEventListener("click",()=>this.dismiss())}static show(e,t="info",r=5e3){let s=D.getInstance(),i=document.createElement("recur-toast");return i.setAttribute("message",e),i.setAttribute("type",t),i.setAttribute("duration",r.toString()),s.appendChild(i),i}},w=class w extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){this.render()}render(){this.shadowRoot.innerHTML=`
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=U.getInstance(),i=document.createElement("recur-toast");return i.setAttribute("message",e),i.setAttribute("type",t),i.setAttribute("duration",r.toString()),s.appendChild(i),i}},x=class x 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 w.instance||(w.instance=document.querySelector("recur-toast-container"),w.instance||(w.instance=document.createElement("recur-toast-container"),document.body.appendChild(w.instance))),w.instance}};c(w,"instance",null);D=w;typeof window<"u"&&!customElements.get("recur-toast")&&customElements.define("recur-toast",N);typeof window<"u"&&!customElements.get("recur-toast-container")&&customElements.define("recur-toast-container",D)});var fe={};k(fe,{RecurPaymentForm:()=>O});var O,ge=v(()=>{"use strict";O=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=`
794
+ `}static getInstance(){return x.instance||(x.instance=document.querySelector("recur-toast-container"),x.instance||(x.instance=document.createElement("recur-toast-container"),document.body.appendChild(x.instance))),x.instance}};c(x,"instance",null);U=x;typeof window<"u"&&!customElements.get("recur-toast")&&customElements.define("recur-toast",N);typeof window<"u"&&!customElements.get("recur-toast-container")&&customElements.define("recur-toast-container",U)});var fe={};v(fe,{RecurPaymentForm:()=>O});var O,ge=w(()=>{"use strict";O=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;
@@ -805,6 +805,8 @@
805
805
  background: white;
806
806
  border-radius: 12px;
807
807
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
808
+ /* Prevent double-tap zoom on iOS */
809
+ touch-action: manipulation;
808
810
  }
809
811
 
810
812
  .form-header {
@@ -1037,7 +1039,7 @@
1037
1039
  .recur-form-input {
1038
1040
  width: 100%;
1039
1041
  padding: 10px 12px;
1040
- font-size: 14px;
1042
+ font-size: 16px; /* 16px prevents iOS Safari auto-zoom on focus */
1041
1043
  border: 1px solid #d1d5db;
1042
1044
  border-radius: 6px;
1043
1045
  transition: border-color 0.2s;
@@ -1097,6 +1099,8 @@
1097
1099
  width: 100%;
1098
1100
  max-width: 100%;
1099
1101
  box-sizing: border-box;
1102
+ /* Help reduce iOS zoom issues */
1103
+ touch-action: manipulation;
1100
1104
  }
1101
1105
 
1102
1106
  .recur-card-row {
@@ -1110,6 +1114,8 @@
1110
1114
  /* Container needs relative positioning for skeleton overlay */
1111
1115
  .recur-card-field-container {
1112
1116
  position: relative;
1117
+ /* Help reduce iOS zoom issues */
1118
+ touch-action: manipulation;
1113
1119
  }
1114
1120
 
1115
1121
  /* Loading skeleton for card fields - shown until PAYUNi iframe loads */
@@ -1220,13 +1226,13 @@
1220
1226
  >
1221
1227
  <span id="${this.containerId}-submit-text">\u78BA\u8A8D\u4ED8\u6B3E</span>
1222
1228
  </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 s=document.getElementById(`${this.containerId}-card-no`),i=document.getElementById(`${this.containerId}-card-exp`),n=document.getElementById(`${this.containerId}-card-cvc`);if(!s||!i||!n)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(d){if((d?.message?.includes("1008")||d?.message?.includes("timeout")||d?.code===1008)&&this._initializationAborted)return console.log("[PaymentForm] Caught 1008 timeout error after component disconnect - ignoring"),this._isInitializing=!1,null;throw d}return this._initializationAborted?(console.log("[PaymentForm] Initialization aborted after paymentSession.start()"),this._isInitializing=!1,null):(this.hideCardSkeletons(),o.onUpdate?.(d=>{if(this._initializationAborted){console.log("[PaymentForm] Ignoring onUpdate event - component disconnected");return}console.log("[PaymentForm] PAYUNi update:",d);let m=d.status&&d.status.CardNo===!0&&d.status.CardExp===!0&&d.status.CardCvc===!0,x=document.getElementById(`${this.containerId}-submit-btn`);x&&(x.disabled=!m,console.log("[PaymentForm] Submit button disabled:",!m))}),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,n=document.getElementById(`${this.containerId}-email`),o=document.getElementById(`${this.containerId}-name`);if(n&&o){if(s=n.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=`
1229
+ `,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`),n=document.getElementById(`${this.containerId}-card-cvc`);if(!s||!i||!n)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(d){if((d?.message?.includes("1008")||d?.message?.includes("timeout")||d?.code===1008)&&this._initializationAborted)return console.log("[PaymentForm] Caught 1008 timeout error after component disconnect - ignoring"),this._isInitializing=!1,null;throw d}return this._initializationAborted?(console.log("[PaymentForm] Initialization aborted after paymentSession.start()"),this._isInitializing=!1,null):(this.hideCardSkeletons(),o.onUpdate?.(d=>{if(this._initializationAborted){console.log("[PaymentForm] Ignoring onUpdate event - component disconnected");return}console.log("[PaymentForm] PAYUNi update:",d);let m=d.status&&d.status.CardNo===!0&&d.status.CardExp===!0&&d.status.CardCvc===!0,y=document.getElementById(`${this.containerId}-submit-btn`);y&&(y.disabled=!m,console.log("[PaymentForm] Submit button disabled:",!m))}),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,n=document.getElementById(`${this.containerId}-email`),o=document.getElementById(`${this.containerId}-name`);if(n&&o){if(s=n.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
1230
  <span class="recur-loading-spinner"></span>
1225
1231
  <span>\u8655\u7406\u4E2D...</span>
1226
1232
  `):(r.classList.remove("loading"),r.disabled=!1,r.innerHTML='<span id="'+this.containerId+'-submit-text">\u78BA\u8A8D\u4ED8\u6B3E</span>'))}resetButton(){this.setButtonLoading(!1)}setVerifying(t){let r=document.getElementById(`${this.containerId}-submit-btn`);r&&(t?(r.classList.add("loading"),r.disabled=!0,r.innerHTML=`
1227
1233
  <span class="recur-loading-spinner"></span>
1228
1234
  <span>3D \u9A57\u8B49\u4E2D...</span>
1229
- `):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",O)});var be={};k(be,{RecurCheckoutButton:()=>j});var j,ye=v(()=>{"use strict";j=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"),n=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(!n){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(n),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=`
1235
+ `):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",O)});var be={};v(be,{RecurCheckoutButton:()=>j});var j,ye=w(()=>{"use strict";j=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"),n=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(!n){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(n),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=`
1230
1236
  <style>
1231
1237
  :host {
1232
1238
  display: inline-block;
@@ -1325,7 +1331,7 @@
1325
1331
  ${this._isLoading?'<span class="spinner"></span>':""}
1326
1332
  <span>${this._isLoading?"\u8655\u7406\u4E2D...":t}</span>
1327
1333
  </button>
1328
- `}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 n=await i.json().catch(()=>({}));throw new Error(n.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",j)});var ke={};k(ke,{RecurPortalButton:()=>F});var F,ve=v(()=>{"use strict";F=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=`
1334
+ `}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 n=await i.json().catch(()=>({}));throw new Error(n.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",j)});var ke={};v(ke,{RecurPortalButton:()=>F});var F,ve=w(()=>{"use strict";F=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=`
1329
1335
  <style>
1330
1336
  :host {
1331
1337
  display: inline-block;
@@ -1452,7 +1458,7 @@
1452
1458
  <path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/>
1453
1459
  <circle cx="12" cy="7" r="4"/>
1454
1460
  </svg>
1455
- `}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 n=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});if(!n.ok){let m=await n.json().catch(()=>({}));throw new Error(m.error?.message||m.message||`HTTP ${n.status}: Failed to create portal session`)}let o=await n.json(),d=o.url||o.portalUrl;if(!d)throw new Error("Portal URL not found in response");this._isLoading=!1,this.render(),this.setupEventListeners(),this.redirectToPortal(d)}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",F)});var je={};k(je,{RecurCheckout:()=>L,RecurElements:()=>T,createElements:()=>Q,default:()=>Oe,init:()=>Se});async function _e(){if(typeof window>"u"){console.warn("[Recur SDK] Cannot register Web Components: window is not defined");return}await Promise.all([Promise.resolve().then(()=>(ie(),se)),Promise.resolve().then(()=>(ne(),oe)),Promise.resolve().then(()=>(ce(),ae)),Promise.resolve().then(()=>(de(),le)),Promise.resolve().then(()=>(me(),ue)),Promise.resolve().then(()=>(he(),pe)),Promise.resolve().then(()=>(ge(),fe)),Promise.resolve().then(()=>(ye(),be)),Promise.resolve().then(()=>(ve(),ke))]);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"&&_e();function ze(a){return a.replace(/_([a-z])/g,(e,t)=>t.toUpperCase())}function g(a){if(a==null)return a;if(Array.isArray(a))return a.map(e=>g(e));if(a instanceof Date)return a;if(typeof a=="object"){let e={};for(let[t,r]of Object.entries(a)){let s=ze(t);e[s]=g(r)}return e}return a}var we={name:"recur-tw",version:"0.9.3",description:"React & Vanilla JS SDK for embedding subscription checkout flows (Taiwan / PAYUNi)",type:"module",private:!1,author:"Recur",license:"Elastic-2.0",keywords:["react","vanilla-js","subscription","checkout","payment","payuni","recurring-billing","taiwan","\u7E41\u9AD4\u4E2D\u6587","sdk","embedded-checkout"],homepage:"https://recur.tw/",bugs:{email:"support@recur.tw"},scripts:{build:"tsup",dev:"tsup --watch",lint:"eslint .","lint:fix":"eslint . --fix","type-check":"tsc --noEmit",examples:"npx http-server examples -p 8080 -o"},main:"./dist/index.js",module:"./dist/index.js",types:"./dist/index.d.ts",exports:{".":{types:"./dist/index.d.ts",import:"./dist/index.js",require:"./dist/index.cjs"},"./server":{types:"./dist/server.d.ts",import:"./dist/server.js",require:"./dist/server.cjs"},"./vanilla":"./dist/recur.umd.js","./package.json":"./package.json"},unpkg:"./dist/recur.umd.js",jsdelivr:"./dist/recur.umd.js",files:["dist","README.md","AGENTS.md","LICENSE"],sideEffects:["src/components/**/*.ts","src/components/**/*.js"],peerDependencies:{react:">=18.0.0","react-dom":">=18.0.0"},peerDependenciesMeta:{react:{optional:!0},"react-dom":{optional:!0}},devDependencies:{"@eslint/js":"^9.39.1","@types/node":"^20.19.9","@types/react":"^19.1.9","@types/react-dom":"^19.1.7","@workspace/typescript-config":"workspace:*",autoprefixer:"^10.4.22",eslint:"^9","eslint-plugin-react":"^7.37.5","eslint-plugin-react-hooks":"^7.0.1",postcss:"^8.5.6",react:"^19.2.1","react-dom":"^19.2.1",tailwindcss:"^4.1.11",tsup:"^8.3.5",typescript:"^5.9.2","typescript-eslint":"^8.47.0"}};var He=we.version,Ke="vanilla",q=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":Ke,"X-Recur-SDK-Version":He,"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 n={customerName:t,customerEmail:r};s&&(n.productId=s),i&&(n.productSlug=i);let o=await fetch(`${this.config.baseUrl}/v1/subscriptions`,{method:"POST",headers:this.getHeaders(),body:JSON.stringify(n)});if(!o.ok){let m=await o.json().catch(()=>({}));throw{code:m.error||"CHECKOUT_FAILED",message:m.message||"Failed to initiate checkout",details:m}}let d=await o.json();return g(d)}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 g(s)}async fetchPlans(){return{plans:(await this.fetchProducts({type:"SUBSCRIPTION"})).products}}getConfig(){return{...this.config}}};var V=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=g(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=`
1461
+ `}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 n=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});if(!n.ok){let m=await n.json().catch(()=>({}));throw new Error(m.error?.message||m.message||`HTTP ${n.status}: Failed to create portal session`)}let o=await n.json(),d=o.url||o.portalUrl;if(!d)throw new Error("Portal URL not found in response");this._isLoading=!1,this.render(),this.setupEventListeners(),this.redirectToPortal(d)}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",F)});var je={};v(je,{RecurCheckout:()=>M,RecurElements:()=>T,createElements:()=>Q,default:()=>Oe,init:()=>Se});async function _e(){if(typeof window>"u"){console.warn("[Recur SDK] Cannot register Web Components: window is not defined");return}await Promise.all([Promise.resolve().then(()=>(ie(),se)),Promise.resolve().then(()=>(ne(),oe)),Promise.resolve().then(()=>(ce(),ae)),Promise.resolve().then(()=>(de(),le)),Promise.resolve().then(()=>(me(),ue)),Promise.resolve().then(()=>(he(),pe)),Promise.resolve().then(()=>(ge(),fe)),Promise.resolve().then(()=>(ye(),be)),Promise.resolve().then(()=>(ve(),ke))]);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"&&_e();function ze(a){return a.replace(/_([a-z])/g,(e,t)=>t.toUpperCase())}function g(a){if(a==null)return a;if(Array.isArray(a))return a.map(e=>g(e));if(a instanceof Date)return a;if(typeof a=="object"){let e={};for(let[t,r]of Object.entries(a)){let s=ze(t);e[s]=g(r)}return e}return a}var we={name:"recur-tw",version:"0.9.5",description:"React & Vanilla JS SDK for embedding subscription checkout flows (Taiwan / PAYUNi)",type:"module",private:!1,author:"Recur",license:"Elastic-2.0",keywords:["react","vanilla-js","subscription","checkout","payment","payuni","recurring-billing","taiwan","\u7E41\u9AD4\u4E2D\u6587","sdk","embedded-checkout"],homepage:"https://recur.tw/",bugs:{email:"support@recur.tw"},scripts:{build:"tsup",dev:"tsup --watch",lint:"eslint .","lint:fix":"eslint . --fix","type-check":"tsc --noEmit",examples:"npx http-server examples -p 8080 -o"},main:"./dist/index.js",module:"./dist/index.js",types:"./dist/index.d.ts",exports:{".":{types:"./dist/index.d.ts",import:"./dist/index.js",require:"./dist/index.cjs"},"./server":{types:"./dist/server.d.ts",import:"./dist/server.js",require:"./dist/server.cjs"},"./vanilla":"./dist/recur.umd.js","./package.json":"./package.json"},unpkg:"./dist/recur.umd.js",jsdelivr:"./dist/recur.umd.js",files:["dist","README.md","AGENTS.md","LICENSE"],sideEffects:["src/components/**/*.ts","src/components/**/*.js"],peerDependencies:{react:">=18.0.0","react-dom":">=18.0.0"},peerDependenciesMeta:{react:{optional:!0},"react-dom":{optional:!0}},devDependencies:{"@eslint/js":"^9.39.1","@types/node":"^20.19.9","@types/react":"^19.1.9","@types/react-dom":"^19.1.7","@workspace/typescript-config":"workspace:*",autoprefixer:"^10.4.22",eslint:"^9","eslint-plugin-react":"^7.37.5","eslint-plugin-react-hooks":"^7.0.1",postcss:"^8.5.6",react:"^19.2.1","react-dom":"^19.2.1",tailwindcss:"^4.1.11",tsup:"^8.3.5",typescript:"^5.9.2","typescript-eslint":"^8.47.0"}};var $e=we.version,Ke="vanilla",q=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":Ke,"X-Recur-SDK-Version":$e,"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 n={customerName:t,customerEmail:r};s&&(n.productId=s),i&&(n.productSlug=i);let o=await fetch(`${this.config.baseUrl}/v1/subscriptions`,{method:"POST",headers:this.getHeaders(),body:JSON.stringify(n)});if(!o.ok){let m=await o.json().catch(()=>({}));throw{code:m.error||"CHECKOUT_FAILED",message:m.message||"Failed to initiate checkout",details:m}}let d=await o.json();return g(d)}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 g(s)}async fetchPlans(){return{plans:(await this.fetchProducts({type:"SUBSCRIPTION"})).products}}getConfig(){return{...this.config}}};var V=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=g(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=`
1456
1462
  <div class="recur-embedded-checkout" style="max-width: 400px; margin: 0 auto; padding: 20px; font-family: system-ui, -apple-system, sans-serif;">
1457
1463
  <div class="recur-checkout-header" style="margin-bottom: 24px;">
1458
1464
  <h2 style="margin: 0 0 8px 0; font-size: 20px; font-weight: 600; color: #111;">Subscribe</h2>
@@ -1529,14 +1535,14 @@
1529
1535
  display: block;
1530
1536
  user-select: none;
1531
1537
  transition: height 0.35s ease, opacity 0.4s ease 0.1s;
1532
- `.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),n=()=>{clearTimeout(i),this.off("ready",n),r()},o=d=>{clearTimeout(i),this.off("error",o),s(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(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)},n=o=>{clearTimeout(s),this.off("error",n),r(new Error(o.message||"Tokenization failed"))};this.on("tokenized",i),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,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(a){return new T(a)}var Be="https://vendor.payuni.com.tw/sdk/uni-payment.js",Ne="https://sandbox-vendor.payuni.com.tw/sdk/uni-payment.js",xe=!1,Y=!1,M=null;async function Ee(a=!1){return xe&&window.UniPayment?Promise.resolve():(Y&&M||(Y=!0,M=new Promise((e,t)=>{let r=document.createElement("script");r.src=a?Ne:Be,r.async=!0,r.onload=()=>{xe=!0,Y=!1,e()},r.onerror=()=>{Y=!1,M=null,t(new Error("Failed to load PAYUNi SDK"))},document.head.appendChild(r)})),M)}var L=class{constructor(e){c(this,"core");c(this,"currentModal",null);c(this,"currentIframe",null);c(this,"currentModalOverlay",null);this.core=new q(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 V(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 d=await i.json().catch(()=>({}));throw new Error(d.error?.message||d.error||"Failed to create checkout session")}let n=await i.json(),o=g(n);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 d=await i.json().catch(()=>({}));throw new Error(d.error||"Failed to get checkout status")}let n=await i.json(),o=g(n);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 n=this.getBaseUrl();console.log("[Recur SDK] Base URL:",n);let o={"Content-Type":"application/json","X-Recur-Publishable-Key":t.publishableKey},d=e.mode||"modal";if(d==="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 h={successUrl:e.successUrl,cancelUrl:e.cancelUrl};s&&(h.productId=s),i&&(h.productSlug=i),e.customerEmail&&(h.customerEmail=e.customerEmail),e.customerName&&(h.customerName=e.customerName),e.externalCustomerId&&(h.externalCustomerId=e.externalCustomerId);let S=await fetch(`${n}/v1/checkout/sessions`,{method:"POST",headers:o,body:JSON.stringify(h)});if(!S.ok){let C=await S.json().catch(()=>({}));throw console.error("[Recur SDK] Failed to create checkout session:",C),new Error(C.error?.message||C.error||"Failed to create checkout session")}let J=await S.json(),y=g(J);console.log("[Recur SDK] Checkout session created:",y),console.log("[Recur SDK] Redirecting to hosted checkout:",y.url),window.location.href=y.url;return}let m=null;if(d==="modal"){let h=this.createModalWithSkeleton(e.onClose);r=h.overlay,m=h.container}else if(d==="iframe"){if(m=this.getEmbeddedContainer(e.container),!m)throw new Error("Container is required for iframe mode");m.innerHTML="";let h=document.createElement("recur-payment-form-skeleton");m.appendChild(h)}console.log("[Recur SDK] Step 1: Creating embedded checkout...");let x={customerName:e.customerName,customerEmail:e.customerEmail};s&&(x.productId=s),i&&(x.productSlug=i),e.externalCustomerId&&(x.externalCustomerId=e.externalCustomerId);let X=await fetch(`${n}/v1/checkouts`,{method:"POST",headers:o,body:JSON.stringify(x)});if(!X.ok){let h=await X.json().catch(()=>({}));console.error("[Recur SDK] Failed to create checkout:",h);let S=h.details||h.error||"Failed to create checkout";throw new Error(S)}let Ce=await X.json(),l=g(Ce);if(console.log("[Recur SDK] Checkout created successfully:",l),e.onSuccess?.(l),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 W=!l.livemode;if(console.log("[Recur SDK] Environment:",W?"SANDBOX":"PRODUCTION"),await Ee(W),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..."),!m)throw new Error("Payment container not available");m.innerHTML="";let p=document.createElement("recur-payment-form");if(p.setAttribute("container-id",m.id||"recur-payment-container"),e.customerName&&p.setAttribute("customer-name",e.customerName),e.customerEmail&&p.setAttribute("customer-email",e.customerEmail),l.plan?.name&&p.setAttribute("plan-name",l.plan.name),l.checkout?.amount&&p.setAttribute("amount",l.checkout.amount.toString()),l.plan?.billingPeriod&&p.setAttribute("billing-period",l.plan.billingPeriod),p.setAttribute("custom-styles",`
1538
+ `.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),n=()=>{clearTimeout(i),this.off("ready",n),r()},o=d=>{clearTimeout(i),this.off("error",o),s(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(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)},n=o=>{clearTimeout(s),this.off("error",n),r(new Error(o.message||"Tokenization failed"))};this.on("tokenized",i),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,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(a){return new T(a)}var Be="https://vendor.payuni.com.tw/sdk/uni-payment.js",Ne="https://sandbox-vendor.payuni.com.tw/sdk/uni-payment.js",xe=!1,Y=!1,D=null;async function Ee(a=!1){return xe&&window.UniPayment?Promise.resolve():(Y&&D||(Y=!0,D=new Promise((e,t)=>{let r=document.createElement("script");r.src=a?Ne:Be,r.async=!0,r.onload=()=>{xe=!0,Y=!1,e()},r.onerror=()=>{Y=!1,D=null,t(new Error("Failed to load PAYUNi SDK"))},document.head.appendChild(r)})),D)}var M=class{constructor(e){c(this,"core");c(this,"currentModal",null);c(this,"currentIframe",null);c(this,"currentModalOverlay",null);this.core=new q(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 V(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 d=await i.json().catch(()=>({}));throw new Error(d.error?.message||d.error||"Failed to create checkout session")}let n=await i.json(),o=g(n);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 d=await i.json().catch(()=>({}));throw new Error(d.error||"Failed to get checkout status")}let n=await i.json(),o=g(n);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 n=this.getBaseUrl();console.log("[Recur SDK] Base URL:",n);let o={"Content-Type":"application/json","X-Recur-Publishable-Key":t.publishableKey},d=e.mode||"modal";if(d==="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 h={successUrl:e.successUrl,cancelUrl:e.cancelUrl};s&&(h.productId=s),i&&(h.productSlug=i),e.customerEmail&&(h.customerEmail=e.customerEmail),e.customerName&&(h.customerName=e.customerName),e.externalCustomerId&&(h.externalCustomerId=e.externalCustomerId);let S=await fetch(`${n}/v1/checkout/sessions`,{method:"POST",headers:o,body:JSON.stringify(h)});if(!S.ok){let C=await S.json().catch(()=>({}));throw console.error("[Recur SDK] Failed to create checkout session:",C),new Error(C.error?.message||C.error||"Failed to create checkout session")}let J=await S.json(),k=g(J);console.log("[Recur SDK] Checkout session created:",k),console.log("[Recur SDK] Redirecting to hosted checkout:",k.url),window.location.href=k.url;return}let m=null;if(d==="modal"){let h=this.createModalWithSkeleton(e.onClose);r=h.overlay,m=h.container}else if(d==="iframe"){if(m=this.getEmbeddedContainer(e.container),!m)throw new Error("Container is required for iframe mode");m.innerHTML="";let h=document.createElement("recur-payment-form-skeleton");m.appendChild(h)}console.log("[Recur SDK] Step 1: Creating embedded checkout...");let y={customerName:e.customerName,customerEmail:e.customerEmail};s&&(y.productId=s),i&&(y.productSlug=i),e.externalCustomerId&&(y.externalCustomerId=e.externalCustomerId),e.successUrl&&(y.successUrl=e.successUrl),e.cancelUrl&&(y.cancelUrl=e.cancelUrl);let X=await fetch(`${n}/v1/checkouts`,{method:"POST",headers:o,body:JSON.stringify(y)});if(!X.ok){let h=await X.json().catch(()=>({}));console.error("[Recur SDK] Failed to create checkout:",h);let S=h.details||h.error||"Failed to create checkout";throw new Error(S)}let Ce=await X.json(),l=g(Ce);if(console.log("[Recur SDK] Checkout created successfully:",l),e.onSuccess?.(l),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 W=!l.livemode;if(console.log("[Recur SDK] Environment:",W?"SANDBOX":"PRODUCTION"),await Ee(W),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..."),!m)throw new Error("Payment container not available");m.innerHTML="";let p=document.createElement("recur-payment-form");if(p.setAttribute("container-id",m.id||"recur-payment-container"),e.customerName&&p.setAttribute("customer-name",e.customerName),e.customerEmail&&p.setAttribute("customer-email",e.customerEmail),l.plan?.name&&p.setAttribute("plan-name",l.plan.name),l.checkout?.amount&&p.setAttribute("amount",l.checkout.amount.toString()),l.plan?.billingPeriod&&p.setAttribute("billing-period",l.plan.billingPeriod),p.setAttribute("custom-styles",`
1533
1539
  .form-input-focus {
1534
1540
  border-color: var(--ring, hsl(215 16% 47%)) !important;
1535
1541
  outline: 0 !important;
1536
1542
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring, hsl(215 16% 47%)) 50%, transparent) !important;
1537
1543
  transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out !important;
1538
1544
  }
1539
- `),m.appendChild(p),await new Promise(h=>setTimeout(h,100)),console.log("[Recur SDK] Step 5: Initializing PAYUNi SDK..."),await p.initializePayment(l.sdkToken,W?"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..."),p.addEventListener("submit",(async h=>{console.log("[Recur SDK] Form submitted");let S=h,{paymentSession:J}=S.detail;try{let y=await J.getTradeResult();console.log("[Recur SDK] Trade result received"),console.log("[Recur SDK] Executing payment...");let C=y.HashTimestamp||y.timestamp,Z={};if(l.checkout.productType==="SUBSCRIPTION"){let E=l.creditToken,b=l.sdkTimestamp;if(!E)throw console.error("[Recur SDK] Missing creditToken from checkout for subscription"),new Error("Missing creditToken for subscription payment");Z={creditToken:E,timestamp:b||C},console.log("[Recur SDK] Using creditToken from checkout:",E.substring(0,30)+"..."),console.log("[Recur SDK] Using timestamp:",b?"from checkout (sdkTimestamp)":"from tradeResult")}let G=await fetch(`${n}/v1/checkouts/${l.checkout.id}/pay`,{method:"POST",headers:o,body:JSON.stringify(Z)});if(!G.ok){let E=await G.json().catch(()=>({}));throw new Error(E.error||"Failed to execute payment")}let Te=await G.json(),u=g(Te);if(console.log("[Recur SDK] Payment executed:",u),u.requires3D&&u.redirectUrl){if(console.log("[Recur SDK] 3D verification required"),/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)||/wv|WebView|FBAN|FBAV|Instagram|Line|MicroMessenger|QQ/i.test(navigator.userAgent)||/(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(navigator.userAgent)||"standalone"in navigator&&navigator.standalone){console.log("[Recur SDK] Mobile/WebView detected, using redirect for 3D verification"),window.location.href=u.redirectUrl;return}console.log("[Recur SDK] Using popup for 3D verification");{let b=window.open(u.redirectUrl,"recur_3d_verification","width=500,height=700,scrollbars=yes,resizable=yes");if(!b){console.log("[Recur SDK] Popup was blocked (null), falling back to redirect"),window.location.href=u.redirectUrl;return}if(await new Promise(R=>setTimeout(R,100)),b.closed){console.log("[Recur SDK] Popup was closed immediately, falling back to redirect"),window.location.href=u.redirectUrl;return}p.setVerifying?.(!0);let U=l.checkout.id,ee=l.checkout.clientSecret,Ie=90,Re=2e3,I=!0,te=!1,re;console.log("[Recur SDK] Starting 3D verification polling...");for(let R=0;R<Ie&&I;R++){if(b.closed){console.log("[Recur SDK] Popup was closed");try{let f=await fetch(`${n}/v1/checkouts/${U}/status?client_secret=${encodeURIComponent(ee)}`,{headers:o});if(f.ok){let P=await f.json();if(P.checkout?.status==="SUCCEEDED"){console.log("[Recur SDK] Payment succeeded after popup close"),I=!1,te=!0,re=P.checkout?.orderId;break}}}catch(f){console.error("[Recur SDK] Final status check failed:",f)}throw console.log("[Recur SDK] Payment not confirmed, user closed popup"),p.setVerifying?.(!1),p.resetButton?.(),new Error("3D \u9A57\u8B49\u5DF2\u53D6\u6D88")}try{let f=await fetch(`${n}/v1/checkouts/${U}/status?client_secret=${encodeURIComponent(ee)}`,{headers:o});if(f.ok){let P=await f.json(),A=P.checkout?.status;if(console.log(`[Recur SDK] Poll ${R+1}: status = ${A}`),A==="SUCCEEDED"){console.log("[Recur SDK] Payment succeeded"),I=!1;try{b.close()}catch{}p.setVerifying?.(!1),e.onPaymentComplete&&(u.subscription?e.onPaymentComplete({id:u.subscription.id,status:"ACTIVE",planId:l.checkout.productId,amount:l.checkout.amount,billingPeriod:u.subscription.billingPeriod,currentPeriodStart:u.subscription.currentPeriodStart,currentPeriodEnd:u.subscription.currentPeriodEnd}):e.onPaymentComplete({id:P.checkout?.orderId||U,status:"SUCCEEDED",planId:l.checkout.productId,amount:l.checkout.amount,billingPeriod:l.checkout.productType})),r&&r.remove();return}if(A==="CANCELED"||A==="FAILED"){console.log("[Recur SDK] Payment failed or canceled"),I=!1;try{b.close()}catch{}throw p.setVerifying?.(!1),p.resetButton?.(),new Error("\u4ED8\u6B3E\u5931\u6557\u6216\u5DF2\u53D6\u6D88")}}}catch(f){if(f.message==="3D \u9A57\u8B49\u5DF2\u53D6\u6D88"||f.message==="\u4ED8\u6B3E\u5931\u6557\u6216\u5DF2\u53D6\u6D88")throw f;console.error("[Recur SDK] Poll error:",f)}await new Promise(f=>setTimeout(f,Re))}if(I){console.log("[Recur SDK] 3D verification polling timeout");try{b.close()}catch{}throw p.setVerifying?.(!1),p.resetButton?.(),new Error("3D \u9A57\u8B49\u903E\u6642\uFF0C\u8ACB\u91CD\u8A66")}te&&(console.log("[Recur SDK] Handling success after popup close"),p.setVerifying?.(!1),e.onPaymentComplete&&(u.subscription?e.onPaymentComplete({id:u.subscription.id,status:"ACTIVE",planId:l.checkout.productId,amount:l.checkout.amount,billingPeriod:u.subscription.billingPeriod,currentPeriodStart:u.subscription.currentPeriodStart,currentPeriodEnd:u.subscription.currentPeriodEnd}):e.onPaymentComplete({id:re||U,status:"SUCCEEDED",planId:l.checkout.productId,amount:l.checkout.amount,billingPeriod:l.checkout.productType})),r&&r.remove());return}}if(e.onPaymentComplete)if(u.subscription)e.onPaymentComplete({id:u.subscription.id,status:u.subscription.status,planId:l.checkout.productId,amount:l.checkout.amount,billingPeriod:u.subscription.billingPeriod,currentPeriodStart:u.subscription.currentPeriodStart,currentPeriodEnd:u.subscription.currentPeriodEnd});else{let E=u.charge?.id||u.paymentIntent?.id,b=u.charge?.status||"SUCCEEDED";e.onPaymentComplete({id:E,status:b,planId:l.checkout.productId,amount:l.checkout.amount,billingPeriod:l.checkout.productType})}console.log("[Recur SDK] Checkout flow completed successfully!"),p.resetButton?.(),r&&r.remove()}catch(y){console.error("[Recur SDK] Payment error:",y);let C={code:"PAYMENT_FAILED",message:y instanceof Error?y.message:"Payment failed"};e.onError?.(C),p.resetButton?.()}})),console.log("[Recur SDK] Checkout flow initialized, waiting for user input...")}catch(n){console.error("[Recur SDK] Checkout error:",n),r&&r.remove();let o={code:"CHECKOUT_ERROR",message:n instanceof Error?n.message:"An unknown error occurred"};throw e.onError?.(o),n}}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=`
1545
+ `),m.appendChild(p),await new Promise(h=>setTimeout(h,100)),console.log("[Recur SDK] Step 5: Initializing PAYUNi SDK..."),await p.initializePayment(l.sdkToken,W?"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..."),p.addEventListener("submit",(async h=>{console.log("[Recur SDK] Form submitted");let S=h,{paymentSession:J}=S.detail;try{let k=await J.getTradeResult();console.log("[Recur SDK] Trade result received"),console.log("[Recur SDK] Executing payment...");let C=k.HashTimestamp||k.timestamp,Z={};if(l.checkout.productType==="SUBSCRIPTION"){let E=l.creditToken,b=l.sdkTimestamp;if(!E)throw console.error("[Recur SDK] Missing creditToken from checkout for subscription"),new Error("Missing creditToken for subscription payment");Z={creditToken:E,timestamp:b||C},console.log("[Recur SDK] Using creditToken from checkout:",E.substring(0,30)+"..."),console.log("[Recur SDK] Using timestamp:",b?"from checkout (sdkTimestamp)":"from tradeResult")}let G=await fetch(`${n}/v1/checkouts/${l.checkout.id}/pay`,{method:"POST",headers:o,body:JSON.stringify(Z)});if(!G.ok){let E=await G.json().catch(()=>({}));throw new Error(E.error||"Failed to execute payment")}let Te=await G.json(),u=g(Te);if(console.log("[Recur SDK] Payment executed:",u),u.requires3D&&u.redirectUrl){if(console.log("[Recur SDK] 3D verification required"),/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)||/wv|WebView|FBAN|FBAV|Instagram|Line|MicroMessenger|QQ/i.test(navigator.userAgent)||/(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(navigator.userAgent)||"standalone"in navigator&&navigator.standalone){console.log("[Recur SDK] Mobile/WebView detected, using redirect for 3D verification"),window.location.href=u.redirectUrl;return}console.log("[Recur SDK] Using popup for 3D verification");{let b=window.open(u.redirectUrl,"recur_3d_verification","width=500,height=700,scrollbars=yes,resizable=yes");if(!b){console.log("[Recur SDK] Popup was blocked (null), falling back to redirect"),window.location.href=u.redirectUrl;return}if(await new Promise(R=>setTimeout(R,100)),b.closed){console.log("[Recur SDK] Popup was closed immediately, falling back to redirect"),window.location.href=u.redirectUrl;return}p.setVerifying?.(!0);let L=l.checkout.id,ee=l.checkout.clientSecret,Ie=90,Re=2e3,I=!0,te=!1,re;console.log("[Recur SDK] Starting 3D verification polling...");for(let R=0;R<Ie&&I;R++){if(b.closed){console.log("[Recur SDK] Popup was closed");try{let f=await fetch(`${n}/v1/checkouts/${L}/status?client_secret=${encodeURIComponent(ee)}`,{headers:o});if(f.ok){let P=await f.json();if(P.checkout?.status==="SUCCEEDED"){console.log("[Recur SDK] Payment succeeded after popup close"),I=!1,te=!0,re=P.checkout?.orderId;break}}}catch(f){console.error("[Recur SDK] Final status check failed:",f)}throw console.log("[Recur SDK] Payment not confirmed, user closed popup"),p.setVerifying?.(!1),p.resetButton?.(),new Error("3D \u9A57\u8B49\u5DF2\u53D6\u6D88")}try{let f=await fetch(`${n}/v1/checkouts/${L}/status?client_secret=${encodeURIComponent(ee)}`,{headers:o});if(f.ok){let P=await f.json(),A=P.checkout?.status;if(console.log(`[Recur SDK] Poll ${R+1}: status = ${A}`),A==="SUCCEEDED"){console.log("[Recur SDK] Payment succeeded"),I=!1;try{b.close()}catch{}p.setVerifying?.(!1),e.onPaymentComplete&&(u.subscription?e.onPaymentComplete({id:u.subscription.id,status:"ACTIVE",planId:l.checkout.productId,amount:l.checkout.amount,billingPeriod:u.subscription.billingPeriod,currentPeriodStart:u.subscription.currentPeriodStart,currentPeriodEnd:u.subscription.currentPeriodEnd}):e.onPaymentComplete({id:P.checkout?.orderId||L,status:"SUCCEEDED",planId:l.checkout.productId,amount:l.checkout.amount,billingPeriod:l.checkout.productType})),r&&r.remove();return}if(A==="CANCELED"||A==="FAILED"){console.log("[Recur SDK] Payment failed or canceled"),I=!1;try{b.close()}catch{}throw p.setVerifying?.(!1),p.resetButton?.(),new Error("\u4ED8\u6B3E\u5931\u6557\u6216\u5DF2\u53D6\u6D88")}}}catch(f){if(f.message==="3D \u9A57\u8B49\u5DF2\u53D6\u6D88"||f.message==="\u4ED8\u6B3E\u5931\u6557\u6216\u5DF2\u53D6\u6D88")throw f;console.error("[Recur SDK] Poll error:",f)}await new Promise(f=>setTimeout(f,Re))}if(I){console.log("[Recur SDK] 3D verification polling timeout");try{b.close()}catch{}throw p.setVerifying?.(!1),p.resetButton?.(),new Error("3D \u9A57\u8B49\u903E\u6642\uFF0C\u8ACB\u91CD\u8A66")}te&&(console.log("[Recur SDK] Handling success after popup close"),p.setVerifying?.(!1),e.onPaymentComplete&&(u.subscription?e.onPaymentComplete({id:u.subscription.id,status:"ACTIVE",planId:l.checkout.productId,amount:l.checkout.amount,billingPeriod:u.subscription.billingPeriod,currentPeriodStart:u.subscription.currentPeriodStart,currentPeriodEnd:u.subscription.currentPeriodEnd}):e.onPaymentComplete({id:re||L,status:"SUCCEEDED",planId:l.checkout.productId,amount:l.checkout.amount,billingPeriod:l.checkout.productType})),r&&r.remove());return}}if(e.onPaymentComplete)if(u.subscription)e.onPaymentComplete({id:u.subscription.id,status:u.subscription.status,planId:l.checkout.productId,amount:l.checkout.amount,billingPeriod:u.subscription.billingPeriod,currentPeriodStart:u.subscription.currentPeriodStart,currentPeriodEnd:u.subscription.currentPeriodEnd});else{let E=u.charge?.id||u.paymentIntent?.id,b=u.charge?.status||"SUCCEEDED";e.onPaymentComplete({id:E,status:b,planId:l.checkout.productId,amount:l.checkout.amount,billingPeriod:l.checkout.productType})}console.log("[Recur SDK] Checkout flow completed successfully!"),p.resetButton?.(),r&&r.remove()}catch(k){console.error("[Recur SDK] Payment error:",k);let C={code:"PAYMENT_FAILED",message:k instanceof Error?k.message:"Payment failed"};e.onError?.(C),p.resetButton?.()}})),console.log("[Recur SDK] Checkout flow initialized, waiting for user input...")}catch(n){console.error("[Recur SDK] Checkout error:",n),r&&r.remove();let o={code:"CHECKOUT_ERROR",message:n instanceof Error?n.message:"An unknown error occurred"};throw e.onError?.(o),n}}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=`
1540
1546
  position: fixed;
1541
1547
  top: 0;
1542
1548
  left: 0;
@@ -1575,7 +1581,7 @@
1575
1581
  border-radius: 50%;
1576
1582
  z-index: 10;
1577
1583
  transition: background 0.2s;
1578
- `,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 n=document.createElement("recur-payment-form-skeleton");return i.appendChild(n),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 Se(a){return new L(a)}var Oe={init:Se,RecurCheckout:L,RecurElements:T,createElements:Q};return Ae(je);})();
1584
+ `,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 n=document.createElement("recur-payment-form-skeleton");return i.appendChild(n),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 Se(a){return new M(a)}var Oe={init:Se,RecurCheckout:M,RecurElements:T,createElements:Q};return Ae(je);})();
1579
1585
  if (typeof window !== "undefined") {
1580
1586
  window.RecurCheckout = RecurCheckout.default;
1581
1587
  window.RecurElements = RecurCheckout.RecurElements;
@@ -0,0 +1,156 @@
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
+ // package.json
22
+ var package_default = {
23
+ version: "0.9.5"};
24
+
25
+ // src/server/resources/portal.ts
26
+ var SDK_VERSION = package_default.version;
27
+ var SDK_TYPE = "server";
28
+ var PortalSessions = class {
29
+ constructor(config) {
30
+ __publicField(this, "config");
31
+ this.config = config;
32
+ }
33
+ /**
34
+ * Create a portal session for a customer
35
+ *
36
+ * Customer can be identified using one of:
37
+ * - `customer`: Internal customer ID (highest priority)
38
+ * - `externalId`: External customer ID from your system
39
+ * - `email`: Customer's email address (lowest priority)
40
+ *
41
+ * @param params - Portal session creation parameters
42
+ * @returns The created portal session with URL
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * // By customer ID
47
+ * const session = await recur.portal.sessions.create({
48
+ * customer: 'cus_xxx',
49
+ * returnUrl: 'https://myapp.com/account',
50
+ * });
51
+ *
52
+ * // By email
53
+ * const session = await recur.portal.sessions.create({
54
+ * email: 'customer@example.com',
55
+ * returnUrl: 'https://myapp.com/account',
56
+ * });
57
+ *
58
+ * // By external ID
59
+ * const session = await recur.portal.sessions.create({
60
+ * externalId: 'user_123',
61
+ * returnUrl: 'https://myapp.com/account',
62
+ * });
63
+ *
64
+ * // Redirect the customer to the portal
65
+ * redirect(session.url);
66
+ * ```
67
+ */
68
+ async create(params) {
69
+ if (!params.customer && !params.email && !params.externalId) {
70
+ throw new RecurAPIError(
71
+ {
72
+ type: "invalid_request_error",
73
+ code: "missing_customer_identifier",
74
+ message: "At least one of customer, email, or externalId is required"
75
+ },
76
+ 400
77
+ );
78
+ }
79
+ const baseUrl = this.config.baseUrl || "https://api.recur.tw";
80
+ const response = await fetch(`${baseUrl}/v1/portal/sessions`, {
81
+ method: "POST",
82
+ headers: {
83
+ "Authorization": `Bearer ${this.config.secretKey}`,
84
+ "Content-Type": "application/json",
85
+ "X-Recur-SDK-Type": SDK_TYPE,
86
+ "X-Recur-SDK-Version": SDK_VERSION,
87
+ "X-Recur-Source": "server"
88
+ },
89
+ body: JSON.stringify({
90
+ customerId: params.customer,
91
+ email: params.email,
92
+ externalId: params.externalId,
93
+ returnUrl: params.returnUrl,
94
+ configurationId: params.configuration,
95
+ locale: params.locale
96
+ })
97
+ });
98
+ const data = await response.json();
99
+ if (!response.ok) {
100
+ const error = data.error;
101
+ throw new RecurAPIError(error, response.status);
102
+ }
103
+ return {
104
+ id: data.id,
105
+ object: "portal.session",
106
+ url: data.url,
107
+ customer: data.customerId,
108
+ returnUrl: data.returnUrl,
109
+ status: data.status,
110
+ expiresAt: data.expiresAt,
111
+ accessedAt: data.accessedAt,
112
+ createdAt: data.createdAt
113
+ };
114
+ }
115
+ };
116
+ var Portal = class {
117
+ constructor(config) {
118
+ __publicField(this, "sessions");
119
+ this.sessions = new PortalSessions(config);
120
+ }
121
+ };
122
+
123
+ // src/server/recur.ts
124
+ var Recur = class {
125
+ /**
126
+ * Create a new Recur client
127
+ *
128
+ * @param secretKey - Your Recur secret API key (sk_test_xxx or sk_live_xxx)
129
+ * @param options - Additional configuration options
130
+ */
131
+ constructor(secretKey, options) {
132
+ __publicField(this, "config");
133
+ /**
134
+ * Portal resource for managing customer portal sessions
135
+ */
136
+ __publicField(this, "portal");
137
+ if (!secretKey) {
138
+ throw new Error(
139
+ "Recur: secretKey is required. Get your API key from your organization settings."
140
+ );
141
+ }
142
+ if (!secretKey.startsWith("sk_")) {
143
+ throw new Error(
144
+ 'Recur: Invalid API key format. Secret keys should start with "sk_". Use your secret key (sk_xxx), not your publishable key (pk_xxx).'
145
+ );
146
+ }
147
+ this.config = {
148
+ secretKey,
149
+ baseUrl: options?.baseUrl || "https://api.recur.tw"
150
+ };
151
+ this.portal = new Portal(this.config);
152
+ }
153
+ };
154
+
155
+ exports.Recur = Recur;
156
+ exports.RecurAPIError = RecurAPIError;
package/dist/server.js ADDED
@@ -0,0 +1,153 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+
5
+ // src/server/types.ts
6
+ var RecurAPIError = class extends Error {
7
+ constructor(error, statusCode) {
8
+ super(error.message);
9
+ __publicField(this, "type");
10
+ __publicField(this, "code");
11
+ __publicField(this, "statusCode");
12
+ this.name = "RecurAPIError";
13
+ this.type = error.type;
14
+ this.code = error.code;
15
+ this.statusCode = statusCode;
16
+ }
17
+ };
18
+
19
+ // package.json
20
+ var package_default = {
21
+ version: "0.9.5"};
22
+
23
+ // src/server/resources/portal.ts
24
+ var SDK_VERSION = package_default.version;
25
+ var SDK_TYPE = "server";
26
+ var PortalSessions = class {
27
+ constructor(config) {
28
+ __publicField(this, "config");
29
+ this.config = config;
30
+ }
31
+ /**
32
+ * Create a portal session for a customer
33
+ *
34
+ * Customer can be identified using one of:
35
+ * - `customer`: Internal customer ID (highest priority)
36
+ * - `externalId`: External customer ID from your system
37
+ * - `email`: Customer's email address (lowest priority)
38
+ *
39
+ * @param params - Portal session creation parameters
40
+ * @returns The created portal session with URL
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * // By customer ID
45
+ * const session = await recur.portal.sessions.create({
46
+ * customer: 'cus_xxx',
47
+ * returnUrl: 'https://myapp.com/account',
48
+ * });
49
+ *
50
+ * // By email
51
+ * const session = await recur.portal.sessions.create({
52
+ * email: 'customer@example.com',
53
+ * returnUrl: 'https://myapp.com/account',
54
+ * });
55
+ *
56
+ * // By external ID
57
+ * const session = await recur.portal.sessions.create({
58
+ * externalId: 'user_123',
59
+ * returnUrl: 'https://myapp.com/account',
60
+ * });
61
+ *
62
+ * // Redirect the customer to the portal
63
+ * redirect(session.url);
64
+ * ```
65
+ */
66
+ async create(params) {
67
+ if (!params.customer && !params.email && !params.externalId) {
68
+ throw new RecurAPIError(
69
+ {
70
+ type: "invalid_request_error",
71
+ code: "missing_customer_identifier",
72
+ message: "At least one of customer, email, or externalId is required"
73
+ },
74
+ 400
75
+ );
76
+ }
77
+ const baseUrl = this.config.baseUrl || "https://api.recur.tw";
78
+ const response = await fetch(`${baseUrl}/v1/portal/sessions`, {
79
+ method: "POST",
80
+ headers: {
81
+ "Authorization": `Bearer ${this.config.secretKey}`,
82
+ "Content-Type": "application/json",
83
+ "X-Recur-SDK-Type": SDK_TYPE,
84
+ "X-Recur-SDK-Version": SDK_VERSION,
85
+ "X-Recur-Source": "server"
86
+ },
87
+ body: JSON.stringify({
88
+ customerId: params.customer,
89
+ email: params.email,
90
+ externalId: params.externalId,
91
+ returnUrl: params.returnUrl,
92
+ configurationId: params.configuration,
93
+ locale: params.locale
94
+ })
95
+ });
96
+ const data = await response.json();
97
+ if (!response.ok) {
98
+ const error = data.error;
99
+ throw new RecurAPIError(error, response.status);
100
+ }
101
+ return {
102
+ id: data.id,
103
+ object: "portal.session",
104
+ url: data.url,
105
+ customer: data.customerId,
106
+ returnUrl: data.returnUrl,
107
+ status: data.status,
108
+ expiresAt: data.expiresAt,
109
+ accessedAt: data.accessedAt,
110
+ createdAt: data.createdAt
111
+ };
112
+ }
113
+ };
114
+ var Portal = class {
115
+ constructor(config) {
116
+ __publicField(this, "sessions");
117
+ this.sessions = new PortalSessions(config);
118
+ }
119
+ };
120
+
121
+ // src/server/recur.ts
122
+ var Recur = class {
123
+ /**
124
+ * Create a new Recur client
125
+ *
126
+ * @param secretKey - Your Recur secret API key (sk_test_xxx or sk_live_xxx)
127
+ * @param options - Additional configuration options
128
+ */
129
+ constructor(secretKey, options) {
130
+ __publicField(this, "config");
131
+ /**
132
+ * Portal resource for managing customer portal sessions
133
+ */
134
+ __publicField(this, "portal");
135
+ if (!secretKey) {
136
+ throw new Error(
137
+ "Recur: secretKey is required. Get your API key from your organization settings."
138
+ );
139
+ }
140
+ if (!secretKey.startsWith("sk_")) {
141
+ throw new Error(
142
+ 'Recur: Invalid API key format. Secret keys should start with "sk_". Use your secret key (sk_xxx), not your publishable key (pk_xxx).'
143
+ );
144
+ }
145
+ this.config = {
146
+ secretKey,
147
+ baseUrl: options?.baseUrl || "https://api.recur.tw"
148
+ };
149
+ this.portal = new Portal(this.config);
150
+ }
151
+ };
152
+
153
+ export { Recur, RecurAPIError };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "recur-tw",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "description": "React & Vanilla JS SDK for embedding subscription checkout flows (Taiwan / PAYUNi)",
5
5
  "type": "module",
6
6
  "private": false,