recur-tw 0.10.7 → 0.10.8

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
@@ -247,7 +247,7 @@ var init_error_display = __esm({
247
247
  "src/components/base/error-display.ts"() {
248
248
  RecurErrorDisplay = class extends HTMLElement {
249
249
  static get observedAttributes() {
250
- return ["error", "dismissible"];
250
+ return ["error", "error-title", "dismissible"];
251
251
  }
252
252
  constructor() {
253
253
  super();
@@ -264,6 +264,9 @@ var init_error_display = __esm({
264
264
  get error() {
265
265
  return this.getAttribute("error") || "";
266
266
  }
267
+ get errorTitle() {
268
+ return this.getAttribute("error-title") || "";
269
+ }
267
270
  get isDismissible() {
268
271
  return this.getAttribute("dismissible") === "true";
269
272
  }
@@ -308,6 +311,14 @@ var init_error_display = __esm({
308
311
  flex: 1;
309
312
  }
310
313
 
314
+ .recur-sdk__error-title {
315
+ margin: 0 0 4px 0;
316
+ font-size: 14px;
317
+ font-weight: 600;
318
+ color: var(--recur-error-text, #991b1b);
319
+ line-height: 1.4;
320
+ }
321
+
311
322
  .recur-sdk__error-message {
312
323
  margin: 0;
313
324
  font-size: 14px;
@@ -359,6 +370,7 @@ var init_error_display = __esm({
359
370
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
360
371
  </svg>
361
372
  <div class="recur-sdk__error-content">
373
+ ${this.errorTitle ? `<p class="recur-sdk__error-title">${this.errorTitle}</p>` : ""}
362
374
  <p class="recur-sdk__error-message">${this.error}</p>
363
375
  </div>
364
376
  ${this.isDismissible ? `
@@ -1968,7 +1980,13 @@ var init_payment_form = __esm({
1968
1980
  this.setButtonLoading(false);
1969
1981
  }
1970
1982
  }
1971
- showError(message) {
1983
+ /**
1984
+ * Show error message in the form
1985
+ * @param options - Either a string message or an object with title and message
1986
+ */
1987
+ showError(options) {
1988
+ const message = typeof options === "string" ? options : options.message;
1989
+ const title = typeof options === "object" ? options.title : void 0;
1972
1990
  let errorContainer = this.querySelector(".recur-sdk__error-container");
1973
1991
  if (!errorContainer) {
1974
1992
  errorContainer = document.createElement("div");
@@ -1978,6 +1996,9 @@ var init_payment_form = __esm({
1978
1996
  }
1979
1997
  const errorDisplay = document.createElement("recur-error-display");
1980
1998
  errorDisplay.setAttribute("error", message);
1999
+ if (title) {
2000
+ errorDisplay.setAttribute("error-title", title);
2001
+ }
1981
2002
  errorDisplay.setAttribute("dismissible", "true");
1982
2003
  errorContainer.innerHTML = "";
1983
2004
  errorContainer.appendChild(errorDisplay);
@@ -2653,7 +2674,7 @@ function toCamelCase(obj) {
2653
2674
 
2654
2675
  // package.json
2655
2676
  var package_default = {
2656
- version: "0.10.6"};
2677
+ version: "0.10.8"};
2657
2678
  var SDK_VERSION = package_default.version;
2658
2679
  var SDK_TYPE = "react";
2659
2680
  var RecurContext = React.createContext(null);
@@ -3051,6 +3072,46 @@ function RecurProvider({ children, config: initialConfig = {} }) {
3051
3072
  const rawPaymentResult = await paymentResponse.json();
3052
3073
  const paymentResult = toCamelCase(rawPaymentResult);
3053
3074
  console.log("[Recur SDK] Payment executed:", paymentResult);
3075
+ if (paymentResult.success === false && paymentResult.failure) {
3076
+ console.log("[Recur SDK] Payment failed:", paymentResult.failure);
3077
+ const failureError = {
3078
+ code: "PAYMENT_FAILED",
3079
+ message: paymentResult.failure.message || "\u4ED8\u6B3E\u5931\u6557",
3080
+ details: {
3081
+ failure_code: paymentResult.failure.code,
3082
+ failure_message: paymentResult.failure.message,
3083
+ can_retry: paymentResult.failure.canRetry
3084
+ }
3085
+ };
3086
+ let action;
3087
+ if (options.onPaymentFailed) {
3088
+ action = options.onPaymentFailed(failureError);
3089
+ }
3090
+ if (!action) {
3091
+ action = paymentResult.failure.canRetry ? { action: "retry" } : { action: "retry" };
3092
+ }
3093
+ if (action.action === "close") {
3094
+ options.onError?.(failureError);
3095
+ paymentForm.resetButton?.();
3096
+ closeDialog();
3097
+ setIsCheckingOut(false);
3098
+ return;
3099
+ } else if (action.action === "custom") {
3100
+ paymentForm.showError?.({
3101
+ title: action.customTitle || "\u4ED8\u6B3E\u5931\u6557",
3102
+ message: action.customMessage || failureError.message
3103
+ });
3104
+ paymentForm.resetButton?.();
3105
+ return;
3106
+ } else {
3107
+ paymentForm.showError?.({
3108
+ title: "\u4ED8\u6B3E\u5931\u6557",
3109
+ message: failureError.details?.failure_message || failureError.message
3110
+ });
3111
+ paymentForm.resetButton?.();
3112
+ return;
3113
+ }
3114
+ }
3054
3115
  if (paymentResult.requires3D && paymentResult.redirectUrl) {
3055
3116
  console.log("[Recur SDK] 3D verification required");
3056
3117
  const isMobileOrWebView = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || // Detect common WebView user agents
@@ -3401,7 +3462,7 @@ function useProducts(options = {}) {
3401
3462
  };
3402
3463
  }
3403
3464
  function useSubscribe(options = {}) {
3404
- const { onSuccess, onError, onPaymentComplete, onPaymentCancel } = options;
3465
+ const { onSuccess, onError, onPaymentComplete, onPaymentCancel, onPaymentFailed } = options;
3405
3466
  const { checkout, isCheckingOut } = useRecur();
3406
3467
  const [error, setError] = React.useState(null);
3407
3468
  const mutate = React.useCallback(
@@ -3420,6 +3481,9 @@ function useSubscribe(options = {}) {
3420
3481
  setError(err);
3421
3482
  onError?.(err);
3422
3483
  },
3484
+ onPaymentFailed: (err) => {
3485
+ return onPaymentFailed?.(err);
3486
+ },
3423
3487
  onPaymentCancel: () => {
3424
3488
  onPaymentCancel?.();
3425
3489
  }
@@ -3433,7 +3497,7 @@ function useSubscribe(options = {}) {
3433
3497
  onError?.(checkoutError);
3434
3498
  }
3435
3499
  },
3436
- [checkout, onSuccess, onError, onPaymentComplete, onPaymentCancel]
3500
+ [checkout, onSuccess, onError, onPaymentComplete, onPaymentCancel, onPaymentFailed]
3437
3501
  );
3438
3502
  const reset = React.useCallback(() => {
3439
3503
  setError(null);
package/dist/index.d.cts CHANGED
@@ -55,6 +55,7 @@ declare class RecurErrorDisplay extends HTMLElement {
55
55
  connectedCallback(): void;
56
56
  attributeChangedCallback(name: string, oldValue: string, newValue: string): void;
57
57
  private get error();
58
+ private get errorTitle();
58
59
  private get isDismissible();
59
60
  private handleDismiss;
60
61
  private render;
@@ -275,6 +276,30 @@ interface CheckoutOptions {
275
276
  * Callback when checkout fails
276
277
  */
277
278
  onError?: (error: CheckoutError) => void;
279
+ /**
280
+ * Callback when payment fails (after user submits payment)
281
+ *
282
+ * This allows you to customize behavior based on the specific failure reason.
283
+ * Return an action object to override default behavior, or return nothing/undefined
284
+ * to use the SDK's default handling.
285
+ *
286
+ * @example
287
+ * ```typescript
288
+ * onPaymentFailed: (error) => {
289
+ * // Custom handling for insufficient funds
290
+ * if (error.details?.failure_code === 'INSUFFICIENT_FUNDS') {
291
+ * return {
292
+ * action: 'custom',
293
+ * customTitle: '餘額不足',
294
+ * customMessage: '請使用其他付款方式或聯繫客服'
295
+ * };
296
+ * }
297
+ * // Use default behavior for other errors
298
+ * return undefined;
299
+ * }
300
+ * ```
301
+ */
302
+ onPaymentFailed?: (error: CheckoutError) => PaymentFailedAction | void | undefined;
278
303
  /**
279
304
  * Callback when payment is completed
280
305
  * (only works in popup mode)
@@ -323,6 +348,31 @@ interface CheckoutResult {
323
348
  */
324
349
  sdkTokenExpiresAt: string;
325
350
  }
351
+ /**
352
+ * Payment failure codes from payment provider
353
+ * These indicate the specific reason a payment was declined
354
+ */
355
+ type PaymentFailureCode = 'PAYUNI_DECLINED' | 'UNAPPROVED' | 'INSUFFICIENT_FUNDS' | 'CARD_DECLINED' | 'EXPIRED_CARD' | 'NETWORK_ERROR' | 'TIMEOUT' | 'INVALID_CARD' | 'UNKNOWN';
356
+ /**
357
+ * Extended error details for payment failures
358
+ */
359
+ interface PaymentFailureDetails {
360
+ /**
361
+ * Specific failure code from payment provider
362
+ * Use this to show appropriate error messages or take specific actions
363
+ */
364
+ failure_code?: PaymentFailureCode | string;
365
+ /**
366
+ * Raw failure message from payment provider
367
+ */
368
+ failure_message?: string;
369
+ /**
370
+ * Whether the user can retry with the same payment method
371
+ * - true: Temporary error (network, timeout) - retry may succeed
372
+ * - false: Permanent error (card declined, expired) - different payment method needed
373
+ */
374
+ can_retry?: boolean;
375
+ }
326
376
  interface CheckoutError {
327
377
  /**
328
378
  * Error code
@@ -335,7 +385,27 @@ interface CheckoutError {
335
385
  /**
336
386
  * Additional error details
337
387
  */
338
- details?: Record<string, unknown>;
388
+ details?: Record<string, unknown> & PaymentFailureDetails;
389
+ }
390
+ /**
391
+ * Action to take after payment failure
392
+ */
393
+ interface PaymentFailedAction {
394
+ /**
395
+ * What action to take
396
+ * - 'retry': Keep dialog open, allow user to retry (default for can_retry=true)
397
+ * - 'close': Close dialog, trigger onError callback
398
+ * - 'custom': Show custom message in dialog
399
+ */
400
+ action: 'retry' | 'close' | 'custom';
401
+ /**
402
+ * Custom message to display (only used when action is 'custom')
403
+ */
404
+ customMessage?: string;
405
+ /**
406
+ * Custom title to display (only used when action is 'custom')
407
+ */
408
+ customTitle?: string;
339
409
  }
340
410
  interface SubscriptionResult {
341
411
  id: string;
@@ -556,7 +626,14 @@ declare class RecurPaymentForm extends HTMLElement {
556
626
  private setButtonLoading;
557
627
  resetButton(): void;
558
628
  setVerifying(verifying: boolean): void;
559
- private showError;
629
+ /**
630
+ * Show error message in the form
631
+ * @param options - Either a string message or an object with title and message
632
+ */
633
+ showError(options: string | {
634
+ title?: string;
635
+ message: string;
636
+ }): void;
560
637
  private clearError;
561
638
  private updateComplete;
562
639
  getFormData(): {
@@ -794,6 +871,14 @@ interface UseSubscribeOptions {
794
871
  * Callback when an error occurs
795
872
  */
796
873
  onError?: (error: CheckoutError) => void;
874
+ /**
875
+ * Callback when payment fails (after user submits payment)
876
+ *
877
+ * This allows you to customize behavior based on the specific failure reason.
878
+ * Return an action object to override default behavior, or return nothing/undefined
879
+ * to use the SDK's default handling.
880
+ */
881
+ onPaymentFailed?: (error: CheckoutError) => PaymentFailedAction | void | undefined;
797
882
  /**
798
883
  * Callback when payment is cancelled
799
884
  */
@@ -803,11 +888,11 @@ interface UseSubscribeResult {
803
888
  /**
804
889
  * Function to initiate subscription checkout
805
890
  */
806
- mutate: (options: Omit<CheckoutOptions, 'onSuccess' | 'onError' | 'onPaymentComplete' | 'onPaymentCancel'>) => Promise<void>;
891
+ mutate: (options: Omit<CheckoutOptions, 'onSuccess' | 'onError' | 'onPaymentComplete' | 'onPaymentCancel' | 'onPaymentFailed'>) => Promise<void>;
807
892
  /**
808
893
  * Alias for mutate (for better DX)
809
894
  */
810
- subscribe: (options: Omit<CheckoutOptions, 'onSuccess' | 'onError' | 'onPaymentComplete' | 'onPaymentCancel'>) => Promise<void>;
895
+ subscribe: (options: Omit<CheckoutOptions, 'onSuccess' | 'onError' | 'onPaymentComplete' | 'onPaymentCancel' | 'onPaymentFailed'>) => Promise<void>;
811
896
  /**
812
897
  * Whether subscription is in progress
813
898
  */
package/dist/index.d.ts CHANGED
@@ -55,6 +55,7 @@ declare class RecurErrorDisplay extends HTMLElement {
55
55
  connectedCallback(): void;
56
56
  attributeChangedCallback(name: string, oldValue: string, newValue: string): void;
57
57
  private get error();
58
+ private get errorTitle();
58
59
  private get isDismissible();
59
60
  private handleDismiss;
60
61
  private render;
@@ -275,6 +276,30 @@ interface CheckoutOptions {
275
276
  * Callback when checkout fails
276
277
  */
277
278
  onError?: (error: CheckoutError) => void;
279
+ /**
280
+ * Callback when payment fails (after user submits payment)
281
+ *
282
+ * This allows you to customize behavior based on the specific failure reason.
283
+ * Return an action object to override default behavior, or return nothing/undefined
284
+ * to use the SDK's default handling.
285
+ *
286
+ * @example
287
+ * ```typescript
288
+ * onPaymentFailed: (error) => {
289
+ * // Custom handling for insufficient funds
290
+ * if (error.details?.failure_code === 'INSUFFICIENT_FUNDS') {
291
+ * return {
292
+ * action: 'custom',
293
+ * customTitle: '餘額不足',
294
+ * customMessage: '請使用其他付款方式或聯繫客服'
295
+ * };
296
+ * }
297
+ * // Use default behavior for other errors
298
+ * return undefined;
299
+ * }
300
+ * ```
301
+ */
302
+ onPaymentFailed?: (error: CheckoutError) => PaymentFailedAction | void | undefined;
278
303
  /**
279
304
  * Callback when payment is completed
280
305
  * (only works in popup mode)
@@ -323,6 +348,31 @@ interface CheckoutResult {
323
348
  */
324
349
  sdkTokenExpiresAt: string;
325
350
  }
351
+ /**
352
+ * Payment failure codes from payment provider
353
+ * These indicate the specific reason a payment was declined
354
+ */
355
+ type PaymentFailureCode = 'PAYUNI_DECLINED' | 'UNAPPROVED' | 'INSUFFICIENT_FUNDS' | 'CARD_DECLINED' | 'EXPIRED_CARD' | 'NETWORK_ERROR' | 'TIMEOUT' | 'INVALID_CARD' | 'UNKNOWN';
356
+ /**
357
+ * Extended error details for payment failures
358
+ */
359
+ interface PaymentFailureDetails {
360
+ /**
361
+ * Specific failure code from payment provider
362
+ * Use this to show appropriate error messages or take specific actions
363
+ */
364
+ failure_code?: PaymentFailureCode | string;
365
+ /**
366
+ * Raw failure message from payment provider
367
+ */
368
+ failure_message?: string;
369
+ /**
370
+ * Whether the user can retry with the same payment method
371
+ * - true: Temporary error (network, timeout) - retry may succeed
372
+ * - false: Permanent error (card declined, expired) - different payment method needed
373
+ */
374
+ can_retry?: boolean;
375
+ }
326
376
  interface CheckoutError {
327
377
  /**
328
378
  * Error code
@@ -335,7 +385,27 @@ interface CheckoutError {
335
385
  /**
336
386
  * Additional error details
337
387
  */
338
- details?: Record<string, unknown>;
388
+ details?: Record<string, unknown> & PaymentFailureDetails;
389
+ }
390
+ /**
391
+ * Action to take after payment failure
392
+ */
393
+ interface PaymentFailedAction {
394
+ /**
395
+ * What action to take
396
+ * - 'retry': Keep dialog open, allow user to retry (default for can_retry=true)
397
+ * - 'close': Close dialog, trigger onError callback
398
+ * - 'custom': Show custom message in dialog
399
+ */
400
+ action: 'retry' | 'close' | 'custom';
401
+ /**
402
+ * Custom message to display (only used when action is 'custom')
403
+ */
404
+ customMessage?: string;
405
+ /**
406
+ * Custom title to display (only used when action is 'custom')
407
+ */
408
+ customTitle?: string;
339
409
  }
340
410
  interface SubscriptionResult {
341
411
  id: string;
@@ -556,7 +626,14 @@ declare class RecurPaymentForm extends HTMLElement {
556
626
  private setButtonLoading;
557
627
  resetButton(): void;
558
628
  setVerifying(verifying: boolean): void;
559
- private showError;
629
+ /**
630
+ * Show error message in the form
631
+ * @param options - Either a string message or an object with title and message
632
+ */
633
+ showError(options: string | {
634
+ title?: string;
635
+ message: string;
636
+ }): void;
560
637
  private clearError;
561
638
  private updateComplete;
562
639
  getFormData(): {
@@ -794,6 +871,14 @@ interface UseSubscribeOptions {
794
871
  * Callback when an error occurs
795
872
  */
796
873
  onError?: (error: CheckoutError) => void;
874
+ /**
875
+ * Callback when payment fails (after user submits payment)
876
+ *
877
+ * This allows you to customize behavior based on the specific failure reason.
878
+ * Return an action object to override default behavior, or return nothing/undefined
879
+ * to use the SDK's default handling.
880
+ */
881
+ onPaymentFailed?: (error: CheckoutError) => PaymentFailedAction | void | undefined;
797
882
  /**
798
883
  * Callback when payment is cancelled
799
884
  */
@@ -803,11 +888,11 @@ interface UseSubscribeResult {
803
888
  /**
804
889
  * Function to initiate subscription checkout
805
890
  */
806
- mutate: (options: Omit<CheckoutOptions, 'onSuccess' | 'onError' | 'onPaymentComplete' | 'onPaymentCancel'>) => Promise<void>;
891
+ mutate: (options: Omit<CheckoutOptions, 'onSuccess' | 'onError' | 'onPaymentComplete' | 'onPaymentCancel' | 'onPaymentFailed'>) => Promise<void>;
807
892
  /**
808
893
  * Alias for mutate (for better DX)
809
894
  */
810
- subscribe: (options: Omit<CheckoutOptions, 'onSuccess' | 'onError' | 'onPaymentComplete' | 'onPaymentCancel'>) => Promise<void>;
895
+ subscribe: (options: Omit<CheckoutOptions, 'onSuccess' | 'onError' | 'onPaymentComplete' | 'onPaymentCancel' | 'onPaymentFailed'>) => Promise<void>;
811
896
  /**
812
897
  * Whether subscription is in progress
813
898
  */
package/dist/index.js CHANGED
@@ -241,7 +241,7 @@ var init_error_display = __esm({
241
241
  "src/components/base/error-display.ts"() {
242
242
  RecurErrorDisplay = class extends HTMLElement {
243
243
  static get observedAttributes() {
244
- return ["error", "dismissible"];
244
+ return ["error", "error-title", "dismissible"];
245
245
  }
246
246
  constructor() {
247
247
  super();
@@ -258,6 +258,9 @@ var init_error_display = __esm({
258
258
  get error() {
259
259
  return this.getAttribute("error") || "";
260
260
  }
261
+ get errorTitle() {
262
+ return this.getAttribute("error-title") || "";
263
+ }
261
264
  get isDismissible() {
262
265
  return this.getAttribute("dismissible") === "true";
263
266
  }
@@ -302,6 +305,14 @@ var init_error_display = __esm({
302
305
  flex: 1;
303
306
  }
304
307
 
308
+ .recur-sdk__error-title {
309
+ margin: 0 0 4px 0;
310
+ font-size: 14px;
311
+ font-weight: 600;
312
+ color: var(--recur-error-text, #991b1b);
313
+ line-height: 1.4;
314
+ }
315
+
305
316
  .recur-sdk__error-message {
306
317
  margin: 0;
307
318
  font-size: 14px;
@@ -353,6 +364,7 @@ var init_error_display = __esm({
353
364
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
354
365
  </svg>
355
366
  <div class="recur-sdk__error-content">
367
+ ${this.errorTitle ? `<p class="recur-sdk__error-title">${this.errorTitle}</p>` : ""}
356
368
  <p class="recur-sdk__error-message">${this.error}</p>
357
369
  </div>
358
370
  ${this.isDismissible ? `
@@ -1962,7 +1974,13 @@ var init_payment_form = __esm({
1962
1974
  this.setButtonLoading(false);
1963
1975
  }
1964
1976
  }
1965
- showError(message) {
1977
+ /**
1978
+ * Show error message in the form
1979
+ * @param options - Either a string message or an object with title and message
1980
+ */
1981
+ showError(options) {
1982
+ const message = typeof options === "string" ? options : options.message;
1983
+ const title = typeof options === "object" ? options.title : void 0;
1966
1984
  let errorContainer = this.querySelector(".recur-sdk__error-container");
1967
1985
  if (!errorContainer) {
1968
1986
  errorContainer = document.createElement("div");
@@ -1972,6 +1990,9 @@ var init_payment_form = __esm({
1972
1990
  }
1973
1991
  const errorDisplay = document.createElement("recur-error-display");
1974
1992
  errorDisplay.setAttribute("error", message);
1993
+ if (title) {
1994
+ errorDisplay.setAttribute("error-title", title);
1995
+ }
1975
1996
  errorDisplay.setAttribute("dismissible", "true");
1976
1997
  errorContainer.innerHTML = "";
1977
1998
  errorContainer.appendChild(errorDisplay);
@@ -2647,7 +2668,7 @@ function toCamelCase(obj) {
2647
2668
 
2648
2669
  // package.json
2649
2670
  var package_default = {
2650
- version: "0.10.6"};
2671
+ version: "0.10.8"};
2651
2672
  var SDK_VERSION = package_default.version;
2652
2673
  var SDK_TYPE = "react";
2653
2674
  var RecurContext = createContext(null);
@@ -3045,6 +3066,46 @@ function RecurProvider({ children, config: initialConfig = {} }) {
3045
3066
  const rawPaymentResult = await paymentResponse.json();
3046
3067
  const paymentResult = toCamelCase(rawPaymentResult);
3047
3068
  console.log("[Recur SDK] Payment executed:", paymentResult);
3069
+ if (paymentResult.success === false && paymentResult.failure) {
3070
+ console.log("[Recur SDK] Payment failed:", paymentResult.failure);
3071
+ const failureError = {
3072
+ code: "PAYMENT_FAILED",
3073
+ message: paymentResult.failure.message || "\u4ED8\u6B3E\u5931\u6557",
3074
+ details: {
3075
+ failure_code: paymentResult.failure.code,
3076
+ failure_message: paymentResult.failure.message,
3077
+ can_retry: paymentResult.failure.canRetry
3078
+ }
3079
+ };
3080
+ let action;
3081
+ if (options.onPaymentFailed) {
3082
+ action = options.onPaymentFailed(failureError);
3083
+ }
3084
+ if (!action) {
3085
+ action = paymentResult.failure.canRetry ? { action: "retry" } : { action: "retry" };
3086
+ }
3087
+ if (action.action === "close") {
3088
+ options.onError?.(failureError);
3089
+ paymentForm.resetButton?.();
3090
+ closeDialog();
3091
+ setIsCheckingOut(false);
3092
+ return;
3093
+ } else if (action.action === "custom") {
3094
+ paymentForm.showError?.({
3095
+ title: action.customTitle || "\u4ED8\u6B3E\u5931\u6557",
3096
+ message: action.customMessage || failureError.message
3097
+ });
3098
+ paymentForm.resetButton?.();
3099
+ return;
3100
+ } else {
3101
+ paymentForm.showError?.({
3102
+ title: "\u4ED8\u6B3E\u5931\u6557",
3103
+ message: failureError.details?.failure_message || failureError.message
3104
+ });
3105
+ paymentForm.resetButton?.();
3106
+ return;
3107
+ }
3108
+ }
3048
3109
  if (paymentResult.requires3D && paymentResult.redirectUrl) {
3049
3110
  console.log("[Recur SDK] 3D verification required");
3050
3111
  const isMobileOrWebView = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || // Detect common WebView user agents
@@ -3395,7 +3456,7 @@ function useProducts(options = {}) {
3395
3456
  };
3396
3457
  }
3397
3458
  function useSubscribe(options = {}) {
3398
- const { onSuccess, onError, onPaymentComplete, onPaymentCancel } = options;
3459
+ const { onSuccess, onError, onPaymentComplete, onPaymentCancel, onPaymentFailed } = options;
3399
3460
  const { checkout, isCheckingOut } = useRecur();
3400
3461
  const [error, setError] = useState(null);
3401
3462
  const mutate = useCallback(
@@ -3414,6 +3475,9 @@ function useSubscribe(options = {}) {
3414
3475
  setError(err);
3415
3476
  onError?.(err);
3416
3477
  },
3478
+ onPaymentFailed: (err) => {
3479
+ return onPaymentFailed?.(err);
3480
+ },
3417
3481
  onPaymentCancel: () => {
3418
3482
  onPaymentCancel?.();
3419
3483
  }
@@ -3427,7 +3491,7 @@ function useSubscribe(options = {}) {
3427
3491
  onError?.(checkoutError);
3428
3492
  }
3429
3493
  },
3430
- [checkout, onSuccess, onError, onPaymentComplete, onPaymentCancel]
3494
+ [checkout, onSuccess, onError, onPaymentComplete, onPaymentCancel, onPaymentFailed]
3431
3495
  );
3432
3496
  const reset = useCallback(() => {
3433
3497
  setError(null);
package/dist/recur.umd.js CHANGED
@@ -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",oe)});var Le={};C(Le,{RecurErrorDisplay:()=>ie});var ie,Ue=k(()=>{"use strict";ie=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",oe)});var Le={};C(Le,{RecurErrorDisplay:()=>ie});var ie,Ue=k(()=>{"use strict";ie=class extends HTMLElement{static get observedAttributes(){return["error","error-title","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 errorTitle(){return this.getAttribute("error-title")||""}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;
@@ -150,6 +150,14 @@
150
150
  flex: 1;
151
151
  }
152
152
 
153
+ .recur-sdk__error-title {
154
+ margin: 0 0 4px 0;
155
+ font-size: 14px;
156
+ font-weight: 600;
157
+ color: var(--recur-error-text, #991b1b);
158
+ line-height: 1.4;
159
+ }
160
+
153
161
  .recur-sdk__error-message {
154
162
  margin: 0;
155
163
  font-size: 14px;
@@ -201,6 +209,7 @@
201
209
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
202
210
  </svg>
203
211
  <div class="recur-sdk__error-content">
212
+ ${this.errorTitle?`<p class="recur-sdk__error-title">${this.errorTitle}</p>`:""}
204
213
  <p class="recur-sdk__error-message">${this.error}</p>
205
214
  </div>
206
215
  ${this.isDismissible?`
@@ -1722,7 +1731,7 @@
1722
1731
  `):(r.classList.remove("loading"),r.disabled=!1,r.innerHTML='<span id="'+this.containerId+'-submit-text">\u78BA\u8A8D\u4ED8\u6B3E</span>'))}resetButton(){this.setButtonLoading(!1)}setVerifying(t){let r=document.getElementById(`${this.containerId}-submit-btn`);r&&(t?(r.classList.add("loading"),r.disabled=!0,r.innerHTML=`
1723
1732
  <span class="recur-loading-spinner"></span>
1724
1733
  <span>3D \u9A57\u8B49\u4E2D...</span>
1725
- `):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 o=document.createElement("recur-error-display");o.setAttribute("error",t),o.setAttribute("dismissible","true"),r.innerHTML="",r.appendChild(o),o.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",le)});var at={};C(at,{RecurCheckoutButton:()=>de});var de,ct=k(()=>{"use strict";de=class extends HTMLElement{constructor(){super();l(this,"_isLoading",!1);l(this,"handleClick",async t=>{if(t.preventDefault(),this._isLoading||this.hasAttribute("disabled"))return;let r=this.getAttribute("publishable-key"),o=this.getAttribute("product-id"),i=this.getAttribute("success-url"),s=this.getAttribute("cancel-url");if(!r){this.dispatchError("Missing required attribute: publishable-key");return}if(!o){this.dispatchError("Missing required attribute: product-id");return}if(!i){this.dispatchError("Missing required attribute: success-url");return}if(!s){this.dispatchError("Missing required attribute: cancel-url");return}this._isLoading=!0,this.render(),this.setupEventListeners();try{let a=await this.createCheckoutSession({publishableKey:r,productId:o,successUrl:this.resolveUrl(i),cancelUrl:this.resolveUrl(s),customerEmail:this.getAttribute("customer-email")||void 0,mode:this.getAttribute("mode")});this.dispatchEvent(new CustomEvent("checkout-started",{detail:{sessionId:a.id,url:a.url},bubbles:!0,composed:!0})),window.location.href=a.url}catch(a){this._isLoading=!1,this.render(),this.setupEventListeners(),this.dispatchError(a.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,o){r!==o&&this.render()}render(){let t=this.getAttribute("button-text")||this.textContent?.trim()||"\u8A02\u95B1",r=this.getAttribute("button-style")||"primary",o=this.hasAttribute("disabled")||this._isLoading;this.shadowRoot.innerHTML=`
1734
+ `):this.setButtonLoading(!1))}showError(t){let r=typeof t=="string"?t:t.message,o=typeof t=="object"?t.title:void 0,i=this.querySelector(".recur-sdk__error-container");i||(i=document.createElement("div"),i.className="recur-sdk__error-container",i.style.cssText="margin-bottom: 16px;",this.insertBefore(i,this.firstChild));let s=document.createElement("recur-error-display");s.setAttribute("error",r),o&&s.setAttribute("error-title",o),s.setAttribute("dismissible","true"),i.innerHTML="",i.appendChild(s),s.scrollIntoView({behavior:"smooth",block:"center"})}clearError(){let t=this.querySelector(".recur-sdk__error-container");t&&(t.innerHTML="")}updateComplete(){return new Promise(t=>{if(this.isConnected)setTimeout(t,0);else{let r=new MutationObserver(()=>{this.isConnected&&(r.disconnect(),setTimeout(t,0))});r.observe(document.body,{childList:!0,subtree:!0})}})}getFormData(){return{email:document.getElementById(`${this.containerId}-email`)?.value,name:document.getElementById(`${this.containerId}-name`)?.value}}setFormData(t){if(t.email){let r=document.getElementById(`${this.containerId}-email`);r&&(r.value=t.email)}if(t.name){let r=document.getElementById(`${this.containerId}-name`);r&&(r.value=t.name)}}};customElements.get("recur-payment-form")||customElements.define("recur-payment-form",le)});var at={};C(at,{RecurCheckoutButton:()=>de});var de,ct=k(()=>{"use strict";de=class extends HTMLElement{constructor(){super();l(this,"_isLoading",!1);l(this,"handleClick",async t=>{if(t.preventDefault(),this._isLoading||this.hasAttribute("disabled"))return;let r=this.getAttribute("publishable-key"),o=this.getAttribute("product-id"),i=this.getAttribute("success-url"),s=this.getAttribute("cancel-url");if(!r){this.dispatchError("Missing required attribute: publishable-key");return}if(!o){this.dispatchError("Missing required attribute: product-id");return}if(!i){this.dispatchError("Missing required attribute: success-url");return}if(!s){this.dispatchError("Missing required attribute: cancel-url");return}this._isLoading=!0,this.render(),this.setupEventListeners();try{let a=await this.createCheckoutSession({publishableKey:r,productId:o,successUrl:this.resolveUrl(i),cancelUrl:this.resolveUrl(s),customerEmail:this.getAttribute("customer-email")||void 0,mode:this.getAttribute("mode")});this.dispatchEvent(new CustomEvent("checkout-started",{detail:{sessionId:a.id,url:a.url},bubbles:!0,composed:!0})),window.location.href=a.url}catch(a){this._isLoading=!1,this.render(),this.setupEventListeners(),this.dispatchError(a.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,o){r!==o&&this.render()}render(){let t=this.getAttribute("button-text")||this.textContent?.trim()||"\u8A02\u95B1",r=this.getAttribute("button-style")||"primary",o=this.hasAttribute("disabled")||this._isLoading;this.shadowRoot.innerHTML=`
1726
1735
  <style>
1727
1736
  :host {
1728
1737
  display: inline-block;
@@ -1948,7 +1957,7 @@
1948
1957
  <path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/>
1949
1958
  <circle cx="12" cy="7" r="4"/>
1950
1959
  </svg>
1951
- `}setupEventListeners(){let t=this.shadowRoot?.querySelector("button");t&&t.addEventListener("click",this.handleClick)}redirectToPortal(t){let r=this.getAttribute("target");this.dispatchEvent(new CustomEvent("portal-redirect",{detail:{url:t},bubbles:!0,composed:!0})),r==="_blank"?window.open(t,"_blank","noopener,noreferrer"):window.location.href=t}async fetchAndRedirect(t){let r=this.getAttribute("customer-id"),o=this.getAttribute("return-url");this._isLoading=!0,this.render(),this.setupEventListeners();try{let i={};r&&(i.customerId=r),o&&(i.returnUrl=o);let s=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});if(!s.ok){let d=await s.json().catch(()=>({}));throw new Error(d.error?.message||d.message||`HTTP ${s.status}: Failed to create portal session`)}let a=await s.json(),c=a.url||a.portalUrl;if(!c)throw new Error("Portal URL not found in response");this._isLoading=!1,this.render(),this.setupEventListeners(),this.redirectToPortal(c)}catch(i){this._isLoading=!1,this.render(),this.setupEventListeners(),this.dispatchError(i.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",ue)});var zt={};C(zt,{RecurCheckout:()=>Z,RecurElements:()=>N,createElements:()=>Se,default:()=>Nt,init:()=>mt});async function Dt(){if(typeof window>"u"){console.warn("[Recur SDK] Cannot register Web Components: window is not defined");return}await Promise.all([Promise.resolve().then(()=>(De(),Re)),Promise.resolve().then(()=>(Me(),Pe)),Promise.resolve().then(()=>(Ue(),Le)),Promise.resolve().then(()=>(Be(),He)),Promise.resolve().then(()=>(Ze(),Ge)),Promise.resolve().then(()=>(et(),Qe)),Promise.resolve().then(()=>(nt(),st)),Promise.resolve().then(()=>(ct(),at)),Promise.resolve().then(()=>(dt(),lt))]);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"&&Dt();function Pt(n){return n.replace(/_([a-z])/g,(e,t)=>t.toUpperCase())}function w(n){if(n==null)return n;if(Array.isArray(n))return n.map(e=>w(e));if(n instanceof Date)return n;if(typeof n=="object"){let e={};for(let[t,r]of Object.entries(n)){let o=Pt(t);e[o]=w(r)}return e}return n}var ut={name:"recur-tw",version:"0.10.6",description:"React & Vanilla JS SDK for embedding subscription checkout flows (Taiwan / PAYUNi)",type:"module",private:!1,author:"Recur",license:"Elastic-2.0",keywords:["react","vanilla-js","subscription","checkout","payment","payuni","recurring-billing","taiwan","\u7E41\u9AD4\u4E2D\u6587","sdk","embedded-checkout"],homepage:"https://recur.tw/",bugs:{email:"support@recur.tw"},scripts:{build:"tsup",dev:"tsup --watch",lint:"eslint .","lint:fix":"eslint . --fix","type-check":"tsc --noEmit",examples:"npx http-server . -p 8080 -o /examples/"},main:"./dist/index.js",module:"./dist/index.js",types:"./dist/index.d.ts",exports:{".":{types:"./dist/index.d.ts",import:"./dist/index.js",require:"./dist/index.cjs"},"./server":{types:"./dist/server.d.ts",import:"./dist/server.js",require:"./dist/server.cjs"},"./vanilla":"./dist/recur.umd.js","./checkout":"./dist/checkout.js","./widget":"./dist/widget.js","./package.json":"./package.json"},unpkg:"./dist/recur.umd.js",jsdelivr:"./dist/recur.umd.js",files:["dist","README.md","AGENTS.md","LICENSE"],sideEffects:["src/components/**/*.ts","src/components/**/*.js"],peerDependencies:{react:">=18.0.0","react-dom":">=18.0.0"},peerDependenciesMeta:{react:{optional:!0},"react-dom":{optional:!0}},devDependencies:{"@eslint/js":"^9.39.1","@types/node":"^20.19.9","@types/react":"^19.1.9","@types/react-dom":"^19.1.7","@workspace/typescript-config":"workspace:*",autoprefixer:"^10.4.22",eslint:"^9","eslint-plugin-react":"^7.37.5","eslint-plugin-react-hooks":"^7.0.1",postcss:"^8.5.6",react:"^19.2.1","react-dom":"^19.2.1",tailwindcss:"^4.1.11",tsup:"^8.3.5",typescript:"^5.9.2","typescript-eslint":"^8.47.0"},dependencies:{"lit-html":"^3.3.1"}};var Lt=ut.version,Ut="vanilla",pe=class{constructor(e){l(this,"config");if(!e.publishableKey)throw new Error("publishableKey is required");this.config={baseUrl:"https://api.recur.tw",...e}}getHeaders(){return{"Content-Type":"application/json","X-Recur-Publishable-Key":this.config.publishableKey,"X-Recur-SDK-Type":Ut,"X-Recur-SDK-Version":Lt,"X-Recur-Source":typeof window<"u"?window.location.origin:"server"}}async createSubscription(e){let{customerName:t,customerEmail:r}=e,o=e.productId||e.planId,i=e.productSlug;if(!o&&!i)throw new Error("Either productId or productSlug is required");let s={customerName:t,customerEmail:r};o&&(s.productId=o),i&&(s.productSlug=i);let a=await fetch(`${this.config.baseUrl}/v1/subscriptions`,{method:"POST",headers:this.getHeaders(),body:JSON.stringify(s)});if(!a.ok){let d=await a.json().catch(()=>({}));throw{code:d.error||"CHECKOUT_FAILED",message:d.message||"Failed to initiate checkout",details:d}}let c=await a.json();return w(c)}async fetchProducts(e){let t=new URL(`${this.config.baseUrl}/v1/products`);e?.type&&t.searchParams.set("type",e.type);let r=await fetch(t.toString(),{method:"GET",headers:this.getHeaders()});if(!r.ok){let i=await r.json().catch(()=>({}));throw{code:i.error||"FETCH_PRODUCTS_FAILED",message:i.message||"Failed to fetch products",details:i}}let o=await r.json();return w(o)}async fetchPlans(){return{plans:(await this.fetchProducts({type:"SUBSCRIPTION"})).products}}getConfig(){return{...this.config}}};var he=class{constructor(e,t){l(this,"config");l(this,"options");l(this,"container");l(this,"checkoutId",null);l(this,"sdkToken",null);l(this,"sdkTimestamp",null);l(this,"creditToken",null);l(this,"sdkEnv","S");l(this,"payuniSDK",null);l(this,"isFormValid",!1);if(this.config=e,this.options=t,typeof t.container=="string"){let r=document.querySelector(t.container);if(!r)throw new Error(`Container not found: ${t.container}`);this.container=r}else this.container=t.container}async render(){try{await this.initCheckout(),this.renderHTML(),await this.loadPayUniSDK(),await this.initPayUniSDK(),this.setupFormSubmit()}catch(e){this.handleError(e)}}getBaseUrl(){if(this.config.baseUrl)return this.config.baseUrl;if(typeof window<"u"){let e=window.location.hostname;if(e==="localhost"||e.includes(".test")||e.includes(".local")||e==="127.0.0.1")return`${window.location.protocol}//${window.location.host}/api`}return"https://api.recur.tw"}async initCheckout(){let e=this.getBaseUrl(),t=await fetch(`${e}/v1/checkouts`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":this.config.publishableKey},body:JSON.stringify({productId:this.options.productId||this.options.planId,customerEmail:this.options.customerEmail,customerName:this.options.customerName})});if(!t.ok){let i=await t.json().catch(()=>({}));throw new Error(i.error||"Failed to initialize checkout")}let r=await t.json(),o=w(r);this.checkoutId=o.checkout.id,this.sdkToken=o.sdkToken,this.sdkTimestamp=o.sdkTimestamp||null,this.creditToken=o.creditToken||null,this.sdkEnv=this.config.publishableKey.startsWith("pk_test_")?"S":"P"}renderHTML(){this.container.innerHTML=`
1960
+ `}setupEventListeners(){let t=this.shadowRoot?.querySelector("button");t&&t.addEventListener("click",this.handleClick)}redirectToPortal(t){let r=this.getAttribute("target");this.dispatchEvent(new CustomEvent("portal-redirect",{detail:{url:t},bubbles:!0,composed:!0})),r==="_blank"?window.open(t,"_blank","noopener,noreferrer"):window.location.href=t}async fetchAndRedirect(t){let r=this.getAttribute("customer-id"),o=this.getAttribute("return-url");this._isLoading=!0,this.render(),this.setupEventListeners();try{let i={};r&&(i.customerId=r),o&&(i.returnUrl=o);let s=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});if(!s.ok){let d=await s.json().catch(()=>({}));throw new Error(d.error?.message||d.message||`HTTP ${s.status}: Failed to create portal session`)}let a=await s.json(),c=a.url||a.portalUrl;if(!c)throw new Error("Portal URL not found in response");this._isLoading=!1,this.render(),this.setupEventListeners(),this.redirectToPortal(c)}catch(i){this._isLoading=!1,this.render(),this.setupEventListeners(),this.dispatchError(i.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",ue)});var zt={};C(zt,{RecurCheckout:()=>Z,RecurElements:()=>N,createElements:()=>Se,default:()=>Nt,init:()=>mt});async function Dt(){if(typeof window>"u"){console.warn("[Recur SDK] Cannot register Web Components: window is not defined");return}await Promise.all([Promise.resolve().then(()=>(De(),Re)),Promise.resolve().then(()=>(Me(),Pe)),Promise.resolve().then(()=>(Ue(),Le)),Promise.resolve().then(()=>(Be(),He)),Promise.resolve().then(()=>(Ze(),Ge)),Promise.resolve().then(()=>(et(),Qe)),Promise.resolve().then(()=>(nt(),st)),Promise.resolve().then(()=>(ct(),at)),Promise.resolve().then(()=>(dt(),lt))]);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"&&Dt();function Pt(n){return n.replace(/_([a-z])/g,(e,t)=>t.toUpperCase())}function w(n){if(n==null)return n;if(Array.isArray(n))return n.map(e=>w(e));if(n instanceof Date)return n;if(typeof n=="object"){let e={};for(let[t,r]of Object.entries(n)){let o=Pt(t);e[o]=w(r)}return e}return n}var ut={name:"recur-tw",version:"0.10.8",description:"React & Vanilla JS SDK for embedding subscription checkout flows (Taiwan / PAYUNi)",type:"module",private:!1,author:"Recur",license:"Elastic-2.0",keywords:["react","vanilla-js","subscription","checkout","payment","payuni","recurring-billing","taiwan","\u7E41\u9AD4\u4E2D\u6587","sdk","embedded-checkout"],homepage:"https://recur.tw/",bugs:{email:"support@recur.tw"},scripts:{build:"tsup",dev:"tsup --watch",lint:"eslint .","lint:fix":"eslint . --fix","type-check":"tsc --noEmit",examples:"npx http-server . -p 8080 -o /examples/"},main:"./dist/index.js",module:"./dist/index.js",types:"./dist/index.d.ts",exports:{".":{types:"./dist/index.d.ts",import:"./dist/index.js",require:"./dist/index.cjs"},"./server":{types:"./dist/server.d.ts",import:"./dist/server.js",require:"./dist/server.cjs"},"./vanilla":"./dist/recur.umd.js","./checkout":"./dist/checkout.js","./widget":"./dist/widget.js","./package.json":"./package.json"},unpkg:"./dist/recur.umd.js",jsdelivr:"./dist/recur.umd.js",files:["dist","README.md","AGENTS.md","LICENSE"],sideEffects:["src/components/**/*.ts","src/components/**/*.js"],peerDependencies:{react:">=18.0.0","react-dom":">=18.0.0"},peerDependenciesMeta:{react:{optional:!0},"react-dom":{optional:!0}},devDependencies:{"@eslint/js":"^9.39.1","@types/node":"^20.19.9","@types/react":"^19.1.9","@types/react-dom":"^19.1.7","@workspace/typescript-config":"workspace:*",autoprefixer:"^10.4.22",eslint:"^9","eslint-plugin-react":"^7.37.5","eslint-plugin-react-hooks":"^7.0.1",postcss:"^8.5.6",react:"^19.2.1","react-dom":"^19.2.1",tailwindcss:"^4.1.11",tsup:"^8.3.5",typescript:"^5.9.2","typescript-eslint":"^8.47.0"},dependencies:{"lit-html":"^3.3.1"}};var Lt=ut.version,Ut="vanilla",pe=class{constructor(e){l(this,"config");if(!e.publishableKey)throw new Error("publishableKey is required");this.config={baseUrl:"https://api.recur.tw",...e}}getHeaders(){return{"Content-Type":"application/json","X-Recur-Publishable-Key":this.config.publishableKey,"X-Recur-SDK-Type":Ut,"X-Recur-SDK-Version":Lt,"X-Recur-Source":typeof window<"u"?window.location.origin:"server"}}async createSubscription(e){let{customerName:t,customerEmail:r}=e,o=e.productId||e.planId,i=e.productSlug;if(!o&&!i)throw new Error("Either productId or productSlug is required");let s={customerName:t,customerEmail:r};o&&(s.productId=o),i&&(s.productSlug=i);let a=await fetch(`${this.config.baseUrl}/v1/subscriptions`,{method:"POST",headers:this.getHeaders(),body:JSON.stringify(s)});if(!a.ok){let d=await a.json().catch(()=>({}));throw{code:d.error||"CHECKOUT_FAILED",message:d.message||"Failed to initiate checkout",details:d}}let c=await a.json();return w(c)}async fetchProducts(e){let t=new URL(`${this.config.baseUrl}/v1/products`);e?.type&&t.searchParams.set("type",e.type);let r=await fetch(t.toString(),{method:"GET",headers:this.getHeaders()});if(!r.ok){let i=await r.json().catch(()=>({}));throw{code:i.error||"FETCH_PRODUCTS_FAILED",message:i.message||"Failed to fetch products",details:i}}let o=await r.json();return w(o)}async fetchPlans(){return{plans:(await this.fetchProducts({type:"SUBSCRIPTION"})).products}}getConfig(){return{...this.config}}};var he=class{constructor(e,t){l(this,"config");l(this,"options");l(this,"container");l(this,"checkoutId",null);l(this,"sdkToken",null);l(this,"sdkTimestamp",null);l(this,"creditToken",null);l(this,"sdkEnv","S");l(this,"payuniSDK",null);l(this,"isFormValid",!1);if(this.config=e,this.options=t,typeof t.container=="string"){let r=document.querySelector(t.container);if(!r)throw new Error(`Container not found: ${t.container}`);this.container=r}else this.container=t.container}async render(){try{await this.initCheckout(),this.renderHTML(),await this.loadPayUniSDK(),await this.initPayUniSDK(),this.setupFormSubmit()}catch(e){this.handleError(e)}}getBaseUrl(){if(this.config.baseUrl)return this.config.baseUrl;if(typeof window<"u"){let e=window.location.hostname;if(e==="localhost"||e.includes(".test")||e.includes(".local")||e==="127.0.0.1")return`${window.location.protocol}//${window.location.host}/api`}return"https://api.recur.tw"}async initCheckout(){let e=this.getBaseUrl(),t=await fetch(`${e}/v1/checkouts`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":this.config.publishableKey},body:JSON.stringify({productId:this.options.productId||this.options.planId,customerEmail:this.options.customerEmail,customerName:this.options.customerName})});if(!t.ok){let i=await t.json().catch(()=>({}));throw new Error(i.error||"Failed to initialize checkout")}let r=await t.json(),o=w(r);this.checkoutId=o.checkout.id,this.sdkToken=o.sdkToken,this.sdkTimestamp=o.sdkTimestamp||null,this.creditToken=o.creditToken||null,this.sdkEnv=this.config.publishableKey.startsWith("pk_test_")?"S":"P"}renderHTML(){this.container.innerHTML=`
1952
1961
  <div class="recur-embedded-checkout" style="max-width: 400px; margin: 0 auto; padding: 20px; font-family: system-ui, -apple-system, sans-serif;">
1953
1962
  <div class="recur-checkout-header" style="margin-bottom: 24px;">
1954
1963
  <h2 style="margin: 0 0 8px 0; font-size: 20px; font-weight: 600; color: #111;">Subscribe</h2>
package/dist/server.cjs CHANGED
@@ -20,7 +20,7 @@ var RecurAPIError = class extends Error {
20
20
 
21
21
  // package.json
22
22
  var package_default = {
23
- version: "0.10.6"};
23
+ version: "0.10.8"};
24
24
 
25
25
  // src/server/resources/portal.ts
26
26
  var SDK_VERSION = package_default.version;
package/dist/server.js CHANGED
@@ -18,7 +18,7 @@ var RecurAPIError = class extends Error {
18
18
 
19
19
  // package.json
20
20
  var package_default = {
21
- version: "0.10.6"};
21
+ version: "0.10.8"};
22
22
 
23
23
  // src/server/resources/portal.ts
24
24
  var SDK_VERSION = package_default.version;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "recur-tw",
3
- "version": "0.10.7",
3
+ "version": "0.10.8",
4
4
  "description": "React & Vanilla JS SDK for embedding subscription checkout flows (Taiwan / PAYUNi)",
5
5
  "type": "module",
6
6
  "private": false,