recur-tw 0.7.0 → 0.7.2

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
@@ -2232,6 +2232,271 @@ var init_checkout_button = __esm({
2232
2232
  }
2233
2233
  });
2234
2234
 
2235
+ // src/components/portal-button.ts
2236
+ var portal_button_exports = {};
2237
+ __export(portal_button_exports, {
2238
+ RecurPortalButton: () => RecurPortalButton
2239
+ });
2240
+ var RecurPortalButton;
2241
+ var init_portal_button = __esm({
2242
+ "src/components/portal-button.ts"() {
2243
+ RecurPortalButton = class extends HTMLElement {
2244
+ constructor() {
2245
+ super();
2246
+ __publicField(this, "_isLoading", false);
2247
+ __publicField(this, "handleClick", async (e) => {
2248
+ e.preventDefault();
2249
+ if (this._isLoading || this.hasAttribute("disabled")) {
2250
+ return;
2251
+ }
2252
+ const portalUrl = this.getAttribute("portal-url");
2253
+ const apiEndpoint = this.getAttribute("api-endpoint");
2254
+ if (portalUrl) {
2255
+ this.redirectToPortal(portalUrl);
2256
+ return;
2257
+ }
2258
+ if (apiEndpoint) {
2259
+ await this.fetchAndRedirect(apiEndpoint);
2260
+ return;
2261
+ }
2262
+ this.dispatchError("Missing required attribute: either portal-url or api-endpoint must be provided");
2263
+ });
2264
+ this.attachShadow({ mode: "open" });
2265
+ }
2266
+ static get observedAttributes() {
2267
+ return [
2268
+ "portal-url",
2269
+ "api-endpoint",
2270
+ "customer-id",
2271
+ "return-url",
2272
+ "button-text",
2273
+ "button-style",
2274
+ "disabled",
2275
+ "target"
2276
+ ];
2277
+ }
2278
+ connectedCallback() {
2279
+ this.render();
2280
+ this.setupEventListeners();
2281
+ }
2282
+ disconnectedCallback() {
2283
+ const button = this.shadowRoot?.querySelector("button");
2284
+ if (button) {
2285
+ button.removeEventListener("click", this.handleClick);
2286
+ }
2287
+ }
2288
+ attributeChangedCallback(_name, oldValue, newValue) {
2289
+ if (oldValue !== newValue) {
2290
+ this.render();
2291
+ }
2292
+ }
2293
+ render() {
2294
+ const buttonText = this.getAttribute("button-text") || this.textContent?.trim() || "\u7BA1\u7406\u8A02\u95B1";
2295
+ const buttonStyle = this.getAttribute("button-style") || "primary";
2296
+ const isDisabled = this.hasAttribute("disabled") || this._isLoading;
2297
+ this.shadowRoot.innerHTML = `
2298
+ <style>
2299
+ :host {
2300
+ display: inline-block;
2301
+ }
2302
+
2303
+ button {
2304
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
2305
+ font-size: 16px;
2306
+ font-weight: 500;
2307
+ padding: 12px 24px;
2308
+ border-radius: 8px;
2309
+ cursor: pointer;
2310
+ transition: all 0.2s ease;
2311
+ display: inline-flex;
2312
+ align-items: center;
2313
+ justify-content: center;
2314
+ gap: 8px;
2315
+ min-width: 120px;
2316
+ border: none;
2317
+ }
2318
+
2319
+ button:disabled {
2320
+ opacity: 0.6;
2321
+ cursor: not-allowed;
2322
+ }
2323
+
2324
+ /* Primary style (default) */
2325
+ button.primary {
2326
+ background: #18181b;
2327
+ color: #ffffff;
2328
+ }
2329
+
2330
+ button.primary:hover:not(:disabled) {
2331
+ background: #27272a;
2332
+ }
2333
+
2334
+ button.primary:active:not(:disabled) {
2335
+ background: #3f3f46;
2336
+ }
2337
+
2338
+ /* Outline style */
2339
+ button.outline {
2340
+ background: transparent;
2341
+ color: #18181b;
2342
+ border: 2px solid #18181b;
2343
+ }
2344
+
2345
+ button.outline:hover:not(:disabled) {
2346
+ background: #f4f4f5;
2347
+ }
2348
+
2349
+ button.outline:active:not(:disabled) {
2350
+ background: #e4e4e7;
2351
+ }
2352
+
2353
+ /* Gradient style */
2354
+ button.gradient {
2355
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
2356
+ color: #ffffff;
2357
+ }
2358
+
2359
+ button.gradient:hover:not(:disabled) {
2360
+ opacity: 0.9;
2361
+ }
2362
+
2363
+ button.gradient:active:not(:disabled) {
2364
+ opacity: 0.8;
2365
+ }
2366
+
2367
+ /* Link style */
2368
+ button.link {
2369
+ background: transparent;
2370
+ color: #3b82f6;
2371
+ padding: 4px 8px;
2372
+ min-width: auto;
2373
+ text-decoration: underline;
2374
+ }
2375
+
2376
+ button.link:hover:not(:disabled) {
2377
+ color: #2563eb;
2378
+ }
2379
+
2380
+ button.link:active:not(:disabled) {
2381
+ color: #1d4ed8;
2382
+ }
2383
+
2384
+ /* Loading spinner */
2385
+ .spinner {
2386
+ width: 16px;
2387
+ height: 16px;
2388
+ border: 2px solid currentColor;
2389
+ border-top-color: transparent;
2390
+ border-radius: 50%;
2391
+ animation: spin 0.6s linear infinite;
2392
+ }
2393
+
2394
+ @keyframes spin {
2395
+ to { transform: rotate(360deg); }
2396
+ }
2397
+
2398
+ /* Focus state */
2399
+ button:focus-visible {
2400
+ outline: 2px solid #3b82f6;
2401
+ outline-offset: 2px;
2402
+ }
2403
+
2404
+ /* Portal icon */
2405
+ .portal-icon {
2406
+ width: 18px;
2407
+ height: 18px;
2408
+ }
2409
+ </style>
2410
+
2411
+ <button
2412
+ class="${buttonStyle}"
2413
+ ${isDisabled ? "disabled" : ""}
2414
+ aria-busy="${this._isLoading}"
2415
+ >
2416
+ ${this._isLoading ? '<span class="spinner"></span>' : this.getPortalIcon()}
2417
+ <span>${this._isLoading ? "\u8655\u7406\u4E2D..." : buttonText}</span>
2418
+ </button>
2419
+ `;
2420
+ }
2421
+ getPortalIcon() {
2422
+ return `
2423
+ <svg class="portal-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2424
+ <path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/>
2425
+ <circle cx="12" cy="7" r="4"/>
2426
+ </svg>
2427
+ `;
2428
+ }
2429
+ setupEventListeners() {
2430
+ const button = this.shadowRoot?.querySelector("button");
2431
+ if (button) {
2432
+ button.addEventListener("click", this.handleClick);
2433
+ }
2434
+ }
2435
+ redirectToPortal(url) {
2436
+ const target = this.getAttribute("target");
2437
+ this.dispatchEvent(new CustomEvent("portal-redirect", {
2438
+ detail: { url },
2439
+ bubbles: true,
2440
+ composed: true
2441
+ }));
2442
+ if (target === "_blank") {
2443
+ window.open(url, "_blank", "noopener,noreferrer");
2444
+ } else {
2445
+ window.location.href = url;
2446
+ }
2447
+ }
2448
+ async fetchAndRedirect(apiEndpoint) {
2449
+ const customerId = this.getAttribute("customer-id");
2450
+ const returnUrl = this.getAttribute("return-url");
2451
+ this._isLoading = true;
2452
+ this.render();
2453
+ this.setupEventListeners();
2454
+ try {
2455
+ const requestBody = {};
2456
+ if (customerId) requestBody.customerId = customerId;
2457
+ if (returnUrl) requestBody.returnUrl = returnUrl;
2458
+ const response = await fetch(apiEndpoint, {
2459
+ method: "POST",
2460
+ headers: {
2461
+ "Content-Type": "application/json"
2462
+ },
2463
+ body: JSON.stringify(requestBody)
2464
+ });
2465
+ if (!response.ok) {
2466
+ const error = await response.json().catch(() => ({}));
2467
+ throw new Error(error.error?.message || error.message || `HTTP ${response.status}: Failed to create portal session`);
2468
+ }
2469
+ const data = await response.json();
2470
+ const portalUrl = data.url || data.portalUrl;
2471
+ if (!portalUrl) {
2472
+ throw new Error("Portal URL not found in response");
2473
+ }
2474
+ this._isLoading = false;
2475
+ this.render();
2476
+ this.setupEventListeners();
2477
+ this.redirectToPortal(portalUrl);
2478
+ } catch (error) {
2479
+ this._isLoading = false;
2480
+ this.render();
2481
+ this.setupEventListeners();
2482
+ this.dispatchError(error.message || "Failed to create portal session");
2483
+ }
2484
+ }
2485
+ dispatchError(message) {
2486
+ console.error("[recur-portal]", message);
2487
+ this.dispatchEvent(new CustomEvent("portal-error", {
2488
+ detail: { message },
2489
+ bubbles: true,
2490
+ composed: true
2491
+ }));
2492
+ }
2493
+ };
2494
+ if (typeof window !== "undefined" && !customElements.get("recur-portal")) {
2495
+ customElements.define("recur-portal", RecurPortalButton);
2496
+ }
2497
+ }
2498
+ });
2499
+
2235
2500
  // src/components/index.ts
2236
2501
  async function registerComponents() {
2237
2502
  if (typeof window === "undefined") {
@@ -2246,7 +2511,8 @@ async function registerComponents() {
2246
2511
  Promise.resolve().then(() => (init_payment_form_skeleton(), payment_form_skeleton_exports)),
2247
2512
  Promise.resolve().then(() => (init_toast(), toast_exports)),
2248
2513
  Promise.resolve().then(() => (init_payment_form(), payment_form_exports)),
2249
- Promise.resolve().then(() => (init_checkout_button(), checkout_button_exports))
2514
+ Promise.resolve().then(() => (init_checkout_button(), checkout_button_exports)),
2515
+ Promise.resolve().then(() => (init_portal_button(), portal_button_exports))
2250
2516
  ]);
2251
2517
  const components = [
2252
2518
  "recur-loading-spinner",
@@ -2257,7 +2523,8 @@ async function registerComponents() {
2257
2523
  "recur-toast",
2258
2524
  "recur-toast-container",
2259
2525
  "recur-payment-form",
2260
- "recur-checkout"
2526
+ "recur-checkout",
2527
+ "recur-portal"
2261
2528
  ];
2262
2529
  const unregistered = components.filter((name) => !customElements.get(name));
2263
2530
  if (unregistered.length > 0) {
@@ -2332,8 +2599,10 @@ function RecurProvider({ children, config: initialConfig = {} }) {
2332
2599
  containerElementId: config.containerElementId
2333
2600
  });
2334
2601
  setIsCheckingOut(true);
2335
- if (!options.planId) {
2336
- throw new Error("planId is required");
2602
+ const productId = options.productId || options.planId;
2603
+ const productSlug = options.productSlug;
2604
+ if (!productId && !productSlug) {
2605
+ throw new Error("Either productId or productSlug is required");
2337
2606
  }
2338
2607
  if (!config.publishableKey) {
2339
2608
  throw new Error("publishableKey is required");
@@ -2459,15 +2728,17 @@ function RecurProvider({ children, config: initialConfig = {} }) {
2459
2728
  }
2460
2729
  }
2461
2730
  console.log("[Recur SDK] Step 1: Creating checkout session...");
2731
+ const checkoutRequestBody = {
2732
+ customerName: options.customerName,
2733
+ customerEmail: options.customerEmail
2734
+ };
2735
+ if (productId) checkoutRequestBody.productId = productId;
2736
+ if (productSlug) checkoutRequestBody.productSlug = productSlug;
2737
+ if (options.externalCustomerId) checkoutRequestBody.externalCustomerId = options.externalCustomerId;
2462
2738
  const checkoutResponse = await fetch(`${baseUrl}/v1/checkouts`, {
2463
2739
  method: "POST",
2464
2740
  headers,
2465
- body: JSON.stringify({
2466
- productId: options.planId,
2467
- customerName: options.customerName,
2468
- customerEmail: options.customerEmail,
2469
- externalCustomerId: options.externalCustomerId
2470
- })
2741
+ body: JSON.stringify(checkoutRequestBody)
2471
2742
  });
2472
2743
  if (!checkoutResponse.ok) {
2473
2744
  const errorData = await checkoutResponse.json().catch(() => ({}));
package/dist/index.d.cts CHANGED
@@ -205,11 +205,24 @@ interface RecurConfig {
205
205
  }
206
206
  interface CheckoutOptions {
207
207
  /**
208
- * Plan ID to subscribe to
208
+ * Product ID to purchase
209
+ * Either productId or productSlug must be provided
209
210
  */
210
- planId: string;
211
+ productId?: string;
212
+ /**
213
+ * Product slug to purchase (alternative to productId)
214
+ * Either productId or productSlug must be provided
215
+ *
216
+ * @example 'premium-monthly', 'basic-yearly'
217
+ */
218
+ productSlug?: string;
211
219
  /**
212
- * Customer information
220
+ * @deprecated Use productId instead
221
+ * Plan ID to subscribe to (kept for backward compatibility)
222
+ */
223
+ planId?: string;
224
+ /**
225
+ * Customer name
213
226
  */
214
227
  customerName?: string;
215
228
  /**
@@ -530,6 +543,61 @@ declare global {
530
543
  }
531
544
  }
532
545
 
546
+ /**
547
+ * Recur Portal Button Web Component
548
+ *
549
+ * A simple, drop-in button for redirecting customers to the Customer Portal.
550
+ *
551
+ * Usage patterns:
552
+ *
553
+ * 1. Direct URL mode (recommended for server-rendered apps):
554
+ * Use when you already have the portal URL from your server.
555
+ * ```html
556
+ * <recur-portal
557
+ * portal-url="https://portal.recur.tw/s/ps_xxx">
558
+ * 管理訂閱
559
+ * </recur-portal>
560
+ * ```
561
+ *
562
+ * 2. API mode (for dynamic portal session creation):
563
+ * Calls your backend API endpoint to create a portal session.
564
+ * ```html
565
+ * <recur-portal
566
+ * api-endpoint="/api/create-portal-session"
567
+ * customer-id="cus_xxx">
568
+ * 管理訂閱
569
+ * </recur-portal>
570
+ * ```
571
+ *
572
+ * Styling:
573
+ * - button-style: "primary" | "outline" | "gradient" | "link"
574
+ * - button-text: Override button text (default: slot content or "管理訂閱")
575
+ *
576
+ * Events:
577
+ * - portal-redirect: Fired before redirecting to portal
578
+ * - portal-error: Fired when an error occurs
579
+ */
580
+ declare class RecurPortalButton extends HTMLElement {
581
+ private _isLoading;
582
+ static get observedAttributes(): string[];
583
+ constructor();
584
+ connectedCallback(): void;
585
+ disconnectedCallback(): void;
586
+ attributeChangedCallback(_name: string, oldValue: string, newValue: string): void;
587
+ private render;
588
+ private getPortalIcon;
589
+ private setupEventListeners;
590
+ private handleClick;
591
+ private redirectToPortal;
592
+ private fetchAndRedirect;
593
+ private dispatchError;
594
+ }
595
+ declare global {
596
+ interface HTMLElementTagNameMap {
597
+ 'recur-portal': RecurPortalButton;
598
+ }
599
+ }
600
+
533
601
  interface RecurProviderProps {
534
602
  children: React.ReactNode;
535
603
  config?: RecurConfig;
@@ -709,7 +777,7 @@ interface UseSubscribeResult {
709
777
  * key={plan.id}
710
778
  * plan={plan}
711
779
  * onSubscribe={() => subscribe({
712
- * planId: plan.id,
780
+ * productId: plan.id, // or productSlug: plan.slug
713
781
  * customerEmail: 'user@example.com',
714
782
  * customerName: 'John Doe'
715
783
  * })}
package/dist/index.d.ts CHANGED
@@ -205,11 +205,24 @@ interface RecurConfig {
205
205
  }
206
206
  interface CheckoutOptions {
207
207
  /**
208
- * Plan ID to subscribe to
208
+ * Product ID to purchase
209
+ * Either productId or productSlug must be provided
209
210
  */
210
- planId: string;
211
+ productId?: string;
212
+ /**
213
+ * Product slug to purchase (alternative to productId)
214
+ * Either productId or productSlug must be provided
215
+ *
216
+ * @example 'premium-monthly', 'basic-yearly'
217
+ */
218
+ productSlug?: string;
211
219
  /**
212
- * Customer information
220
+ * @deprecated Use productId instead
221
+ * Plan ID to subscribe to (kept for backward compatibility)
222
+ */
223
+ planId?: string;
224
+ /**
225
+ * Customer name
213
226
  */
214
227
  customerName?: string;
215
228
  /**
@@ -530,6 +543,61 @@ declare global {
530
543
  }
531
544
  }
532
545
 
546
+ /**
547
+ * Recur Portal Button Web Component
548
+ *
549
+ * A simple, drop-in button for redirecting customers to the Customer Portal.
550
+ *
551
+ * Usage patterns:
552
+ *
553
+ * 1. Direct URL mode (recommended for server-rendered apps):
554
+ * Use when you already have the portal URL from your server.
555
+ * ```html
556
+ * <recur-portal
557
+ * portal-url="https://portal.recur.tw/s/ps_xxx">
558
+ * 管理訂閱
559
+ * </recur-portal>
560
+ * ```
561
+ *
562
+ * 2. API mode (for dynamic portal session creation):
563
+ * Calls your backend API endpoint to create a portal session.
564
+ * ```html
565
+ * <recur-portal
566
+ * api-endpoint="/api/create-portal-session"
567
+ * customer-id="cus_xxx">
568
+ * 管理訂閱
569
+ * </recur-portal>
570
+ * ```
571
+ *
572
+ * Styling:
573
+ * - button-style: "primary" | "outline" | "gradient" | "link"
574
+ * - button-text: Override button text (default: slot content or "管理訂閱")
575
+ *
576
+ * Events:
577
+ * - portal-redirect: Fired before redirecting to portal
578
+ * - portal-error: Fired when an error occurs
579
+ */
580
+ declare class RecurPortalButton extends HTMLElement {
581
+ private _isLoading;
582
+ static get observedAttributes(): string[];
583
+ constructor();
584
+ connectedCallback(): void;
585
+ disconnectedCallback(): void;
586
+ attributeChangedCallback(_name: string, oldValue: string, newValue: string): void;
587
+ private render;
588
+ private getPortalIcon;
589
+ private setupEventListeners;
590
+ private handleClick;
591
+ private redirectToPortal;
592
+ private fetchAndRedirect;
593
+ private dispatchError;
594
+ }
595
+ declare global {
596
+ interface HTMLElementTagNameMap {
597
+ 'recur-portal': RecurPortalButton;
598
+ }
599
+ }
600
+
533
601
  interface RecurProviderProps {
534
602
  children: React.ReactNode;
535
603
  config?: RecurConfig;
@@ -709,7 +777,7 @@ interface UseSubscribeResult {
709
777
  * key={plan.id}
710
778
  * plan={plan}
711
779
  * onSubscribe={() => subscribe({
712
- * planId: plan.id,
780
+ * productId: plan.id, // or productSlug: plan.slug
713
781
  * customerEmail: 'user@example.com',
714
782
  * customerName: 'John Doe'
715
783
  * })}
package/dist/index.js CHANGED
@@ -2226,6 +2226,271 @@ var init_checkout_button = __esm({
2226
2226
  }
2227
2227
  });
2228
2228
 
2229
+ // src/components/portal-button.ts
2230
+ var portal_button_exports = {};
2231
+ __export(portal_button_exports, {
2232
+ RecurPortalButton: () => RecurPortalButton
2233
+ });
2234
+ var RecurPortalButton;
2235
+ var init_portal_button = __esm({
2236
+ "src/components/portal-button.ts"() {
2237
+ RecurPortalButton = class extends HTMLElement {
2238
+ constructor() {
2239
+ super();
2240
+ __publicField(this, "_isLoading", false);
2241
+ __publicField(this, "handleClick", async (e) => {
2242
+ e.preventDefault();
2243
+ if (this._isLoading || this.hasAttribute("disabled")) {
2244
+ return;
2245
+ }
2246
+ const portalUrl = this.getAttribute("portal-url");
2247
+ const apiEndpoint = this.getAttribute("api-endpoint");
2248
+ if (portalUrl) {
2249
+ this.redirectToPortal(portalUrl);
2250
+ return;
2251
+ }
2252
+ if (apiEndpoint) {
2253
+ await this.fetchAndRedirect(apiEndpoint);
2254
+ return;
2255
+ }
2256
+ this.dispatchError("Missing required attribute: either portal-url or api-endpoint must be provided");
2257
+ });
2258
+ this.attachShadow({ mode: "open" });
2259
+ }
2260
+ static get observedAttributes() {
2261
+ return [
2262
+ "portal-url",
2263
+ "api-endpoint",
2264
+ "customer-id",
2265
+ "return-url",
2266
+ "button-text",
2267
+ "button-style",
2268
+ "disabled",
2269
+ "target"
2270
+ ];
2271
+ }
2272
+ connectedCallback() {
2273
+ this.render();
2274
+ this.setupEventListeners();
2275
+ }
2276
+ disconnectedCallback() {
2277
+ const button = this.shadowRoot?.querySelector("button");
2278
+ if (button) {
2279
+ button.removeEventListener("click", this.handleClick);
2280
+ }
2281
+ }
2282
+ attributeChangedCallback(_name, oldValue, newValue) {
2283
+ if (oldValue !== newValue) {
2284
+ this.render();
2285
+ }
2286
+ }
2287
+ render() {
2288
+ const buttonText = this.getAttribute("button-text") || this.textContent?.trim() || "\u7BA1\u7406\u8A02\u95B1";
2289
+ const buttonStyle = this.getAttribute("button-style") || "primary";
2290
+ const isDisabled = this.hasAttribute("disabled") || this._isLoading;
2291
+ this.shadowRoot.innerHTML = `
2292
+ <style>
2293
+ :host {
2294
+ display: inline-block;
2295
+ }
2296
+
2297
+ button {
2298
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
2299
+ font-size: 16px;
2300
+ font-weight: 500;
2301
+ padding: 12px 24px;
2302
+ border-radius: 8px;
2303
+ cursor: pointer;
2304
+ transition: all 0.2s ease;
2305
+ display: inline-flex;
2306
+ align-items: center;
2307
+ justify-content: center;
2308
+ gap: 8px;
2309
+ min-width: 120px;
2310
+ border: none;
2311
+ }
2312
+
2313
+ button:disabled {
2314
+ opacity: 0.6;
2315
+ cursor: not-allowed;
2316
+ }
2317
+
2318
+ /* Primary style (default) */
2319
+ button.primary {
2320
+ background: #18181b;
2321
+ color: #ffffff;
2322
+ }
2323
+
2324
+ button.primary:hover:not(:disabled) {
2325
+ background: #27272a;
2326
+ }
2327
+
2328
+ button.primary:active:not(:disabled) {
2329
+ background: #3f3f46;
2330
+ }
2331
+
2332
+ /* Outline style */
2333
+ button.outline {
2334
+ background: transparent;
2335
+ color: #18181b;
2336
+ border: 2px solid #18181b;
2337
+ }
2338
+
2339
+ button.outline:hover:not(:disabled) {
2340
+ background: #f4f4f5;
2341
+ }
2342
+
2343
+ button.outline:active:not(:disabled) {
2344
+ background: #e4e4e7;
2345
+ }
2346
+
2347
+ /* Gradient style */
2348
+ button.gradient {
2349
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
2350
+ color: #ffffff;
2351
+ }
2352
+
2353
+ button.gradient:hover:not(:disabled) {
2354
+ opacity: 0.9;
2355
+ }
2356
+
2357
+ button.gradient:active:not(:disabled) {
2358
+ opacity: 0.8;
2359
+ }
2360
+
2361
+ /* Link style */
2362
+ button.link {
2363
+ background: transparent;
2364
+ color: #3b82f6;
2365
+ padding: 4px 8px;
2366
+ min-width: auto;
2367
+ text-decoration: underline;
2368
+ }
2369
+
2370
+ button.link:hover:not(:disabled) {
2371
+ color: #2563eb;
2372
+ }
2373
+
2374
+ button.link:active:not(:disabled) {
2375
+ color: #1d4ed8;
2376
+ }
2377
+
2378
+ /* Loading spinner */
2379
+ .spinner {
2380
+ width: 16px;
2381
+ height: 16px;
2382
+ border: 2px solid currentColor;
2383
+ border-top-color: transparent;
2384
+ border-radius: 50%;
2385
+ animation: spin 0.6s linear infinite;
2386
+ }
2387
+
2388
+ @keyframes spin {
2389
+ to { transform: rotate(360deg); }
2390
+ }
2391
+
2392
+ /* Focus state */
2393
+ button:focus-visible {
2394
+ outline: 2px solid #3b82f6;
2395
+ outline-offset: 2px;
2396
+ }
2397
+
2398
+ /* Portal icon */
2399
+ .portal-icon {
2400
+ width: 18px;
2401
+ height: 18px;
2402
+ }
2403
+ </style>
2404
+
2405
+ <button
2406
+ class="${buttonStyle}"
2407
+ ${isDisabled ? "disabled" : ""}
2408
+ aria-busy="${this._isLoading}"
2409
+ >
2410
+ ${this._isLoading ? '<span class="spinner"></span>' : this.getPortalIcon()}
2411
+ <span>${this._isLoading ? "\u8655\u7406\u4E2D..." : buttonText}</span>
2412
+ </button>
2413
+ `;
2414
+ }
2415
+ getPortalIcon() {
2416
+ return `
2417
+ <svg class="portal-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2418
+ <path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/>
2419
+ <circle cx="12" cy="7" r="4"/>
2420
+ </svg>
2421
+ `;
2422
+ }
2423
+ setupEventListeners() {
2424
+ const button = this.shadowRoot?.querySelector("button");
2425
+ if (button) {
2426
+ button.addEventListener("click", this.handleClick);
2427
+ }
2428
+ }
2429
+ redirectToPortal(url) {
2430
+ const target = this.getAttribute("target");
2431
+ this.dispatchEvent(new CustomEvent("portal-redirect", {
2432
+ detail: { url },
2433
+ bubbles: true,
2434
+ composed: true
2435
+ }));
2436
+ if (target === "_blank") {
2437
+ window.open(url, "_blank", "noopener,noreferrer");
2438
+ } else {
2439
+ window.location.href = url;
2440
+ }
2441
+ }
2442
+ async fetchAndRedirect(apiEndpoint) {
2443
+ const customerId = this.getAttribute("customer-id");
2444
+ const returnUrl = this.getAttribute("return-url");
2445
+ this._isLoading = true;
2446
+ this.render();
2447
+ this.setupEventListeners();
2448
+ try {
2449
+ const requestBody = {};
2450
+ if (customerId) requestBody.customerId = customerId;
2451
+ if (returnUrl) requestBody.returnUrl = returnUrl;
2452
+ const response = await fetch(apiEndpoint, {
2453
+ method: "POST",
2454
+ headers: {
2455
+ "Content-Type": "application/json"
2456
+ },
2457
+ body: JSON.stringify(requestBody)
2458
+ });
2459
+ if (!response.ok) {
2460
+ const error = await response.json().catch(() => ({}));
2461
+ throw new Error(error.error?.message || error.message || `HTTP ${response.status}: Failed to create portal session`);
2462
+ }
2463
+ const data = await response.json();
2464
+ const portalUrl = data.url || data.portalUrl;
2465
+ if (!portalUrl) {
2466
+ throw new Error("Portal URL not found in response");
2467
+ }
2468
+ this._isLoading = false;
2469
+ this.render();
2470
+ this.setupEventListeners();
2471
+ this.redirectToPortal(portalUrl);
2472
+ } catch (error) {
2473
+ this._isLoading = false;
2474
+ this.render();
2475
+ this.setupEventListeners();
2476
+ this.dispatchError(error.message || "Failed to create portal session");
2477
+ }
2478
+ }
2479
+ dispatchError(message) {
2480
+ console.error("[recur-portal]", message);
2481
+ this.dispatchEvent(new CustomEvent("portal-error", {
2482
+ detail: { message },
2483
+ bubbles: true,
2484
+ composed: true
2485
+ }));
2486
+ }
2487
+ };
2488
+ if (typeof window !== "undefined" && !customElements.get("recur-portal")) {
2489
+ customElements.define("recur-portal", RecurPortalButton);
2490
+ }
2491
+ }
2492
+ });
2493
+
2229
2494
  // src/components/index.ts
2230
2495
  async function registerComponents() {
2231
2496
  if (typeof window === "undefined") {
@@ -2240,7 +2505,8 @@ async function registerComponents() {
2240
2505
  Promise.resolve().then(() => (init_payment_form_skeleton(), payment_form_skeleton_exports)),
2241
2506
  Promise.resolve().then(() => (init_toast(), toast_exports)),
2242
2507
  Promise.resolve().then(() => (init_payment_form(), payment_form_exports)),
2243
- Promise.resolve().then(() => (init_checkout_button(), checkout_button_exports))
2508
+ Promise.resolve().then(() => (init_checkout_button(), checkout_button_exports)),
2509
+ Promise.resolve().then(() => (init_portal_button(), portal_button_exports))
2244
2510
  ]);
2245
2511
  const components = [
2246
2512
  "recur-loading-spinner",
@@ -2251,7 +2517,8 @@ async function registerComponents() {
2251
2517
  "recur-toast",
2252
2518
  "recur-toast-container",
2253
2519
  "recur-payment-form",
2254
- "recur-checkout"
2520
+ "recur-checkout",
2521
+ "recur-portal"
2255
2522
  ];
2256
2523
  const unregistered = components.filter((name) => !customElements.get(name));
2257
2524
  if (unregistered.length > 0) {
@@ -2326,8 +2593,10 @@ function RecurProvider({ children, config: initialConfig = {} }) {
2326
2593
  containerElementId: config.containerElementId
2327
2594
  });
2328
2595
  setIsCheckingOut(true);
2329
- if (!options.planId) {
2330
- throw new Error("planId is required");
2596
+ const productId = options.productId || options.planId;
2597
+ const productSlug = options.productSlug;
2598
+ if (!productId && !productSlug) {
2599
+ throw new Error("Either productId or productSlug is required");
2331
2600
  }
2332
2601
  if (!config.publishableKey) {
2333
2602
  throw new Error("publishableKey is required");
@@ -2453,15 +2722,17 @@ function RecurProvider({ children, config: initialConfig = {} }) {
2453
2722
  }
2454
2723
  }
2455
2724
  console.log("[Recur SDK] Step 1: Creating checkout session...");
2725
+ const checkoutRequestBody = {
2726
+ customerName: options.customerName,
2727
+ customerEmail: options.customerEmail
2728
+ };
2729
+ if (productId) checkoutRequestBody.productId = productId;
2730
+ if (productSlug) checkoutRequestBody.productSlug = productSlug;
2731
+ if (options.externalCustomerId) checkoutRequestBody.externalCustomerId = options.externalCustomerId;
2456
2732
  const checkoutResponse = await fetch(`${baseUrl}/v1/checkouts`, {
2457
2733
  method: "POST",
2458
2734
  headers,
2459
- body: JSON.stringify({
2460
- productId: options.planId,
2461
- customerName: options.customerName,
2462
- customerEmail: options.customerEmail,
2463
- externalCustomerId: options.externalCustomerId
2464
- })
2735
+ body: JSON.stringify(checkoutRequestBody)
2465
2736
  });
2466
2737
  if (!checkoutResponse.ok) {
2467
2738
  const errorData = await checkoutResponse.json().catch(() => ({}));
package/dist/recur.umd.js CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";var RecurCheckout=(()=>{var C=Object.defineProperty;var ce=Object.getOwnPropertyDescriptor;var le=Object.getOwnPropertyNames;var de=Object.prototype.hasOwnProperty;var ue=(c,e,t)=>e in c?C(c,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):c[e]=t;var b=(c,e)=>()=>(c&&(e=c(c=0)),e);var h=(c,e)=>{for(var t in e)C(c,t,{get:e[t],enumerable:!0})},me=(c,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of le(e))!de.call(c,i)&&i!==t&&C(c,i,{get:()=>e[i],enumerable:!(r=ce(e,i))||r.enumerable});return c};var pe=c=>me(C({},"__esModule",{value:!0}),c);var a=(c,e,t)=>ue(c,typeof e!="symbol"?e+"":e,t);var O={};h(O,{RecurLoadingSpinner:()=>S});var S,F=b(()=>{"use strict";S=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 C=Object.defineProperty;var he=Object.getOwnPropertyDescriptor;var fe=Object.getOwnPropertyNames;var be=Object.prototype.hasOwnProperty;var ge=(c,e,t)=>e in c?C(c,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):c[e]=t;var b=(c,e)=>()=>(c&&(e=c(c=0)),e);var f=(c,e)=>{for(var t in e)C(c,t,{get:e[t],enumerable:!0})},ye=(c,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of fe(e))!be.call(c,i)&&i!==t&&C(c,i,{get:()=>e[i],enumerable:!(r=he(e,i))||r.enumerable});return c};var ve=c=>ye(C({},"__esModule",{value:!0}),c);var a=(c,e,t)=>ge(c,typeof e!="symbol"?e+"":e,t);var Y={};f(Y,{RecurLoadingSpinner:()=>T});var T,V=b(()=>{"use strict";T=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",S)});var j={};h(j,{RecurSuccessMessage:()=>T});var T,Y=b(()=>{"use strict";T=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",T)});var X={};f(X,{RecurSuccessMessage:()=>I});var I,J=b(()=>{"use strict";I=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",T)});var q={};h(q,{RecurErrorDisplay:()=>I});var I,V=b(()=>{"use strict";I=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",I)});var W={};f(W,{RecurErrorDisplay:()=>R});var R,G=b(()=>{"use strict";R=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",I)});var X={};h(X,{RecurSkeletonLoader:()=>R});var R,W=b(()=>{"use strict";R=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",R)});var Z={};f(Z,{RecurSkeletonLoader:()=>P});var P,Q=b(()=>{"use strict";P=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",R)});var J={};h(J,{RecurPaymentFormSkeleton:()=>P});var P,Z=b(()=>{"use strict";P=class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){this.render()}render(){this.shadowRoot.innerHTML=`
380
+ `}};typeof window<"u"&&!customElements.get("recur-skeleton-loader")&&customElements.define("recur-skeleton-loader",P)});var ee={};f(ee,{RecurPaymentFormSkeleton:()=>L});var L,te=b(()=>{"use strict";L=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",P)});var G={};h(G,{RecurToast:()=>M,RecurToastContainer:()=>x});var M,f,x,Q=b(()=>{"use strict";M=class extends HTMLElement{static get observedAttributes(){return["message","type","duration"]}constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){this.render(),this.setupAutoDismiss()}get message(){return this.getAttribute("message")||"Notification"}get type(){let e=this.getAttribute("type");return e==="success"||e==="error"?e:"info"}get duration(){let e=this.getAttribute("duration");return e?parseInt(e,10):5e3}setupAutoDismiss(){let e=this.duration;e>0&&setTimeout(()=>this.dismiss(),e)}dismiss(){this.style.animation="recur-toast-slide-out 0.3s ease-in-out",setTimeout(()=>this.remove(),300)}getTypeIcon(){switch(this.type){case"success":return`<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
647
+ `}};typeof window<"u"&&!customElements.get("recur-payment-form-skeleton")&&customElements.define("recur-payment-form-skeleton",L)});var re={};f(re,{RecurToast:()=>M,RecurToastContainer:()=>x});var M,g,x,ie=b(()=>{"use strict";M=class extends HTMLElement{static get observedAttributes(){return["message","type","duration"]}constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){this.render(),this.setupAutoDismiss()}get message(){return this.getAttribute("message")||"Notification"}get type(){let e=this.getAttribute("type");return e==="success"||e==="error"?e:"info"}get duration(){let e=this.getAttribute("duration");return e?parseInt(e,10):5e3}setupAutoDismiss(){let e=this.duration;e>0&&setTimeout(()=>this.dismiss(),e)}dismiss(){this.style.animation="recur-toast-slide-out 0.3s ease-in-out",setTimeout(()=>this.remove(),300)}getTypeIcon(){switch(this.type){case"success":return`<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
648
648
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
649
649
  </svg>`;case"error":return`<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
650
650
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
@@ -764,7 +764,7 @@
764
764
  </svg>
765
765
  </button>
766
766
  </div>
767
- `,this.shadowRoot.querySelector(".toast-close")?.addEventListener("click",i=>{i.stopPropagation(),this.dismiss()}),this.shadowRoot.querySelector(".toast")?.addEventListener("click",()=>this.dismiss())}static show(e,t="info",r=5e3){let i=x.getInstance(),s=document.createElement("recur-toast");return s.setAttribute("message",e),s.setAttribute("type",t),s.setAttribute("duration",r.toString()),i.appendChild(s),s}},f=class f extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){this.render()}render(){this.shadowRoot.innerHTML=`
767
+ `,this.shadowRoot.querySelector(".toast-close")?.addEventListener("click",i=>{i.stopPropagation(),this.dismiss()}),this.shadowRoot.querySelector(".toast")?.addEventListener("click",()=>this.dismiss())}static show(e,t="info",r=5e3){let i=x.getInstance(),s=document.createElement("recur-toast");return s.setAttribute("message",e),s.setAttribute("type",t),s.setAttribute("duration",r.toString()),i.appendChild(s),s}},g=class g 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 f.instance||(f.instance=document.querySelector("recur-toast-container"),f.instance||(f.instance=document.createElement("recur-toast-container"),document.body.appendChild(f.instance))),f.instance}};a(f,"instance",null);x=f;typeof window<"u"&&!customElements.get("recur-toast")&&customElements.define("recur-toast",M);typeof window<"u"&&!customElements.get("recur-toast-container")&&customElements.define("recur-toast-container",x)});var ee={};h(ee,{RecurPaymentForm:()=>L});var L,te=b(()=>{"use strict";L=class extends HTMLElement{constructor(){super();a(this,"containerId");a(this,"customStyles");a(this,"_isInitializing",!1);a(this,"_initializationAborted",!1);this.containerId=this.getAttribute("container-id")||`recur-${Date.now()}`,this.customStyles=this.getAttribute("custom-styles")||"",this.attachShadow({mode:"open"})}connectedCallback(){this.render()}disconnectedCallback(){console.log("[PaymentForm] Component disconnected, cleaning up..."),this._initializationAborted=!0,this._paymentSession=null;let t=document.getElementById(`${this.containerId}-submit-btn`);if(t){let r=t.cloneNode(!0);t.parentNode?.replaceChild(r,t)}}static get observedAttributes(){return["custom-styles","customer-name","customer-email","plan-name","amount","billing-period"]}attributeChangedCallback(t,r,i){t==="custom-styles"&&r!==i?(this.customStyles=i||"",this.updateCustomStyles()):r!==i&&this.updateCustomerInfoSection()}render(){this.shadowRoot.innerHTML=`
794
+ `}static getInstance(){return g.instance||(g.instance=document.querySelector("recur-toast-container"),g.instance||(g.instance=document.createElement("recur-toast-container"),document.body.appendChild(g.instance))),g.instance}};a(g,"instance",null);x=g;typeof window<"u"&&!customElements.get("recur-toast")&&customElements.define("recur-toast",M);typeof window<"u"&&!customElements.get("recur-toast-container")&&customElements.define("recur-toast-container",x)});var se={};f(se,{RecurPaymentForm:()=>U});var U,oe=b(()=>{"use strict";U=class extends HTMLElement{constructor(){super();a(this,"containerId");a(this,"customStyles");a(this,"_isInitializing",!1);a(this,"_initializationAborted",!1);this.containerId=this.getAttribute("container-id")||`recur-${Date.now()}`,this.customStyles=this.getAttribute("custom-styles")||"",this.attachShadow({mode:"open"})}connectedCallback(){this.render()}disconnectedCallback(){console.log("[PaymentForm] Component disconnected, cleaning up..."),this._initializationAborted=!0,this._paymentSession=null;let t=document.getElementById(`${this.containerId}-submit-btn`);if(t){let r=t.cloneNode(!0);t.parentNode?.replaceChild(r,t)}}static get observedAttributes(){return["custom-styles","customer-name","customer-email","plan-name","amount","billing-period"]}attributeChangedCallback(t,r,i){t==="custom-styles"&&r!==i?(this.customStyles=i||"",this.updateCustomStyles()):r!==i&&this.updateCustomerInfoSection()}render(){this.shadowRoot.innerHTML=`
795
795
  <style>
796
796
  /* \u9019\u4E9B\u6A23\u5F0F\u88AB\u9694\u96E2\u5728 Shadow DOM \u5167\uFF0C\u4E0D\u6703\u5F71\u97FF\u5916\u90E8 */
797
797
  :host {
@@ -1217,10 +1217,10 @@
1217
1217
  >
1218
1218
  <span id="${this.containerId}-submit-text">\u78BA\u8A8D\u4ED8\u6B3E</span>
1219
1219
  </button>
1220
- `,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 i=document.getElementById(`${this.containerId}-card-no`),s=document.getElementById(`${this.containerId}-card-exp`),n=document.getElementById(`${this.containerId}-card-cvc`);if(!i||!s||!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 l=d.status&&d.status.CardNo===!0&&d.status.CardExp===!0&&d.status.CardCvc===!0,v=document.getElementById(`${this.containerId}-submit-btn`);v&&(v.disabled=!l,console.log("[PaymentForm] Submit button disabled:",!l))}),this._initializationAborted?(console.log("[PaymentForm] Initialization aborted before saving session"),this._isInitializing=!1,null):(this._paymentSession=o,this.setupFormSubmission(),this._isInitializing=!1,o))}catch(i){throw this._isInitializing=!1,console.error("[PaymentForm] Failed to initialize payment:",i),i}}setupFormSubmission(){let t=document.getElementById(`${this.containerId}-submit-btn`);t&&t.addEventListener("click",async r=>{if(r.preventDefault(),t.classList.contains("loading"))return;let i,s,n=document.getElementById(`${this.containerId}-email`),o=document.getElementById(`${this.containerId}-name`);if(n&&o){if(i=n.value,s=o.value,!i||!s){this.showError("\u8ACB\u586B\u5BEB\u59D3\u540D\u548C\u96FB\u5B50\u90F5\u4EF6");return}}else if(i=this.getAttribute("customer-email")||void 0,s=this.getAttribute("customer-name")||void 0,!i||!s){this.showError("\u5BA2\u6236\u8CC7\u8A0A\u4E0D\u5B8C\u6574");return}this.clearError(),this.setButtonLoading(!0),this.dispatchEvent(new CustomEvent("submit",{detail:{customerEmail:i,customerName:s,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=`
1220
+ `,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 i=document.getElementById(`${this.containerId}-card-no`),s=document.getElementById(`${this.containerId}-card-exp`),n=document.getElementById(`${this.containerId}-card-cvc`);if(!i||!s||!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(l){if((l?.message?.includes("1008")||l?.message?.includes("timeout")||l?.code===1008)&&this._initializationAborted)return console.log("[PaymentForm] Caught 1008 timeout error after component disconnect - ignoring"),this._isInitializing=!1,null;throw l}return this._initializationAborted?(console.log("[PaymentForm] Initialization aborted after paymentSession.start()"),this._isInitializing=!1,null):(this.hideCardSkeletons(),o.onUpdate?.(l=>{if(this._initializationAborted){console.log("[PaymentForm] Ignoring onUpdate event - component disconnected");return}console.log("[PaymentForm] PAYUNi update:",l);let u=l.status&&l.status.CardNo===!0&&l.status.CardExp===!0&&l.status.CardCvc===!0,y=document.getElementById(`${this.containerId}-submit-btn`);y&&(y.disabled=!u,console.log("[PaymentForm] Submit button disabled:",!u))}),this._initializationAborted?(console.log("[PaymentForm] Initialization aborted before saving session"),this._isInitializing=!1,null):(this._paymentSession=o,this.setupFormSubmission(),this._isInitializing=!1,o))}catch(i){throw this._isInitializing=!1,console.error("[PaymentForm] Failed to initialize payment:",i),i}}setupFormSubmission(){let t=document.getElementById(`${this.containerId}-submit-btn`);t&&t.addEventListener("click",async r=>{if(r.preventDefault(),t.classList.contains("loading"))return;let i,s,n=document.getElementById(`${this.containerId}-email`),o=document.getElementById(`${this.containerId}-name`);if(n&&o){if(i=n.value,s=o.value,!i||!s){this.showError("\u8ACB\u586B\u5BEB\u59D3\u540D\u548C\u96FB\u5B50\u90F5\u4EF6");return}}else if(i=this.getAttribute("customer-email")||void 0,s=this.getAttribute("customer-name")||void 0,!i||!s){this.showError("\u5BA2\u6236\u8CC7\u8A0A\u4E0D\u5B8C\u6574");return}this.clearError(),this.setButtonLoading(!0),this.dispatchEvent(new CustomEvent("submit",{detail:{customerEmail:i,customerName:s,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=`
1221
1221
  <span class="recur-loading-spinner"></span>
1222
1222
  <span>\u8655\u7406\u4E2D...</span>
1223
- `):(r.classList.remove("loading"),r.disabled=!1,r.innerHTML='<span id="'+this.containerId+'-submit-text">\u78BA\u8A8D\u4ED8\u6B3E</span>'))}resetButton(){this.setButtonLoading(!1)}showError(t){let r=this.querySelector(".recur-sdk__error-container");r||(r=document.createElement("div"),r.className="recur-sdk__error-container",r.style.cssText="margin-bottom: 16px;",this.insertBefore(r,this.firstChild));let i=document.createElement("recur-error-display");i.setAttribute("error",t),i.setAttribute("dismissible","true"),r.innerHTML="",r.appendChild(i),i.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",L)});var re={};h(re,{RecurCheckoutButton:()=>U});var U,ie=b(()=>{"use strict";U=class extends HTMLElement{constructor(){super();a(this,"_isLoading",!1);a(this,"handleClick",async t=>{if(t.preventDefault(),this._isLoading||this.hasAttribute("disabled"))return;let r=this.getAttribute("publishable-key"),i=this.getAttribute("product-id"),s=this.getAttribute("success-url"),n=this.getAttribute("cancel-url");if(!r){this.dispatchError("Missing required attribute: publishable-key");return}if(!i){this.dispatchError("Missing required attribute: product-id");return}if(!s){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:i,successUrl:this.resolveUrl(s),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,i){r!==i&&this.render()}render(){let t=this.getAttribute("button-text")||this.textContent?.trim()||"\u8A02\u95B1",r=this.getAttribute("button-style")||"primary",i=this.hasAttribute("disabled")||this._isLoading;this.shadowRoot.innerHTML=`
1223
+ `):(r.classList.remove("loading"),r.disabled=!1,r.innerHTML='<span id="'+this.containerId+'-submit-text">\u78BA\u8A8D\u4ED8\u6B3E</span>'))}resetButton(){this.setButtonLoading(!1)}showError(t){let r=this.querySelector(".recur-sdk__error-container");r||(r=document.createElement("div"),r.className="recur-sdk__error-container",r.style.cssText="margin-bottom: 16px;",this.insertBefore(r,this.firstChild));let i=document.createElement("recur-error-display");i.setAttribute("error",t),i.setAttribute("dismissible","true"),r.innerHTML="",r.appendChild(i),i.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",U)});var ne={};f(ne,{RecurCheckoutButton:()=>_});var _,ae=b(()=>{"use strict";_=class extends HTMLElement{constructor(){super();a(this,"_isLoading",!1);a(this,"handleClick",async t=>{if(t.preventDefault(),this._isLoading||this.hasAttribute("disabled"))return;let r=this.getAttribute("publishable-key"),i=this.getAttribute("product-id"),s=this.getAttribute("success-url"),n=this.getAttribute("cancel-url");if(!r){this.dispatchError("Missing required attribute: publishable-key");return}if(!i){this.dispatchError("Missing required attribute: product-id");return}if(!s){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:i,successUrl:this.resolveUrl(s),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,i){r!==i&&this.render()}render(){let t=this.getAttribute("button-text")||this.textContent?.trim()||"\u8A02\u95B1",r=this.getAttribute("button-style")||"primary",i=this.hasAttribute("disabled")||this._isLoading;this.shadowRoot.innerHTML=`
1224
1224
  <style>
1225
1225
  :host {
1226
1226
  display: inline-block;
@@ -1319,7 +1319,134 @@
1319
1319
  ${this._isLoading?'<span class="spinner"></span>':""}
1320
1320
  <span>${this._isLoading?"\u8655\u7406\u4E2D...":t}</span>
1321
1321
  </button>
1322
- `}setupEventListeners(){let t=this.shadowRoot?.querySelector("button");t&&t.addEventListener("click",this.handleClick)}async createCheckoutSession(t){let r=this.getApiBaseUrl(),i={productId:t.productId,successUrl:t.successUrl,cancelUrl:t.cancelUrl};t.mode&&(i.mode=t.mode),t.customerEmail&&(i.customerEmail=t.customerEmail);let s=await fetch(`${r}/v1/checkout/sessions`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":t.publishableKey},body:JSON.stringify(i)});if(!s.ok){let n=await s.json().catch(()=>({}));throw new Error(n.error?.message||`HTTP ${s.status}: Failed to create checkout session`)}return s.json()}getApiBaseUrl(){let t=this.getAttribute("api-base-url");if(t)return t;if(typeof window<"u"){let r=window.location.hostname;if(r==="localhost"||r.includes(".test")||r.includes(".local")||r==="127.0.0.1")return`${window.location.protocol}//${window.location.host}/api`}return"https://api.recur.tw"}resolveUrl(t){return t.startsWith("/")?`${window.location.origin}${t}`:t}dispatchError(t){console.error("[recur-checkout]",t),this.dispatchEvent(new CustomEvent("checkout-error",{detail:{message:t},bubbles:!0,composed:!0}))}};typeof window<"u"&&!customElements.get("recur-checkout")&&customElements.define("recur-checkout",U)});var ye={};h(ye,{RecurCheckout:()=>E,RecurElements:()=>y,createElements:()=>N,default:()=>ge,init:()=>ne});async function he(){if(typeof window>"u"){console.warn("[Recur SDK] Cannot register Web Components: window is not defined");return}await Promise.all([Promise.resolve().then(()=>(F(),O)),Promise.resolve().then(()=>(Y(),j)),Promise.resolve().then(()=>(V(),q)),Promise.resolve().then(()=>(W(),X)),Promise.resolve().then(()=>(Z(),J)),Promise.resolve().then(()=>(Q(),G)),Promise.resolve().then(()=>(te(),ee)),Promise.resolve().then(()=>(ie(),re))]);let e=["recur-loading-spinner","recur-success-message","recur-error-display","recur-skeleton-loader","recur-payment-form-skeleton","recur-toast","recur-toast-container","recur-payment-form","recur-checkout"].filter(t=>!customElements.get(t));e.length>0&&console.warn("[Recur SDK] The following components are not registered:",e)}typeof window<"u"&&he();var D=class{constructor(e){a(this,"config");if(!e.publishableKey)throw new Error("publishableKey is required");this.config={baseUrl:"https://api.recur.tw",...e}}async createSubscription(e){let{planId:t,customerName:r,customerEmail:i}=e;if(!t)throw new Error("planId is required");let s=await fetch(`${this.config.baseUrl}/v1/subscriptions`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":this.config.publishableKey},body:JSON.stringify({planId:t,customerName:r,customerEmail:i})});if(!s.ok){let n=await s.json().catch(()=>({}));throw{code:n.error||"CHECKOUT_FAILED",message:n.message||"Failed to initiate checkout",details:n}}return await s.json()}async fetchProducts(e){let t=new URL(`${this.config.baseUrl}/v1/products`);e?.type&&t.searchParams.set("type",e.type);let r=await fetch(t.toString(),{method:"GET",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":this.config.publishableKey}});if(!r.ok){let i=await r.json().catch(()=>({}));throw{code:i.error||"FETCH_PRODUCTS_FAILED",message:i.message||"Failed to fetch products",details:i}}return await r.json()}async fetchPlans(){return{plans:(await this.fetchProducts({type:"SUBSCRIPTION"})).products}}getConfig(){return{...this.config}}};var _=class{constructor(e,t){a(this,"config");a(this,"options");a(this,"container");a(this,"checkoutId",null);a(this,"sdkToken",null);a(this,"sdkEnv","S");a(this,"payuniSDK",null);a(this,"isFormValid",!1);if(this.config=e,this.options=t,typeof t.container=="string"){let r=document.querySelector(t.container);if(!r)throw new Error(`Container not found: ${t.container}`);this.container=r}else this.container=t.container}async render(){try{await this.initCheckout(),this.renderHTML(),await this.loadPayUniSDK(),await this.initPayUniSDK(),this.setupFormSubmit()}catch(e){this.handleError(e)}}getBaseUrl(){if(this.config.baseUrl)return this.config.baseUrl;if(typeof window<"u"){let e=window.location.hostname;if(e==="localhost"||e.includes(".test")||e.includes(".local")||e==="127.0.0.1")return`${window.location.protocol}//${window.location.host}/api`}return"https://api.recur.tw"}async initCheckout(){let e=this.getBaseUrl(),t=await fetch(`${e}/v1/checkouts`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":this.config.publishableKey},body:JSON.stringify({productId:this.options.planId,customerEmail:this.options.customerEmail,customerName:this.options.customerName})});if(!t.ok){let i=await t.json().catch(()=>({}));throw new Error(i.error||"Failed to initialize checkout")}let r=await t.json();this.checkoutId=r.checkout.id,this.sdkToken=r.sdkToken,this.sdkEnv=this.config.publishableKey.startsWith("pk_test_")?"S":"P"}renderHTML(){this.container.innerHTML=`
1322
+ `}setupEventListeners(){let t=this.shadowRoot?.querySelector("button");t&&t.addEventListener("click",this.handleClick)}async createCheckoutSession(t){let r=this.getApiBaseUrl(),i={productId:t.productId,successUrl:t.successUrl,cancelUrl:t.cancelUrl};t.mode&&(i.mode=t.mode),t.customerEmail&&(i.customerEmail=t.customerEmail);let s=await fetch(`${r}/v1/checkout/sessions`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":t.publishableKey},body:JSON.stringify(i)});if(!s.ok){let n=await s.json().catch(()=>({}));throw new Error(n.error?.message||`HTTP ${s.status}: Failed to create checkout session`)}return s.json()}getApiBaseUrl(){let t=this.getAttribute("api-base-url");if(t)return t;if(typeof window<"u"){let r=window.location.hostname;if(r==="localhost"||r.includes(".test")||r.includes(".local")||r==="127.0.0.1")return`${window.location.protocol}//${window.location.host}/api`}return"https://api.recur.tw"}resolveUrl(t){return t.startsWith("/")?`${window.location.origin}${t}`:t}dispatchError(t){console.error("[recur-checkout]",t),this.dispatchEvent(new CustomEvent("checkout-error",{detail:{message:t},bubbles:!0,composed:!0}))}};typeof window<"u"&&!customElements.get("recur-checkout")&&customElements.define("recur-checkout",_)});var ce={};f(ce,{RecurPortalButton:()=>A});var A,le=b(()=>{"use strict";A=class extends HTMLElement{constructor(){super();a(this,"_isLoading",!1);a(this,"handleClick",async t=>{if(t.preventDefault(),this._isLoading||this.hasAttribute("disabled"))return;let r=this.getAttribute("portal-url"),i=this.getAttribute("api-endpoint");if(r){this.redirectToPortal(r);return}if(i){await this.fetchAndRedirect(i);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,i){r!==i&&this.render()}render(){let t=this.getAttribute("button-text")||this.textContent?.trim()||"\u7BA1\u7406\u8A02\u95B1",r=this.getAttribute("button-style")||"primary",i=this.hasAttribute("disabled")||this._isLoading;this.shadowRoot.innerHTML=`
1323
+ <style>
1324
+ :host {
1325
+ display: inline-block;
1326
+ }
1327
+
1328
+ button {
1329
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1330
+ font-size: 16px;
1331
+ font-weight: 500;
1332
+ padding: 12px 24px;
1333
+ border-radius: 8px;
1334
+ cursor: pointer;
1335
+ transition: all 0.2s ease;
1336
+ display: inline-flex;
1337
+ align-items: center;
1338
+ justify-content: center;
1339
+ gap: 8px;
1340
+ min-width: 120px;
1341
+ border: none;
1342
+ }
1343
+
1344
+ button:disabled {
1345
+ opacity: 0.6;
1346
+ cursor: not-allowed;
1347
+ }
1348
+
1349
+ /* Primary style (default) */
1350
+ button.primary {
1351
+ background: #18181b;
1352
+ color: #ffffff;
1353
+ }
1354
+
1355
+ button.primary:hover:not(:disabled) {
1356
+ background: #27272a;
1357
+ }
1358
+
1359
+ button.primary:active:not(:disabled) {
1360
+ background: #3f3f46;
1361
+ }
1362
+
1363
+ /* Outline style */
1364
+ button.outline {
1365
+ background: transparent;
1366
+ color: #18181b;
1367
+ border: 2px solid #18181b;
1368
+ }
1369
+
1370
+ button.outline:hover:not(:disabled) {
1371
+ background: #f4f4f5;
1372
+ }
1373
+
1374
+ button.outline:active:not(:disabled) {
1375
+ background: #e4e4e7;
1376
+ }
1377
+
1378
+ /* Gradient style */
1379
+ button.gradient {
1380
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1381
+ color: #ffffff;
1382
+ }
1383
+
1384
+ button.gradient:hover:not(:disabled) {
1385
+ opacity: 0.9;
1386
+ }
1387
+
1388
+ button.gradient:active:not(:disabled) {
1389
+ opacity: 0.8;
1390
+ }
1391
+
1392
+ /* Link style */
1393
+ button.link {
1394
+ background: transparent;
1395
+ color: #3b82f6;
1396
+ padding: 4px 8px;
1397
+ min-width: auto;
1398
+ text-decoration: underline;
1399
+ }
1400
+
1401
+ button.link:hover:not(:disabled) {
1402
+ color: #2563eb;
1403
+ }
1404
+
1405
+ button.link:active:not(:disabled) {
1406
+ color: #1d4ed8;
1407
+ }
1408
+
1409
+ /* Loading spinner */
1410
+ .spinner {
1411
+ width: 16px;
1412
+ height: 16px;
1413
+ border: 2px solid currentColor;
1414
+ border-top-color: transparent;
1415
+ border-radius: 50%;
1416
+ animation: spin 0.6s linear infinite;
1417
+ }
1418
+
1419
+ @keyframes spin {
1420
+ to { transform: rotate(360deg); }
1421
+ }
1422
+
1423
+ /* Focus state */
1424
+ button:focus-visible {
1425
+ outline: 2px solid #3b82f6;
1426
+ outline-offset: 2px;
1427
+ }
1428
+
1429
+ /* Portal icon */
1430
+ .portal-icon {
1431
+ width: 18px;
1432
+ height: 18px;
1433
+ }
1434
+ </style>
1435
+
1436
+ <button
1437
+ class="${r}"
1438
+ ${i?"disabled":""}
1439
+ aria-busy="${this._isLoading}"
1440
+ >
1441
+ ${this._isLoading?'<span class="spinner"></span>':this.getPortalIcon()}
1442
+ <span>${this._isLoading?"\u8655\u7406\u4E2D...":t}</span>
1443
+ </button>
1444
+ `}getPortalIcon(){return`
1445
+ <svg class="portal-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1446
+ <path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/>
1447
+ <circle cx="12" cy="7" r="4"/>
1448
+ </svg>
1449
+ `}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"),i=this.getAttribute("return-url");this._isLoading=!0,this.render(),this.setupEventListeners();try{let s={};r&&(s.customerId=r),i&&(s.returnUrl=i);let n=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});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 o=await n.json(),l=o.url||o.portalUrl;if(!l)throw new Error("Portal URL not found in response");this._isLoading=!1,this.render(),this.setupEventListeners(),this.redirectToPortal(l)}catch(s){this._isLoading=!1,this.render(),this.setupEventListeners(),this.dispatchError(s.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",A)});var Se={};f(Se,{RecurCheckout:()=>S,RecurElements:()=>k,createElements:()=>K,default:()=>Ee,init:()=>me});async function ke(){if(typeof window>"u"){console.warn("[Recur SDK] Cannot register Web Components: window is not defined");return}await Promise.all([Promise.resolve().then(()=>(V(),Y)),Promise.resolve().then(()=>(J(),X)),Promise.resolve().then(()=>(G(),W)),Promise.resolve().then(()=>(Q(),Z)),Promise.resolve().then(()=>(te(),ee)),Promise.resolve().then(()=>(ie(),re)),Promise.resolve().then(()=>(oe(),se)),Promise.resolve().then(()=>(ae(),ne)),Promise.resolve().then(()=>(le(),ce))]);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"&&ke();var D=class{constructor(e){a(this,"config");if(!e.publishableKey)throw new Error("publishableKey is required");this.config={baseUrl:"https://api.recur.tw",...e}}async createSubscription(e){let{customerName:t,customerEmail:r}=e,i=e.productId||e.planId,s=e.productSlug;if(!i&&!s)throw new Error("Either productId or productSlug is required");let n={customerName:t,customerEmail:r};i&&(n.productId=i),s&&(n.productSlug=s);let o=await fetch(`${this.config.baseUrl}/v1/subscriptions`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":this.config.publishableKey},body:JSON.stringify(n)});if(!o.ok){let l=await o.json().catch(()=>({}));throw{code:l.error||"CHECKOUT_FAILED",message:l.message||"Failed to initiate checkout",details:l}}return await o.json()}async fetchProducts(e){let t=new URL(`${this.config.baseUrl}/v1/products`);e?.type&&t.searchParams.set("type",e.type);let r=await fetch(t.toString(),{method:"GET",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":this.config.publishableKey}});if(!r.ok){let i=await r.json().catch(()=>({}));throw{code:i.error||"FETCH_PRODUCTS_FAILED",message:i.message||"Failed to fetch products",details:i}}return await r.json()}async fetchPlans(){return{plans:(await this.fetchProducts({type:"SUBSCRIPTION"})).products}}getConfig(){return{...this.config}}};var z=class{constructor(e,t){a(this,"config");a(this,"options");a(this,"container");a(this,"checkoutId",null);a(this,"sdkToken",null);a(this,"sdkEnv","S");a(this,"payuniSDK",null);a(this,"isFormValid",!1);if(this.config=e,this.options=t,typeof t.container=="string"){let r=document.querySelector(t.container);if(!r)throw new Error(`Container not found: ${t.container}`);this.container=r}else this.container=t.container}async render(){try{await this.initCheckout(),this.renderHTML(),await this.loadPayUniSDK(),await this.initPayUniSDK(),this.setupFormSubmit()}catch(e){this.handleError(e)}}getBaseUrl(){if(this.config.baseUrl)return this.config.baseUrl;if(typeof window<"u"){let e=window.location.hostname;if(e==="localhost"||e.includes(".test")||e.includes(".local")||e==="127.0.0.1")return`${window.location.protocol}//${window.location.host}/api`}return"https://api.recur.tw"}async initCheckout(){let e=this.getBaseUrl(),t=await fetch(`${e}/v1/checkouts`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":this.config.publishableKey},body:JSON.stringify({productId:this.options.planId,customerEmail:this.options.customerEmail,customerName:this.options.customerName})});if(!t.ok){let i=await t.json().catch(()=>({}));throw new Error(i.error||"Failed to initialize checkout")}let r=await t.json();this.checkoutId=r.checkout.id,this.sdkToken=r.sdkToken,this.sdkEnv=this.config.publishableKey.startsWith("pk_test_")?"S":"P"}renderHTML(){this.container.innerHTML=`
1323
1450
  <div class="recur-embedded-checkout" style="max-width: 400px; margin: 0 auto; padding: 20px; font-family: system-ui, -apple-system, sans-serif;">
1324
1451
  <div class="recur-checkout-header" style="margin-bottom: 24px;">
1325
1452
  <h2 style="margin: 0 0 8px 0; font-size: 20px; font-weight: 600; color: #111;">Subscribe</h2>
@@ -1389,21 +1516,21 @@
1389
1516
  </p>
1390
1517
  </form>
1391
1518
  </div>
1392
- `}async loadPayUniSDK(){return new Promise((e,t)=>{if(window.UniPayment){e();return}let r=document.createElement("script");r.src=this.sdkEnv==="P"?"https://vendor.payuni.com.tw/sdk/uni-payment.js":"https://sandbox-vendor.payuni.com.tw/sdk/uni-payment.js",r.async=!0,r.onload=()=>e(),r.onerror=()=>t(new Error("Failed to load PAYUNi SDK")),document.head.appendChild(r)})}async initPayUniSDK(){if(!this.sdkToken)throw new Error("SDK token not initialized");let e=window.UniPayment;if(!e)throw new Error("PAYUNi SDK not loaded");let t={env:this.sdkEnv,useInst:!1,elements:{CardNo:"recur-card-number",CardExp:"recur-card-exp",CardCvc:"recur-card-cvc"},style:{color:"#111827",errorColor:"#dc2626",fontSize:"14px",fontWeight:"400",lineHeight:"24px"}};this.payuniSDK=e.createSession(this.sdkToken,t),await this.payuniSDK.start(),this.payuniSDK.onUpdate(r=>{let i=r?.status;i&&(this.isFormValid=i.CardNo===!0&&i.CardExp===!0&&i.CardCvc===!0,this.updateSubmitButton())})}updateSubmitButton(){let e=document.getElementById("recur-submit-btn");e&&(e.disabled=!this.isFormValid,e.style.opacity=this.isFormValid?"1":"0.5",e.style.cursor=this.isFormValid?"pointer":"not-allowed")}setupFormSubmit(){let e=document.getElementById("recur-checkout-form");e&&e.addEventListener("submit",async t=>{t.preventDefault(),await this.handleSubmit()})}async handleSubmit(){try{this.showLoading(!0),this.hideError();let e=document.getElementById("recur-email").value,t=document.getElementById("recur-name").value;if(!e||!t)throw new Error("Please fill in all required fields");if(!this.checkoutId)throw new Error("Checkout session not initialized");if(!this.payuniSDK)throw new Error("Payment system not initialized");let r=await this.payuniSDK.getTradeResult();if(r.Status!=="SUCCESS")throw new Error(r.Message||"Card validation failed");let i=r.EncryptInfo||r.creditToken;if(!i)throw console.error("[Recur SDK] Missing payment token. Available fields:",Object.keys(r)),new Error("Missing payment token");let s=this.getBaseUrl(),n=await fetch(`${s}/v1/checkouts/${this.checkoutId}/pay`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":this.config.publishableKey},body:JSON.stringify({creditToken:i,timestamp:r.HashTimestamp||r.timestamp})});if(!n.ok){let l=await n.json().catch(()=>({}));throw new Error(l.error||"Failed to process payment")}let o=await n.json(),d={subscription:{id:o.subscription?.id||o.charge?.id||"",status:o.success?"active":"failed",planId:this.options.planId,planName:"",amount:o.charge?.amount||0,billingPeriod:o.subscription?.billingPeriod||"MONTHLY",trialDays:null},subscriber:{id:"",email:document.getElementById("recur-email")?.value||"",name:document.getElementById("recur-name")?.value||""},nextSteps:{getSdkToken:"",completeSubscription:""}};this.options.onSuccess&&this.options.onSuccess(d),this.showSuccess()}catch(e){this.handleError(e)}finally{this.showLoading(!1)}}showLoading(e){let t=document.getElementById("recur-submit-btn");t&&(t.disabled=e,t.textContent=e?"Processing...":"Subscribe Now")}showError(e){let t=document.getElementById("recur-error");t&&(t.textContent=e,t.style.display="block")}hideError(){let e=document.getElementById("recur-error");e&&(e.style.display="none")}showSuccess(){this.container.innerHTML="";let e=document.createElement("recur-success-message");e.setAttribute("title","Subscription Complete!"),e.setAttribute("message","Thank you for subscribing. You will receive a confirmation email shortly."),this.container.appendChild(e)}handleError(e){let t=e?.message||"An error occurred";this.showError(t);let r={code:"CHECKOUT_ERROR",message:t};this.options.onError&&this.options.onError(r)}};var y=class{constructor(e){a(this,"publishableKey");a(this,"baseUrl");a(this,"embedUrl");a(this,"iframe",null);a(this,"container",null);a(this,"sessionId",null);a(this,"timestamp",null);a(this,"creditToken",null);a(this,"cardToken",null);a(this,"cardTimestamp",null);a(this,"eventHandlers",new Map);typeof e=="string"?(this.publishableKey=e,this.baseUrl=this.getDefaultBaseUrl(),this.embedUrl=this.getDefaultEmbedUrl()):(this.publishableKey=e.publishableKey,this.baseUrl=e.baseUrl||this.getDefaultBaseUrl(),this.embedUrl=e.embedUrl||this.getDefaultEmbedUrl()),window.addEventListener("message",this.handleMessage.bind(this))}async mount(e){let t=typeof e=="string"?document.querySelector(e):e;if(!t)throw new Error(`Container not found: ${e}`);return this.container=t,this.iframe=document.createElement("iframe"),this.iframe.src=`${this.embedUrl}/elements?key=${encodeURIComponent(this.publishableKey)}`,this.iframe.style.cssText=`
1519
+ `}async loadPayUniSDK(){return new Promise((e,t)=>{if(window.UniPayment){e();return}let r=document.createElement("script");r.src=this.sdkEnv==="P"?"https://vendor.payuni.com.tw/sdk/uni-payment.js":"https://sandbox-vendor.payuni.com.tw/sdk/uni-payment.js",r.async=!0,r.onload=()=>e(),r.onerror=()=>t(new Error("Failed to load PAYUNi SDK")),document.head.appendChild(r)})}async initPayUniSDK(){if(!this.sdkToken)throw new Error("SDK token not initialized");let e=window.UniPayment;if(!e)throw new Error("PAYUNi SDK not loaded");let t={env:this.sdkEnv,useInst:!1,elements:{CardNo:"recur-card-number",CardExp:"recur-card-exp",CardCvc:"recur-card-cvc"},style:{color:"#111827",errorColor:"#dc2626",fontSize:"14px",fontWeight:"400",lineHeight:"24px"}};this.payuniSDK=e.createSession(this.sdkToken,t),await this.payuniSDK.start(),this.payuniSDK.onUpdate(r=>{let i=r?.status;i&&(this.isFormValid=i.CardNo===!0&&i.CardExp===!0&&i.CardCvc===!0,this.updateSubmitButton())})}updateSubmitButton(){let e=document.getElementById("recur-submit-btn");e&&(e.disabled=!this.isFormValid,e.style.opacity=this.isFormValid?"1":"0.5",e.style.cursor=this.isFormValid?"pointer":"not-allowed")}setupFormSubmit(){let e=document.getElementById("recur-checkout-form");e&&e.addEventListener("submit",async t=>{t.preventDefault(),await this.handleSubmit()})}async handleSubmit(){try{this.showLoading(!0),this.hideError();let e=document.getElementById("recur-email").value,t=document.getElementById("recur-name").value;if(!e||!t)throw new Error("Please fill in all required fields");if(!this.checkoutId)throw new Error("Checkout session not initialized");if(!this.payuniSDK)throw new Error("Payment system not initialized");let r=await this.payuniSDK.getTradeResult();if(r.Status!=="SUCCESS")throw new Error(r.Message||"Card validation failed");let i=r.EncryptInfo||r.creditToken;if(!i)throw console.error("[Recur SDK] Missing payment token. Available fields:",Object.keys(r)),new Error("Missing payment token");let s=this.getBaseUrl(),n=await fetch(`${s}/v1/checkouts/${this.checkoutId}/pay`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":this.config.publishableKey},body:JSON.stringify({creditToken:i,timestamp:r.HashTimestamp||r.timestamp})});if(!n.ok){let u=await n.json().catch(()=>({}));throw new Error(u.error||"Failed to process payment")}let o=await n.json(),l={subscription:{id:o.subscription?.id||o.charge?.id||"",status:o.success?"active":"failed",planId:this.options.planId,planName:"",amount:o.charge?.amount||0,billingPeriod:o.subscription?.billingPeriod||"MONTHLY",trialDays:null},subscriber:{id:"",email:document.getElementById("recur-email")?.value||"",name:document.getElementById("recur-name")?.value||""},nextSteps:{getSdkToken:"",completeSubscription:""}};this.options.onSuccess&&this.options.onSuccess(l),this.showSuccess()}catch(e){this.handleError(e)}finally{this.showLoading(!1)}}showLoading(e){let t=document.getElementById("recur-submit-btn");t&&(t.disabled=e,t.textContent=e?"Processing...":"Subscribe Now")}showError(e){let t=document.getElementById("recur-error");t&&(t.textContent=e,t.style.display="block")}hideError(){let e=document.getElementById("recur-error");e&&(e.style.display="none")}showSuccess(){this.container.innerHTML="";let e=document.createElement("recur-success-message");e.setAttribute("title","Subscription Complete!"),e.setAttribute("message","Thank you for subscribing. You will receive a confirmation email shortly."),this.container.appendChild(e)}handleError(e){let t=e?.message||"An error occurred";this.showError(t);let r={code:"CHECKOUT_ERROR",message:t};this.options.onError&&this.options.onError(r)}};var k=class{constructor(e){a(this,"publishableKey");a(this,"baseUrl");a(this,"embedUrl");a(this,"iframe",null);a(this,"container",null);a(this,"sessionId",null);a(this,"timestamp",null);a(this,"creditToken",null);a(this,"cardToken",null);a(this,"cardTimestamp",null);a(this,"eventHandlers",new Map);typeof e=="string"?(this.publishableKey=e,this.baseUrl=this.getDefaultBaseUrl(),this.embedUrl=this.getDefaultEmbedUrl()):(this.publishableKey=e.publishableKey,this.baseUrl=e.baseUrl||this.getDefaultBaseUrl(),this.embedUrl=e.embedUrl||this.getDefaultEmbedUrl()),window.addEventListener("message",this.handleMessage.bind(this))}async mount(e){let t=typeof e=="string"?document.querySelector(e):e;if(!t)throw new Error(`Container not found: ${e}`);return this.container=t,this.iframe=document.createElement("iframe"),this.iframe.src=`${this.embedUrl}/elements?key=${encodeURIComponent(this.publishableKey)}`,this.iframe.style.cssText=`
1393
1520
  width: 100%;
1394
1521
  border: none;
1395
1522
  min-height: 200px;
1396
1523
  display: block;
1397
1524
  user-select: none;
1398
1525
  transition: height 0.35s ease, opacity 0.4s ease 0.1s;
1399
- `.replace(/\s+/g," ").trim(),this.iframe.allow="payment *",this.iframe.setAttribute("title","Secure payment input frame"),this.iframe.setAttribute("role","presentation"),this.iframe.setAttribute("scrolling","no"),this.iframe.setAttribute("frameBorder","0"),this.container.innerHTML="",this.container.appendChild(this.iframe),new Promise((r,i)=>{let s=setTimeout(()=>{i(new Error("Elements initialization timeout"))},3e4),n=()=>{clearTimeout(s),this.off("ready",n),r()},o=d=>{clearTimeout(s),this.off("error",o),i(new Error(d.message||"Elements initialization failed"))};this.on("ready",n),this.on("error",o)})}on(e,t){this.eventHandlers.has(e)||this.eventHandlers.set(e,new Set),this.eventHandlers.get(e).add(t)}off(e,t){let r=this.eventHandlers.get(e);r&&r.delete(t)}emit(e,t){let r=this.eventHandlers.get(e);r&&r.forEach(i=>i(t))}handleMessage(e){if(!this.isValidOrigin(e.origin))return;let{type:t,data:r}=e.data||{};switch(t){case"RECUR_ELEMENTS_READY":this.sessionId=r.sessionId,this.timestamp=r.timestamp,this.creditToken=r.creditToken,this.emit("ready",r);break;case"RECUR_CARD_VALID":this.emit("change",{valid:r.valid});break;case"RECUR_CARD_TOKENIZED":this.cardToken=r.cardToken,this.cardTimestamp=r.timestamp,this.emit("tokenized",r);break;case"RECUR_PAYMENT_ERROR":this.emit("error",r);break;case"RECUR_RESIZE":this.iframe&&r?.height&&(this.iframe.style.height=`${r.height}px`);break}}isValidOrigin(e){try{let t=new URL(this.embedUrl).origin;return e===t}catch{return!1}}async tokenize(){if(!this.iframe||!this.iframe.contentWindow)throw new Error("Elements not mounted");let e=new URL(this.embedUrl).origin;return this.iframe.contentWindow.postMessage({type:"RECUR_TOKENIZE"},e),new Promise((t,r)=>{let i=setTimeout(()=>{r(new Error("Tokenization timeout"))},6e4),s=o=>{clearTimeout(i),this.off("tokenized",s),t(o)},n=o=>{clearTimeout(i),this.off("error",n),r(new Error(o.message||"Tokenization failed"))};this.on("tokenized",s),this.on("error",n)})}async confirmPayment(e){if(!this.sessionId||!this.cardToken)throw new Error("Card not tokenized. Call tokenize() first.");let t=await fetch(`${this.baseUrl}/api/v1/elements/confirm`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":this.publishableKey},body:JSON.stringify({sessionId:this.sessionId,sdkToken:this.cardToken,timestamp:this.cardTimestamp,creditToken:this.creditToken,productId:e.productId,email:e.email,name:e.name,metadata:e.metadata})}),r=await t.json();return t.ok?r:{status:"failed",message:r.error||r.message||"Payment failed",canRetry:r.canRetry??!0}}getDefaultBaseUrl(){if(typeof window>"u")return"https://app.recur.tw";let e=window.location.hostname;return e==="localhost"||e.includes(".test")||e.includes(".local")||e==="127.0.0.1"?`${window.location.protocol}//${e}:3000`:"https://app.recur.tw"}getDefaultEmbedUrl(){if(typeof window>"u")return"https://embed.recur.tw";let e=window.location.hostname;return e==="localhost"||e.includes(".test")||e.includes(".local")||e==="127.0.0.1"?`${window.location.protocol}//${e}:3002`:"https://embed.recur.tw"}destroy(){this.iframe&&this.iframe.parentNode&&this.iframe.parentNode.removeChild(this.iframe),this.iframe=null,this.container=null,this.sessionId=null,this.timestamp=null,this.creditToken=null,this.cardToken=null,this.cardTimestamp=null,this.eventHandlers.clear(),window.removeEventListener("message",this.handleMessage.bind(this))}};function N(c){return new y(c)}var fe="https://vendor.payuni.com.tw/sdk/uni-payment.js",be="https://sandbox-vendor.payuni.com.tw/sdk/uni-payment.js",se=!1,z=!1,w=null;async function oe(c=!1){return se&&window.UniPayment?Promise.resolve():(z&&w||(z=!0,w=new Promise((e,t)=>{let r=document.createElement("script");r.src=c?be:fe,r.async=!0,r.onload=()=>{se=!0,z=!1,e()},r.onerror=()=>{z=!1,w=null,t(new Error("Failed to load PAYUNi SDK"))},document.head.appendChild(r)})),w)}var E=class{constructor(e){a(this,"core");a(this,"currentModal",null);a(this,"currentIframe",null);a(this,"currentModalOverlay",null);this.core=new D(e)}async fetchProducts(e){return await this.core.fetchProducts(e)}async fetchPlans(){return await this.core.fetchPlans()}async createEmbeddedCheckout(e){let t=this.core.getConfig();await new _(t,e).render()}async redirectToCheckout(e){let t=this.core.getConfig(),r=()=>{if(typeof window<"u"){let d=window.location.hostname;if(d==="localhost"||d.includes(".test")||d.includes(".local")||d==="127.0.0.1")return`${window.location.protocol}//${window.location.host}/api`}return"https://api.recur.tw"},i=t.baseUrl||r(),s={productId:e.productId,successUrl:e.successUrl,cancelUrl:e.cancelUrl};e.mode&&(s.mode=e.mode),e.customerEmail&&(s.customerEmail=e.customerEmail),e.externalCustomerId&&(s.externalCustomerId=e.externalCustomerId);let n=await fetch(`${i}/v1/checkout/sessions`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":t.publishableKey},body:JSON.stringify(s)});if(!n.ok){let d=await n.json().catch(()=>({}));throw new Error(d.error?.message||"Failed to create checkout session")}let o=await n.json();window.location.href=o.url}async createCheckoutSession(e){let t=this.core.getConfig(),r=()=>{if(typeof window<"u"){let o=window.location.hostname;if(o==="localhost"||o.includes(".test")||o.includes(".local")||o==="127.0.0.1")return`${window.location.protocol}//${window.location.host}/api`}return"https://api.recur.tw"},i=t.baseUrl||r(),s={productId:e.productId,successUrl:e.successUrl,cancelUrl:e.cancelUrl};e.mode&&(s.mode=e.mode),e.customerEmail&&(s.customerEmail=e.customerEmail),e.externalCustomerId&&(s.externalCustomerId=e.externalCustomerId);let n=await fetch(`${i}/v1/checkout/sessions`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":t.publishableKey},body:JSON.stringify(s)});if(!n.ok){let o=await n.json().catch(()=>({}));throw new Error(o.error?.message||"Failed to create checkout session")}return n.json()}async checkout(e){let t=this.core.getConfig(),r=null;try{if(console.log("[Recur SDK] Starting checkout flow...",{planId:e.planId,mode:e.mode}),!e.planId)throw new Error("planId is required");if(!t.publishableKey)throw new Error("publishableKey is required");if(!e.customerName)throw new Error("customerName is required");if(!e.customerEmail)throw new Error("customerEmail is required");let i=this.getBaseUrl();console.log("[Recur SDK] Base URL:",i);let s={"Content-Type":"application/json","X-Recur-Publishable-Key":t.publishableKey},n=e.mode||"modal",o=null;if(n==="modal"){let u=this.createModalWithSkeleton(e.onClose);r=u.overlay,o=u.container}else if(n==="iframe"){if(o=this.getEmbeddedContainer(e.container),!o)throw new Error("Container is required for iframe mode");o.innerHTML="";let u=document.createElement("recur-payment-form-skeleton");o.appendChild(u)}console.log("[Recur SDK] Step 1: Creating checkout session...");let d=await fetch(`${i}/v1/checkouts`,{method:"POST",headers:s,body:JSON.stringify({productId:e.planId,customerName:e.customerName,customerEmail:e.customerEmail,externalCustomerId:e.externalCustomerId})});if(!d.ok){let u=await d.json().catch(()=>({}));console.error("[Recur SDK] Failed to create checkout:",u);let A=u.details||u.error||"Failed to create checkout";throw new Error(A)}let l=await d.json();if(console.log("[Recur SDK] Checkout created successfully:",l),e.onSuccess?.(l),n==="redirect"){let u=`https://checkout.recur.tw/${l.checkout.id}`;console.log("[Recur SDK] Redirecting to hosted checkout:",u),window.location.href=u;return}if(console.log("[Recur SDK] Step 2: Extracting SDK token..."),!l.sdkToken)throw new Error("SDK token missing from checkout response");console.log("[Recur SDK] Step 3: Loading PAYUNi SDK...");let v=!0;if(await oe(v),console.log("[Recur SDK] PAYUNi SDK loaded successfully"),!window.UniPayment)throw new Error("PAYUNi SDK failed to load");if(console.log("[Recur SDK] Step 4: Rendering payment form..."),!o)throw new Error("Payment container not available");o.innerHTML="";let m=document.createElement("recur-payment-form");if(m.setAttribute("container-id",o.id||"recur-payment-container"),e.customerName&&m.setAttribute("customer-name",e.customerName),e.customerEmail&&m.setAttribute("customer-email",e.customerEmail),l.plan?.name&&m.setAttribute("plan-name",l.plan.name),l.checkout?.amount&&m.setAttribute("amount",l.checkout.amount.toString()),l.plan?.billingPeriod&&m.setAttribute("billing-period",l.plan.billingPeriod),m.setAttribute("custom-styles",`
1526
+ `.replace(/\s+/g," ").trim(),this.iframe.allow="payment *",this.iframe.setAttribute("title","Secure payment input frame"),this.iframe.setAttribute("role","presentation"),this.iframe.setAttribute("scrolling","no"),this.iframe.setAttribute("frameBorder","0"),this.container.innerHTML="",this.container.appendChild(this.iframe),new Promise((r,i)=>{let s=setTimeout(()=>{i(new Error("Elements initialization timeout"))},3e4),n=()=>{clearTimeout(s),this.off("ready",n),r()},o=l=>{clearTimeout(s),this.off("error",o),i(new Error(l.message||"Elements initialization failed"))};this.on("ready",n),this.on("error",o)})}on(e,t){this.eventHandlers.has(e)||this.eventHandlers.set(e,new Set),this.eventHandlers.get(e).add(t)}off(e,t){let r=this.eventHandlers.get(e);r&&r.delete(t)}emit(e,t){let r=this.eventHandlers.get(e);r&&r.forEach(i=>i(t))}handleMessage(e){if(!this.isValidOrigin(e.origin))return;let{type:t,data:r}=e.data||{};switch(t){case"RECUR_ELEMENTS_READY":this.sessionId=r.sessionId,this.timestamp=r.timestamp,this.creditToken=r.creditToken,this.emit("ready",r);break;case"RECUR_CARD_VALID":this.emit("change",{valid:r.valid});break;case"RECUR_CARD_TOKENIZED":this.cardToken=r.cardToken,this.cardTimestamp=r.timestamp,this.emit("tokenized",r);break;case"RECUR_PAYMENT_ERROR":this.emit("error",r);break;case"RECUR_RESIZE":this.iframe&&r?.height&&(this.iframe.style.height=`${r.height}px`);break}}isValidOrigin(e){try{let t=new URL(this.embedUrl).origin;return e===t}catch{return!1}}async tokenize(){if(!this.iframe||!this.iframe.contentWindow)throw new Error("Elements not mounted");let e=new URL(this.embedUrl).origin;return this.iframe.contentWindow.postMessage({type:"RECUR_TOKENIZE"},e),new Promise((t,r)=>{let i=setTimeout(()=>{r(new Error("Tokenization timeout"))},6e4),s=o=>{clearTimeout(i),this.off("tokenized",s),t(o)},n=o=>{clearTimeout(i),this.off("error",n),r(new Error(o.message||"Tokenization failed"))};this.on("tokenized",s),this.on("error",n)})}async confirmPayment(e){if(!this.sessionId||!this.cardToken)throw new Error("Card not tokenized. Call tokenize() first.");let t=await fetch(`${this.baseUrl}/api/v1/elements/confirm`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":this.publishableKey},body:JSON.stringify({sessionId:this.sessionId,sdkToken:this.cardToken,timestamp:this.cardTimestamp,creditToken:this.creditToken,productId:e.productId,email:e.email,name:e.name,metadata:e.metadata})}),r=await t.json();return t.ok?r:{status:"failed",message:r.error||r.message||"Payment failed",canRetry:r.canRetry??!0}}getDefaultBaseUrl(){if(typeof window>"u")return"https://app.recur.tw";let e=window.location.hostname;return e==="localhost"||e.includes(".test")||e.includes(".local")||e==="127.0.0.1"?`${window.location.protocol}//${e}:3000`:"https://app.recur.tw"}getDefaultEmbedUrl(){if(typeof window>"u")return"https://embed.recur.tw";let e=window.location.hostname;return e==="localhost"||e.includes(".test")||e.includes(".local")||e==="127.0.0.1"?`${window.location.protocol}//${e}:3002`:"https://embed.recur.tw"}destroy(){this.iframe&&this.iframe.parentNode&&this.iframe.parentNode.removeChild(this.iframe),this.iframe=null,this.container=null,this.sessionId=null,this.timestamp=null,this.creditToken=null,this.cardToken=null,this.cardTimestamp=null,this.eventHandlers.clear(),window.removeEventListener("message",this.handleMessage.bind(this))}};function K(c){return new k(c)}var we="https://vendor.payuni.com.tw/sdk/uni-payment.js",xe="https://sandbox-vendor.payuni.com.tw/sdk/uni-payment.js",de=!1,H=!1,E=null;async function ue(c=!1){return de&&window.UniPayment?Promise.resolve():(H&&E||(H=!0,E=new Promise((e,t)=>{let r=document.createElement("script");r.src=c?xe:we,r.async=!0,r.onload=()=>{de=!0,H=!1,e()},r.onerror=()=>{H=!1,E=null,t(new Error("Failed to load PAYUNi SDK"))},document.head.appendChild(r)})),E)}var S=class{constructor(e){a(this,"core");a(this,"currentModal",null);a(this,"currentIframe",null);a(this,"currentModalOverlay",null);this.core=new D(e)}async fetchProducts(e){return await this.core.fetchProducts(e)}async fetchPlans(){return await this.core.fetchPlans()}async createEmbeddedCheckout(e){let t=this.core.getConfig();await new z(t,e).render()}async redirectToCheckout(e){let t=this.core.getConfig(),r=()=>{if(typeof window<"u"){let l=window.location.hostname;if(l==="localhost"||l.includes(".test")||l.includes(".local")||l==="127.0.0.1")return`${window.location.protocol}//${window.location.host}/api`}return"https://api.recur.tw"},i=t.baseUrl||r();if(!e.productId&&!e.productSlug)throw new Error("Either productId or productSlug is required");let s={successUrl:e.successUrl,cancelUrl:e.cancelUrl};e.productId&&(s.productId=e.productId),e.productSlug&&(s.productSlug=e.productSlug),e.mode&&(s.mode=e.mode),e.customerEmail&&(s.customerEmail=e.customerEmail),e.customerName&&(s.customerName=e.customerName),e.externalCustomerId&&(s.externalCustomerId=e.externalCustomerId);let n=await fetch(`${i}/v1/checkout/sessions`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":t.publishableKey},body:JSON.stringify(s)});if(!n.ok){let l=await n.json().catch(()=>({}));throw new Error(l.error?.message||"Failed to create checkout session")}let o=await n.json();window.location.href=o.url}async createCheckoutSession(e){let t=this.core.getConfig(),r=()=>{if(typeof window<"u"){let o=window.location.hostname;if(o==="localhost"||o.includes(".test")||o.includes(".local")||o==="127.0.0.1")return`${window.location.protocol}//${window.location.host}/api`}return"https://api.recur.tw"},i=t.baseUrl||r();if(!e.productId&&!e.productSlug)throw new Error("Either productId or productSlug is required");let s={successUrl:e.successUrl,cancelUrl:e.cancelUrl};e.productId&&(s.productId=e.productId),e.productSlug&&(s.productSlug=e.productSlug),e.mode&&(s.mode=e.mode),e.customerEmail&&(s.customerEmail=e.customerEmail),e.customerName&&(s.customerName=e.customerName),e.externalCustomerId&&(s.externalCustomerId=e.externalCustomerId);let n=await fetch(`${i}/v1/checkout/sessions`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":t.publishableKey},body:JSON.stringify(s)});if(!n.ok){let o=await n.json().catch(()=>({}));throw new Error(o.error?.message||"Failed to create checkout session")}return n.json()}async checkout(e){let t=this.core.getConfig(),r=null,i=e.productId||e.planId,s=e.productSlug;try{if(console.log("[Recur SDK] Starting checkout flow...",{productId:i,productSlug:s,mode:e.mode}),!i&&!s)throw new Error("Either productId or productSlug is required");if(!t.publishableKey)throw new Error("publishableKey is required");if(!e.customerName)throw new Error("customerName is required");if(!e.customerEmail)throw new Error("customerEmail is required");let n=this.getBaseUrl();console.log("[Recur SDK] Base URL:",n);let o={"Content-Type":"application/json","X-Recur-Publishable-Key":t.publishableKey},l=e.mode||"modal",u=null;if(l==="modal"){let m=this.createModalWithSkeleton(e.onClose);r=m.overlay,u=m.container}else if(l==="iframe"){if(u=this.getEmbeddedContainer(e.container),!u)throw new Error("Container is required for iframe mode");u.innerHTML="";let m=document.createElement("recur-payment-form-skeleton");u.appendChild(m)}console.log("[Recur SDK] Step 1: Creating checkout session...");let y={customerName:e.customerName,customerEmail:e.customerEmail};i&&(y.productId=i),s&&(y.productSlug=s),e.externalCustomerId&&(y.externalCustomerId=e.externalCustomerId);let $=await fetch(`${n}/v1/checkouts`,{method:"POST",headers:o,body:JSON.stringify(y)});if(!$.ok){let m=await $.json().catch(()=>({}));console.error("[Recur SDK] Failed to create checkout:",m);let N=m.details||m.error||"Failed to create checkout";throw new Error(N)}let d=await $.json();if(console.log("[Recur SDK] Checkout created successfully:",d),e.onSuccess?.(d),l==="redirect"){let m=`https://checkout.recur.tw/${d.checkout.id}`;console.log("[Recur SDK] Redirecting to hosted checkout:",m),window.location.href=m;return}if(console.log("[Recur SDK] Step 2: Extracting SDK token..."),!d.sdkToken)throw new Error("SDK token missing from checkout response");console.log("[Recur SDK] Step 3: Loading PAYUNi SDK...");let j=!0;if(await ue(j),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..."),!u)throw new Error("Payment container not available");u.innerHTML="";let p=document.createElement("recur-payment-form");if(p.setAttribute("container-id",u.id||"recur-payment-container"),e.customerName&&p.setAttribute("customer-name",e.customerName),e.customerEmail&&p.setAttribute("customer-email",e.customerEmail),d.plan?.name&&p.setAttribute("plan-name",d.plan.name),d.checkout?.amount&&p.setAttribute("amount",d.checkout.amount.toString()),d.plan?.billingPeriod&&p.setAttribute("billing-period",d.plan.billingPeriod),p.setAttribute("custom-styles",`
1400
1527
  .form-input-focus {
1401
1528
  border-color: var(--ring, hsl(215 16% 47%)) !important;
1402
1529
  outline: 0 !important;
1403
1530
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring, hsl(215 16% 47%)) 50%, transparent) !important;
1404
1531
  transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out !important;
1405
1532
  }
1406
- `),o.appendChild(m),await new Promise(u=>setTimeout(u,100)),console.log("[Recur SDK] Step 5: Initializing PAYUNi SDK..."),await m.initializePayment(l.sdkToken,v?"SANDBOX":"PRODUCTION")===null){console.log("[Recur SDK] Initialization aborted");return}console.log("[Recur SDK] PAYUNi SDK initialized successfully"),console.log("[Recur SDK] Step 6: Setting up submit handler..."),m.addEventListener("submit",(async u=>{console.log("[Recur SDK] Form submitted");let A=u,{paymentSession:ae}=A.detail;try{let g=await ae.getTradeResult();console.log("[Recur SDK] Trade result received"),console.log("[Recur SDK] Executing payment...");let H=g.HashTimestamp||g.timestamp,B={};if(l.checkout.productType==="SUBSCRIPTION"){let k=l.creditToken,K=l.sdkTimestamp;if(!k)throw console.error("[Recur SDK] Missing creditToken from checkout for subscription"),new Error("Missing creditToken for subscription payment");B={creditToken:k,timestamp:K||H},console.log("[Recur SDK] Using creditToken from checkout:",k.substring(0,30)+"..."),console.log("[Recur SDK] Using timestamp:",K?"from checkout (sdkTimestamp)":"from tradeResult")}let $=await fetch(`${i}/v1/checkouts/${l.checkout.id}/pay`,{method:"POST",headers:s,body:JSON.stringify(B)});if(!$.ok){let k=await $.json().catch(()=>({}));throw new Error(k.error||"Failed to execute payment")}let p=await $.json();if(console.log("[Recur SDK] Payment executed:",p),p.requires3D&&p.redirectUrl){console.log("[Recur SDK] 3D verification required"),window.location.href=p.redirectUrl;return}e.onPaymentComplete&&(p.subscription?e.onPaymentComplete({id:p.subscription.id,status:p.subscription.status,planId:l.checkout.productId,amount:l.checkout.amount,billingPeriod:p.subscription.billingPeriod,currentPeriodStart:p.subscription.currentPeriodStart,currentPeriodEnd:p.subscription.currentPeriodEnd}):e.onPaymentComplete({id:p.checkout.id,status:p.checkout.status,planId:l.checkout.productId,amount:l.checkout.amount,billingPeriod:l.checkout.productType})),console.log("[Recur SDK] Checkout flow completed successfully!"),m.resetButton?.(),r&&r.remove()}catch(g){console.error("[Recur SDK] Payment error:",g);let H={code:"PAYMENT_FAILED",message:g instanceof Error?g.message:"Payment failed"};e.onError?.(H),m.resetButton?.()}})),console.log("[Recur SDK] Checkout flow initialized, waiting for user input...")}catch(i){console.error("[Recur SDK] Checkout error:",i),r&&r.remove();let s={code:"CHECKOUT_ERROR",message:i instanceof Error?i.message:"An unknown error occurred"};throw e.onError?.(s),i}}getBaseUrl(){let e=this.core.getConfig();if(e.baseUrl)return e.baseUrl;if(typeof window<"u"){let t=window.location.hostname;if(t==="localhost"||t.includes(".test")||t.includes(".local")||t==="127.0.0.1")return`${window.location.protocol}//${window.location.host}/api`}return"https://api.recur.tw"}createModalWithSkeleton(e){this.closeModal();let t=document.createElement("div");t.id="recur-modal-overlay",t.style.cssText=`
1533
+ `),u.appendChild(p),await new Promise(m=>setTimeout(m,100)),console.log("[Recur SDK] Step 5: Initializing PAYUNi SDK..."),await p.initializePayment(d.sdkToken,j?"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 m=>{console.log("[Recur SDK] Form submitted");let N=m,{paymentSession:pe}=N.detail;try{let v=await pe.getTradeResult();console.log("[Recur SDK] Trade result received"),console.log("[Recur SDK] Executing payment...");let B=v.HashTimestamp||v.timestamp,F={};if(d.checkout.productType==="SUBSCRIPTION"){let w=d.creditToken,q=d.sdkTimestamp;if(!w)throw console.error("[Recur SDK] Missing creditToken from checkout for subscription"),new Error("Missing creditToken for subscription payment");F={creditToken:w,timestamp:q||B},console.log("[Recur SDK] Using creditToken from checkout:",w.substring(0,30)+"..."),console.log("[Recur SDK] Using timestamp:",q?"from checkout (sdkTimestamp)":"from tradeResult")}let O=await fetch(`${n}/v1/checkouts/${d.checkout.id}/pay`,{method:"POST",headers:o,body:JSON.stringify(F)});if(!O.ok){let w=await O.json().catch(()=>({}));throw new Error(w.error||"Failed to execute payment")}let h=await O.json();if(console.log("[Recur SDK] Payment executed:",h),h.requires3D&&h.redirectUrl){console.log("[Recur SDK] 3D verification required"),window.location.href=h.redirectUrl;return}e.onPaymentComplete&&(h.subscription?e.onPaymentComplete({id:h.subscription.id,status:h.subscription.status,planId:d.checkout.productId,amount:d.checkout.amount,billingPeriod:h.subscription.billingPeriod,currentPeriodStart:h.subscription.currentPeriodStart,currentPeriodEnd:h.subscription.currentPeriodEnd}):e.onPaymentComplete({id:h.checkout.id,status:h.checkout.status,planId:d.checkout.productId,amount:d.checkout.amount,billingPeriod:d.checkout.productType})),console.log("[Recur SDK] Checkout flow completed successfully!"),p.resetButton?.(),r&&r.remove()}catch(v){console.error("[Recur SDK] Payment error:",v);let B={code:"PAYMENT_FAILED",message:v instanceof Error?v.message:"Payment failed"};e.onError?.(B),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=`
1407
1534
  position: fixed;
1408
1535
  top: 0;
1409
1536
  left: 0;
@@ -1442,7 +1569,7 @@
1442
1569
  background: white;
1443
1570
  border-radius: 12px;
1444
1571
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
1445
- `;let n=document.createElement("recur-payment-form-skeleton");return s.appendChild(n),r.appendChild(i),r.appendChild(s),t.appendChild(r),document.body.appendChild(t),this.currentModalOverlay=t,{overlay:t,container:s}}getEmbeddedContainer(e){return e?typeof e=="string"?document.getElementById(e)||document.querySelector(e):e:null}closeModal(){this.currentModal&&(this.currentModal.close(),this.currentModal=null),this.currentModalOverlay&&(this.currentModalOverlay.remove(),this.currentModalOverlay=null)}removeIframe(){this.currentIframe&&(this.currentIframe.remove(),this.currentIframe=null)}close(){this.closeModal(),this.removeIframe()}};function ne(c){return new E(c)}var ge={init:ne,RecurCheckout:E,RecurElements:y,createElements:N};return pe(ye);})();
1572
+ `;let n=document.createElement("recur-payment-form-skeleton");return s.appendChild(n),r.appendChild(i),r.appendChild(s),t.appendChild(r),document.body.appendChild(t),this.currentModalOverlay=t,{overlay:t,container:s}}getEmbeddedContainer(e){return e?typeof e=="string"?document.getElementById(e)||document.querySelector(e):e:null}closeModal(){this.currentModal&&(this.currentModal.close(),this.currentModal=null),this.currentModalOverlay&&(this.currentModalOverlay.remove(),this.currentModalOverlay=null)}removeIframe(){this.currentIframe&&(this.currentIframe.remove(),this.currentIframe=null)}close(){this.closeModal(),this.removeIframe()}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 s=await r.json().catch(()=>({}));throw new Error(s.error?.message||s.message||"Failed to create portal session")}let i=await r.json();return{id:i.id,url:i.url||i.portalUrl,expiresAt:i.expiresAt}}async redirectToPortal(e,t){let r=await this.createPortalSession(e,t);window.location.href=r.url}};function me(c){return new S(c)}var Ee={init:me,RecurCheckout:S,RecurElements:k,createElements:K};return ve(Se);})();
1446
1573
  if (typeof window !== "undefined") {
1447
1574
  window.RecurCheckout = RecurCheckout.default;
1448
1575
  window.RecurElements = RecurCheckout.RecurElements;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "recur-tw",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "React & Vanilla JS SDK for embedding subscription checkout flows (Taiwan / PAYUNi)",
5
5
  "type": "module",
6
6
  "private": false,