recur-tw 0.16.0 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2814,7 +2814,7 @@ function toCamelCase(obj) {
2814
2814
 
2815
2815
  // package.json
2816
2816
  var package_default = {
2817
- version: "0.15.0"};
2817
+ version: "0.16.1"};
2818
2818
  var SDK_VERSION = package_default.version;
2819
2819
  var SDK_TYPE = "react";
2820
2820
  var RecurContext = React.createContext(null);
@@ -3646,17 +3646,77 @@ function RecurProvider({ children, config: initialConfig = {}, customer: custome
3646
3646
  },
3647
3647
  [config]
3648
3648
  );
3649
+ const createCheckoutSession = React.useCallback(
3650
+ async (options) => {
3651
+ const baseUrl = config.baseUrl || "https://api.recur.tw";
3652
+ if (!options.productId && !options.productSlug) {
3653
+ throw new Error("Either productId or productSlug is required");
3654
+ }
3655
+ if (!options.successUrl) {
3656
+ throw new Error("successUrl is required for hosted checkout");
3657
+ }
3658
+ if (!options.cancelUrl) {
3659
+ throw new Error("cancelUrl is required for hosted checkout");
3660
+ }
3661
+ if (!config.publishableKey) {
3662
+ throw new Error("publishableKey is required");
3663
+ }
3664
+ const requestBody = {
3665
+ successUrl: options.successUrl,
3666
+ cancelUrl: options.cancelUrl
3667
+ };
3668
+ if (options.productId) requestBody.productId = options.productId;
3669
+ if (options.productSlug) requestBody.productSlug = options.productSlug;
3670
+ if (options.mode) requestBody.mode = options.mode;
3671
+ if (options.customerEmail) requestBody.customerEmail = options.customerEmail;
3672
+ if (options.customerName) requestBody.customerName = options.customerName;
3673
+ if (options.externalCustomerId) requestBody.externalCustomerId = options.externalCustomerId;
3674
+ const response = await fetch(`${baseUrl}/v1/checkout/sessions`, {
3675
+ method: "POST",
3676
+ headers: {
3677
+ "Content-Type": "application/json",
3678
+ "X-Recur-Publishable-Key": config.publishableKey,
3679
+ "X-Recur-SDK-Type": SDK_TYPE,
3680
+ "X-Recur-SDK-Version": SDK_VERSION,
3681
+ "X-Recur-Source": typeof window !== "undefined" ? window.location.origin : "server"
3682
+ },
3683
+ body: JSON.stringify(requestBody)
3684
+ });
3685
+ if (!response.ok) {
3686
+ const error = await response.json().catch(() => ({}));
3687
+ throw new Error(error.error?.message || error.error || "Failed to create checkout session");
3688
+ }
3689
+ const rawResult = await response.json();
3690
+ const result = toCamelCase(rawResult);
3691
+ return {
3692
+ id: result.id,
3693
+ url: result.url,
3694
+ expiresAt: result.expiresAt,
3695
+ clientSecret: result.clientSecret
3696
+ };
3697
+ },
3698
+ [config]
3699
+ );
3700
+ const redirectToCheckout = React.useCallback(
3701
+ async (options) => {
3702
+ const session = await createCheckoutSession(options);
3703
+ window.location.href = session.url;
3704
+ },
3705
+ [createCheckoutSession]
3706
+ );
3649
3707
  const value = React.useMemo(
3650
3708
  () => ({
3651
3709
  config,
3652
3710
  checkout,
3711
+ redirectToCheckout,
3712
+ createCheckoutSession,
3653
3713
  fetchProducts,
3654
3714
  fetchPlans,
3655
3715
  isCheckingOut,
3656
3716
  updateConfig,
3657
3717
  getCheckoutStatus
3658
3718
  }),
3659
- [config, checkout, fetchProducts, fetchPlans, isCheckingOut, updateConfig, getCheckoutStatus]
3719
+ [config, checkout, redirectToCheckout, createCheckoutSession, fetchProducts, fetchPlans, isCheckingOut, updateConfig, getCheckoutStatus]
3660
3720
  );
3661
3721
  return /* @__PURE__ */ jsxRuntime.jsx(RecurContext.Provider, { value, children: /* @__PURE__ */ jsxRuntime.jsx(CustomerContext.Provider, { value: customerContextValue, children }) });
3662
3722
  }
package/dist/index.d.cts CHANGED
@@ -529,15 +529,103 @@ interface FetchProductsOptions {
529
529
  */
530
530
  type?: 'SUBSCRIPTION' | 'ONE_TIME' | 'CREDITS' | 'DONATION';
531
531
  }
532
+ /**
533
+ * Options for redirectToCheckout() and createCheckoutSession()
534
+ * Used with the Hosted Checkout flow (checkout.recur.tw)
535
+ */
536
+ interface RedirectToCheckoutOptions {
537
+ /**
538
+ * Product ID to purchase
539
+ * Either productId or productSlug must be provided
540
+ */
541
+ productId?: string;
542
+ /**
543
+ * Product slug to purchase (alternative to productId)
544
+ * Either productId or productSlug must be provided
545
+ *
546
+ * @example 'premium-monthly', 'basic-yearly'
547
+ */
548
+ productSlug?: string;
549
+ /** Checkout mode: PAYMENT (one-time), SUBSCRIPTION (recurring), or SETUP (save card) */
550
+ mode?: 'PAYMENT' | 'SUBSCRIPTION' | 'SETUP';
551
+ /** URL to redirect after successful payment */
552
+ successUrl: string;
553
+ /** URL to redirect if user cancels */
554
+ cancelUrl: string;
555
+ /** Optional: Pre-fill customer email */
556
+ customerEmail?: string;
557
+ /** Optional: Pre-fill customer name */
558
+ customerName?: string;
559
+ /**
560
+ * External customer ID from your system
561
+ * Use this to link Recur subscriptions to your existing users
562
+ *
563
+ * @example 'user_123', 'cus_abc456'
564
+ */
565
+ externalCustomerId?: string;
566
+ }
567
+ /**
568
+ * Result from creating a checkout session
569
+ */
570
+ interface CheckoutSessionResult {
571
+ /** Checkout session ID */
572
+ id: string;
573
+ /** URL to redirect customer to hosted checkout */
574
+ url: string;
575
+ /** Session expiry time (ISO 8601) */
576
+ expiresAt: string;
577
+ /** Stripe-like client secret for secure status polling */
578
+ clientSecret?: string;
579
+ }
532
580
  interface RecurContextValue {
533
581
  /**
534
582
  * Current configuration
535
583
  */
536
584
  config: RecurConfig;
537
585
  /**
538
- * Initiate checkout flow
586
+ * Initiate checkout flow (embedded/modal mode)
587
+ *
588
+ * Uses PAYUNi SDK to render payment form on your page.
589
+ * Requires a registered domain — does NOT work on localhost.
590
+ *
591
+ * For localhost development or simpler integration, use redirectToCheckout() instead.
539
592
  */
540
593
  checkout: (options: CheckoutOptions) => Promise<void>;
594
+ /**
595
+ * Redirect to Hosted Checkout page (recommended)
596
+ *
597
+ * Creates a checkout session and redirects the customer to checkout.recur.tw.
598
+ * This is the simplest integration — works on any domain including localhost.
599
+ *
600
+ * @example
601
+ * ```tsx
602
+ * const { redirectToCheckout } = useRecur();
603
+ * await redirectToCheckout({
604
+ * productId: 'prod_xxx',
605
+ * successUrl: `${window.location.origin}/success`,
606
+ * cancelUrl: `${window.location.origin}/cancel`,
607
+ * });
608
+ * ```
609
+ */
610
+ redirectToCheckout: (options: RedirectToCheckoutOptions) => Promise<void>;
611
+ /**
612
+ * Create a checkout session without redirecting
613
+ *
614
+ * Useful when you want to control the redirect yourself,
615
+ * or open checkout in a new tab/window.
616
+ *
617
+ * @example
618
+ * ```tsx
619
+ * const { createCheckoutSession } = useRecur();
620
+ * const session = await createCheckoutSession({
621
+ * productId: 'prod_xxx',
622
+ * successUrl: '/success',
623
+ * cancelUrl: '/cancel',
624
+ * });
625
+ * window.open(session.url, '_blank');
626
+ * ```
627
+ */
628
+ createCheckoutSession: (options: RedirectToCheckoutOptions) => Promise<CheckoutSessionResult>;
541
629
  /**
542
630
  * Fetch available products
543
631
  */
@@ -1672,4 +1760,4 @@ declare function PromoCodeInput({ promo, placeholder, applyText, clearText, disa
1672
1760
  */
1673
1761
  declare function useCustomer(): UseCustomerResult;
1674
1762
 
1675
- export { type ApiErrorCode, type ApplicableProduct, type CheckDeniedReason, type CheckOptions, type CheckResult, type CheckoutError, type CheckoutErrorDetails, type CheckoutOptions, type CheckoutResult, type CustomerIdentifier, type DuplicateSubscriptionDetails, type Entitlement, type EntitlementCustomer, type EntitlementStatus, type EntitlementSubscription, type PaymentFailedAction, type PaymentFailureCode, type PaymentFailureDetails, type Plan, type PlansResult, type Product, type ProductMetadata, type ProductsResult, type PromoCodeDiscount, PromoCodeInput, type PromoCodeInputProps, type PromoCodeStatus, type RecurConfig, type RecurContextValue, RecurProvider, type RecurProviderProps, type SubscriptionResult, type UseCustomerResult, type UseProductsOptions as UsePlansOptions, type UseProductsResult as UsePlansResult, type UseProductsOptions, type UseProductsResult, type UsePromoCodeOptions, type UsePromoCodeReturn, type UseSubscribeOptions, type UseSubscribeResult, useCustomer, useProducts as usePlans, useProducts, usePromoCode, useRecur, useSubscribe };
1763
+ export { type ApiErrorCode, type ApplicableProduct, type CheckDeniedReason, type CheckOptions, type CheckResult, type CheckoutError, type CheckoutErrorDetails, type CheckoutOptions, type CheckoutResult, type CheckoutSessionResult, type CustomerIdentifier, type DuplicateSubscriptionDetails, type Entitlement, type EntitlementCustomer, type EntitlementStatus, type EntitlementSubscription, type PaymentFailedAction, type PaymentFailureCode, type PaymentFailureDetails, type Plan, type PlansResult, type Product, type ProductMetadata, type ProductsResult, type PromoCodeDiscount, PromoCodeInput, type PromoCodeInputProps, type PromoCodeStatus, type RecurConfig, type RecurContextValue, RecurProvider, type RecurProviderProps, type RedirectToCheckoutOptions, type SubscriptionResult, type UseCustomerResult, type UseProductsOptions as UsePlansOptions, type UseProductsResult as UsePlansResult, type UseProductsOptions, type UseProductsResult, type UsePromoCodeOptions, type UsePromoCodeReturn, type UseSubscribeOptions, type UseSubscribeResult, useCustomer, useProducts as usePlans, useProducts, usePromoCode, useRecur, useSubscribe };
package/dist/index.d.ts CHANGED
@@ -529,15 +529,103 @@ interface FetchProductsOptions {
529
529
  */
530
530
  type?: 'SUBSCRIPTION' | 'ONE_TIME' | 'CREDITS' | 'DONATION';
531
531
  }
532
+ /**
533
+ * Options for redirectToCheckout() and createCheckoutSession()
534
+ * Used with the Hosted Checkout flow (checkout.recur.tw)
535
+ */
536
+ interface RedirectToCheckoutOptions {
537
+ /**
538
+ * Product ID to purchase
539
+ * Either productId or productSlug must be provided
540
+ */
541
+ productId?: string;
542
+ /**
543
+ * Product slug to purchase (alternative to productId)
544
+ * Either productId or productSlug must be provided
545
+ *
546
+ * @example 'premium-monthly', 'basic-yearly'
547
+ */
548
+ productSlug?: string;
549
+ /** Checkout mode: PAYMENT (one-time), SUBSCRIPTION (recurring), or SETUP (save card) */
550
+ mode?: 'PAYMENT' | 'SUBSCRIPTION' | 'SETUP';
551
+ /** URL to redirect after successful payment */
552
+ successUrl: string;
553
+ /** URL to redirect if user cancels */
554
+ cancelUrl: string;
555
+ /** Optional: Pre-fill customer email */
556
+ customerEmail?: string;
557
+ /** Optional: Pre-fill customer name */
558
+ customerName?: string;
559
+ /**
560
+ * External customer ID from your system
561
+ * Use this to link Recur subscriptions to your existing users
562
+ *
563
+ * @example 'user_123', 'cus_abc456'
564
+ */
565
+ externalCustomerId?: string;
566
+ }
567
+ /**
568
+ * Result from creating a checkout session
569
+ */
570
+ interface CheckoutSessionResult {
571
+ /** Checkout session ID */
572
+ id: string;
573
+ /** URL to redirect customer to hosted checkout */
574
+ url: string;
575
+ /** Session expiry time (ISO 8601) */
576
+ expiresAt: string;
577
+ /** Stripe-like client secret for secure status polling */
578
+ clientSecret?: string;
579
+ }
532
580
  interface RecurContextValue {
533
581
  /**
534
582
  * Current configuration
535
583
  */
536
584
  config: RecurConfig;
537
585
  /**
538
- * Initiate checkout flow
586
+ * Initiate checkout flow (embedded/modal mode)
587
+ *
588
+ * Uses PAYUNi SDK to render payment form on your page.
589
+ * Requires a registered domain — does NOT work on localhost.
590
+ *
591
+ * For localhost development or simpler integration, use redirectToCheckout() instead.
539
592
  */
540
593
  checkout: (options: CheckoutOptions) => Promise<void>;
594
+ /**
595
+ * Redirect to Hosted Checkout page (recommended)
596
+ *
597
+ * Creates a checkout session and redirects the customer to checkout.recur.tw.
598
+ * This is the simplest integration — works on any domain including localhost.
599
+ *
600
+ * @example
601
+ * ```tsx
602
+ * const { redirectToCheckout } = useRecur();
603
+ * await redirectToCheckout({
604
+ * productId: 'prod_xxx',
605
+ * successUrl: `${window.location.origin}/success`,
606
+ * cancelUrl: `${window.location.origin}/cancel`,
607
+ * });
608
+ * ```
609
+ */
610
+ redirectToCheckout: (options: RedirectToCheckoutOptions) => Promise<void>;
611
+ /**
612
+ * Create a checkout session without redirecting
613
+ *
614
+ * Useful when you want to control the redirect yourself,
615
+ * or open checkout in a new tab/window.
616
+ *
617
+ * @example
618
+ * ```tsx
619
+ * const { createCheckoutSession } = useRecur();
620
+ * const session = await createCheckoutSession({
621
+ * productId: 'prod_xxx',
622
+ * successUrl: '/success',
623
+ * cancelUrl: '/cancel',
624
+ * });
625
+ * window.open(session.url, '_blank');
626
+ * ```
627
+ */
628
+ createCheckoutSession: (options: RedirectToCheckoutOptions) => Promise<CheckoutSessionResult>;
541
629
  /**
542
630
  * Fetch available products
543
631
  */
@@ -1672,4 +1760,4 @@ declare function PromoCodeInput({ promo, placeholder, applyText, clearText, disa
1672
1760
  */
1673
1761
  declare function useCustomer(): UseCustomerResult;
1674
1762
 
1675
- export { type ApiErrorCode, type ApplicableProduct, type CheckDeniedReason, type CheckOptions, type CheckResult, type CheckoutError, type CheckoutErrorDetails, type CheckoutOptions, type CheckoutResult, type CustomerIdentifier, type DuplicateSubscriptionDetails, type Entitlement, type EntitlementCustomer, type EntitlementStatus, type EntitlementSubscription, type PaymentFailedAction, type PaymentFailureCode, type PaymentFailureDetails, type Plan, type PlansResult, type Product, type ProductMetadata, type ProductsResult, type PromoCodeDiscount, PromoCodeInput, type PromoCodeInputProps, type PromoCodeStatus, type RecurConfig, type RecurContextValue, RecurProvider, type RecurProviderProps, type SubscriptionResult, type UseCustomerResult, type UseProductsOptions as UsePlansOptions, type UseProductsResult as UsePlansResult, type UseProductsOptions, type UseProductsResult, type UsePromoCodeOptions, type UsePromoCodeReturn, type UseSubscribeOptions, type UseSubscribeResult, useCustomer, useProducts as usePlans, useProducts, usePromoCode, useRecur, useSubscribe };
1763
+ export { type ApiErrorCode, type ApplicableProduct, type CheckDeniedReason, type CheckOptions, type CheckResult, type CheckoutError, type CheckoutErrorDetails, type CheckoutOptions, type CheckoutResult, type CheckoutSessionResult, type CustomerIdentifier, type DuplicateSubscriptionDetails, type Entitlement, type EntitlementCustomer, type EntitlementStatus, type EntitlementSubscription, type PaymentFailedAction, type PaymentFailureCode, type PaymentFailureDetails, type Plan, type PlansResult, type Product, type ProductMetadata, type ProductsResult, type PromoCodeDiscount, PromoCodeInput, type PromoCodeInputProps, type PromoCodeStatus, type RecurConfig, type RecurContextValue, RecurProvider, type RecurProviderProps, type RedirectToCheckoutOptions, type SubscriptionResult, type UseCustomerResult, type UseProductsOptions as UsePlansOptions, type UseProductsResult as UsePlansResult, type UseProductsOptions, type UseProductsResult, type UsePromoCodeOptions, type UsePromoCodeReturn, type UseSubscribeOptions, type UseSubscribeResult, useCustomer, useProducts as usePlans, useProducts, usePromoCode, useRecur, useSubscribe };
package/dist/index.js CHANGED
@@ -2808,7 +2808,7 @@ function toCamelCase(obj) {
2808
2808
 
2809
2809
  // package.json
2810
2810
  var package_default = {
2811
- version: "0.15.0"};
2811
+ version: "0.16.1"};
2812
2812
  var SDK_VERSION = package_default.version;
2813
2813
  var SDK_TYPE = "react";
2814
2814
  var RecurContext = createContext(null);
@@ -3640,17 +3640,77 @@ function RecurProvider({ children, config: initialConfig = {}, customer: custome
3640
3640
  },
3641
3641
  [config]
3642
3642
  );
3643
+ const createCheckoutSession = useCallback(
3644
+ async (options) => {
3645
+ const baseUrl = config.baseUrl || "https://api.recur.tw";
3646
+ if (!options.productId && !options.productSlug) {
3647
+ throw new Error("Either productId or productSlug is required");
3648
+ }
3649
+ if (!options.successUrl) {
3650
+ throw new Error("successUrl is required for hosted checkout");
3651
+ }
3652
+ if (!options.cancelUrl) {
3653
+ throw new Error("cancelUrl is required for hosted checkout");
3654
+ }
3655
+ if (!config.publishableKey) {
3656
+ throw new Error("publishableKey is required");
3657
+ }
3658
+ const requestBody = {
3659
+ successUrl: options.successUrl,
3660
+ cancelUrl: options.cancelUrl
3661
+ };
3662
+ if (options.productId) requestBody.productId = options.productId;
3663
+ if (options.productSlug) requestBody.productSlug = options.productSlug;
3664
+ if (options.mode) requestBody.mode = options.mode;
3665
+ if (options.customerEmail) requestBody.customerEmail = options.customerEmail;
3666
+ if (options.customerName) requestBody.customerName = options.customerName;
3667
+ if (options.externalCustomerId) requestBody.externalCustomerId = options.externalCustomerId;
3668
+ const response = await fetch(`${baseUrl}/v1/checkout/sessions`, {
3669
+ method: "POST",
3670
+ headers: {
3671
+ "Content-Type": "application/json",
3672
+ "X-Recur-Publishable-Key": config.publishableKey,
3673
+ "X-Recur-SDK-Type": SDK_TYPE,
3674
+ "X-Recur-SDK-Version": SDK_VERSION,
3675
+ "X-Recur-Source": typeof window !== "undefined" ? window.location.origin : "server"
3676
+ },
3677
+ body: JSON.stringify(requestBody)
3678
+ });
3679
+ if (!response.ok) {
3680
+ const error = await response.json().catch(() => ({}));
3681
+ throw new Error(error.error?.message || error.error || "Failed to create checkout session");
3682
+ }
3683
+ const rawResult = await response.json();
3684
+ const result = toCamelCase(rawResult);
3685
+ return {
3686
+ id: result.id,
3687
+ url: result.url,
3688
+ expiresAt: result.expiresAt,
3689
+ clientSecret: result.clientSecret
3690
+ };
3691
+ },
3692
+ [config]
3693
+ );
3694
+ const redirectToCheckout = useCallback(
3695
+ async (options) => {
3696
+ const session = await createCheckoutSession(options);
3697
+ window.location.href = session.url;
3698
+ },
3699
+ [createCheckoutSession]
3700
+ );
3643
3701
  const value = useMemo(
3644
3702
  () => ({
3645
3703
  config,
3646
3704
  checkout,
3705
+ redirectToCheckout,
3706
+ createCheckoutSession,
3647
3707
  fetchProducts,
3648
3708
  fetchPlans,
3649
3709
  isCheckingOut,
3650
3710
  updateConfig,
3651
3711
  getCheckoutStatus
3652
3712
  }),
3653
- [config, checkout, fetchProducts, fetchPlans, isCheckingOut, updateConfig, getCheckoutStatus]
3713
+ [config, checkout, redirectToCheckout, createCheckoutSession, fetchProducts, fetchPlans, isCheckingOut, updateConfig, getCheckoutStatus]
3654
3714
  );
3655
3715
  return /* @__PURE__ */ jsx(RecurContext.Provider, { value, children: /* @__PURE__ */ jsx(CustomerContext.Provider, { value: customerContextValue, children }) });
3656
3716
  }
package/dist/recur.umd.js CHANGED
@@ -2101,7 +2101,7 @@
2101
2101
  <path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/>
2102
2102
  <circle cx="12" cy="7" r="4"/>
2103
2103
  </svg>
2104
- `}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"),o=this.getAttribute("return-url");this._isLoading=!0,this.render(),this.setupEventListeners();try{let i={};r&&(i.customerId=r),o&&(i.returnUrl=o);let n=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});if(!n.ok){let u=await n.json().catch(()=>({}));throw new Error(u.error?.message||u.message||`HTTP ${n.status}: Failed to create portal session`)}let a=await n.json(),c=a.url||a.portalUrl;if(!c)throw new Error("Portal URL not found in response");this._isLoading=!1,this.render(),this.setupEventListeners(),this.redirectToPortal(c)}catch(i){this._isLoading=!1,this.render(),this.setupEventListeners(),this.dispatchError(i instanceof Error?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",pe)});var Qt={};S(Qt,{RecurCheckout:()=>O,RecurElements:()=>z,create:()=>kt,createElements:()=>$e,default:()=>Zt,init:()=>vt});async function jt(){if(typeof window>"u"){console.warn("[Recur SDK] Cannot register Web Components: window is not defined");return}await Promise.all([Promise.resolve().then(()=>(Le(),Me)),Promise.resolve().then(()=>(He(),Ue)),Promise.resolve().then(()=>(Be(),Ne)),Promise.resolve().then(()=>(Oe(),ze)),Promise.resolve().then(()=>(tt(),et)),Promise.resolve().then(()=>(ot(),rt)),Promise.resolve().then(()=>(pt(),dt)),Promise.resolve().then(()=>(ht(),mt)),Promise.resolve().then(()=>(ft(),gt))]);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"&&jt();function Ft(s){return s.replace(/_([a-z])/g,(e,t)=>t.toUpperCase())}function w(s){if(s==null)return s;if(Array.isArray(s))return s.map(e=>w(e));if(s instanceof Date)return s;if(typeof s=="object"){let e={};for(let[t,r]of Object.entries(s)){let o=Ft(t);e[o]=w(r)}return e}return s}var me={name:"recur-tw",version:"0.15.0",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",test:"vitest run","test:watch":"vitest",lint:"eslint .","lint:fix":"eslint . --fix","type-check":"tsc --noEmit",examples:"npx http-server . -p 8080 -o /examples/"},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","./checkout":"./dist/checkout.js","./widget":"./dist/widget.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","@testing-library/dom":"^10.4.1","@testing-library/jest-dom":"^6.9.1","@testing-library/react":"^16.3.0","@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",jsdom:"^27.2.0",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",vitest:"^4.1.0"},dependencies:{"lit-html":"^3.3.1"}};var qt=me.version,Yt="vanilla",he=class{constructor(e){l(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":Yt,"X-Recur-SDK-Version":qt,"X-Recur-Source":typeof window<"u"?window.location.origin:"server"}}async createSubscription(e){let{customerName:t,customerEmail:r}=e,o=e.productId||e.planId,i=e.productSlug;if(!o&&!i)throw new Error("Either productId or productSlug is required");let n={customerName:t,customerEmail:r};o&&(n.productId=o),i&&(n.productSlug=i);let a=await fetch(`${this.config.baseUrl}/v1/subscriptions`,{method:"POST",headers:this.getHeaders(),body:JSON.stringify(n)});if(!a.ok){let u=await a.json().catch(()=>({}));throw{code:u.error||"CHECKOUT_FAILED",message:u.message||"Failed to initiate checkout",details:u}}let c=await a.json();return w(c)}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 o=await r.json();return w(o)}async fetchPlans(){return{plans:(await this.fetchProducts({type:"SUBSCRIPTION"})).data}}getConfig(){return{...this.config}}};var ge=class{constructor(e,t){l(this,"config");l(this,"options");l(this,"container");l(this,"checkoutId",null);l(this,"sdkToken",null);l(this,"sdkTimestamp",null);l(this,"creditToken",null);l(this,"sdkEnv","S");l(this,"payuniSDK",null);l(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.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(),o=w(r);this.checkoutId=o.checkout.id,this.sdkToken=o.sdkToken,this.sdkTimestamp=o.sdkTimestamp||null,this.creditToken=o.creditToken||null,this.sdkEnv=o.livemode?"P":"S"}renderHTML(){this.container.innerHTML=`
2104
+ `}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"),o=this.getAttribute("return-url");this._isLoading=!0,this.render(),this.setupEventListeners();try{let i={};r&&(i.customerId=r),o&&(i.returnUrl=o);let n=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});if(!n.ok){let u=await n.json().catch(()=>({}));throw new Error(u.error?.message||u.message||`HTTP ${n.status}: Failed to create portal session`)}let a=await n.json(),c=a.url||a.portalUrl;if(!c)throw new Error("Portal URL not found in response");this._isLoading=!1,this.render(),this.setupEventListeners(),this.redirectToPortal(c)}catch(i){this._isLoading=!1,this.render(),this.setupEventListeners(),this.dispatchError(i instanceof Error?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",pe)});var Qt={};S(Qt,{RecurCheckout:()=>O,RecurElements:()=>z,create:()=>kt,createElements:()=>$e,default:()=>Zt,init:()=>vt});async function jt(){if(typeof window>"u"){console.warn("[Recur SDK] Cannot register Web Components: window is not defined");return}await Promise.all([Promise.resolve().then(()=>(Le(),Me)),Promise.resolve().then(()=>(He(),Ue)),Promise.resolve().then(()=>(Be(),Ne)),Promise.resolve().then(()=>(Oe(),ze)),Promise.resolve().then(()=>(tt(),et)),Promise.resolve().then(()=>(ot(),rt)),Promise.resolve().then(()=>(pt(),dt)),Promise.resolve().then(()=>(ht(),mt)),Promise.resolve().then(()=>(ft(),gt))]);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"&&jt();function Ft(s){return s.replace(/_([a-z])/g,(e,t)=>t.toUpperCase())}function w(s){if(s==null)return s;if(Array.isArray(s))return s.map(e=>w(e));if(s instanceof Date)return s;if(typeof s=="object"){let e={};for(let[t,r]of Object.entries(s)){let o=Ft(t);e[o]=w(r)}return e}return s}var me={name:"recur-tw",version:"0.16.1",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",test:"vitest run","test:watch":"vitest",lint:"eslint .","lint:fix":"eslint . --fix","type-check":"tsc --noEmit",examples:"npx http-server . -p 8080 -o /examples/"},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","./checkout":"./dist/checkout.js","./widget":"./dist/widget.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","@testing-library/dom":"^10.4.1","@testing-library/jest-dom":"^6.9.1","@testing-library/react":"^16.3.0","@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",jsdom:"^27.2.0",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",vitest:"^4.1.0"},dependencies:{"lit-html":"^3.3.1"}};var qt=me.version,Yt="vanilla",he=class{constructor(e){l(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":Yt,"X-Recur-SDK-Version":qt,"X-Recur-Source":typeof window<"u"?window.location.origin:"server"}}async createSubscription(e){let{customerName:t,customerEmail:r}=e,o=e.productId||e.planId,i=e.productSlug;if(!o&&!i)throw new Error("Either productId or productSlug is required");let n={customerName:t,customerEmail:r};o&&(n.productId=o),i&&(n.productSlug=i);let a=await fetch(`${this.config.baseUrl}/v1/subscriptions`,{method:"POST",headers:this.getHeaders(),body:JSON.stringify(n)});if(!a.ok){let u=await a.json().catch(()=>({}));throw{code:u.error||"CHECKOUT_FAILED",message:u.message||"Failed to initiate checkout",details:u}}let c=await a.json();return w(c)}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 o=await r.json();return w(o)}async fetchPlans(){return{plans:(await this.fetchProducts({type:"SUBSCRIPTION"})).data}}getConfig(){return{...this.config}}};var ge=class{constructor(e,t){l(this,"config");l(this,"options");l(this,"container");l(this,"checkoutId",null);l(this,"sdkToken",null);l(this,"sdkTimestamp",null);l(this,"creditToken",null);l(this,"sdkEnv","S");l(this,"payuniSDK",null);l(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.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(),o=w(r);this.checkoutId=o.checkout.id,this.sdkToken=o.sdkToken,this.sdkTimestamp=o.sdkTimestamp||null,this.creditToken=o.creditToken||null,this.sdkEnv=o.livemode?"P":"S"}renderHTML(){this.container.innerHTML=`
2105
2105
  <div class="recur-embedded-checkout" style="max-width: 400px; margin: 0 auto; padding: 20px; font-family: system-ui, -apple-system, sans-serif;">
2106
2106
  <div class="recur-checkout-header" style="margin-bottom: 24px;">
2107
2107
  <h2 style="margin: 0 0 8px 0; font-size: 20px; font-weight: 600; color: #111;">Subscribe</h2>