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