recur-tw 0.5.0 β 0.7.0
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/README.md +84 -49
- package/dist/index.cjs +27 -14
- package/dist/index.d.cts +52 -24
- package/dist/index.d.ts +52 -24
- package/dist/index.js +26 -14
- package/dist/recur.umd.js +7 -7
- package/dist/server.cjs +117 -0
- package/dist/server.d.cts +157 -0
- package/dist/server.d.ts +157 -0
- package/dist/server.js +114 -0
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Taiwan-specific subscription checkout SDK with embedded payment forms powered by
|
|
|
9
9
|
## π Features
|
|
10
10
|
|
|
11
11
|
- β
**Hosted Checkout** - Zero-backend integration with `<recur-checkout>` Web Component
|
|
12
|
-
- β
**React SDK** - Full React integration with hooks (`
|
|
12
|
+
- β
**React SDK** - Full React integration with hooks (`useProducts`, `useRecur`)
|
|
13
13
|
- β
**Vanilla JS** - Use with plain HTML/JavaScript (no framework required)
|
|
14
14
|
- β
**Embedded Checkout** - Native payment forms with PAYUNi credit card fields
|
|
15
15
|
- β
**Web Components** - Modern, encapsulated UI components
|
|
@@ -53,7 +53,7 @@ Full framework integration with React hooks and embedded checkout:
|
|
|
53
53
|
```tsx
|
|
54
54
|
'use client';
|
|
55
55
|
|
|
56
|
-
import { RecurProvider,
|
|
56
|
+
import { RecurProvider, useProducts, useRecur } from 'recur-tw';
|
|
57
57
|
|
|
58
58
|
// 1. Wrap your app with RecurProvider
|
|
59
59
|
export default function App() {
|
|
@@ -64,31 +64,33 @@ export default function App() {
|
|
|
64
64
|
containerElementId: 'recur-payment-container' // For embedded checkout
|
|
65
65
|
}}
|
|
66
66
|
>
|
|
67
|
-
<
|
|
67
|
+
<ProductsPage />
|
|
68
68
|
</RecurProvider>
|
|
69
69
|
);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
// 2. Fetch
|
|
73
|
-
function
|
|
74
|
-
|
|
72
|
+
// 2. Fetch products and checkout
|
|
73
|
+
function ProductsPage() {
|
|
74
|
+
// Fetch all products (or filter by type: 'SUBSCRIPTION', 'ONE_TIME', etc.)
|
|
75
|
+
const { data: products, isLoading } = useProducts();
|
|
75
76
|
const { checkout, isCheckingOut } = useRecur();
|
|
76
77
|
|
|
77
|
-
if (isLoading) return <div>Loading
|
|
78
|
+
if (isLoading) return <div>Loading products...</div>;
|
|
78
79
|
|
|
79
80
|
return (
|
|
80
81
|
<div>
|
|
81
|
-
{
|
|
82
|
+
{products?.map((product) => (
|
|
82
83
|
<button
|
|
83
|
-
key={
|
|
84
|
+
key={product.id}
|
|
84
85
|
onClick={() => checkout({
|
|
85
|
-
planId:
|
|
86
|
+
planId: product.id,
|
|
86
87
|
customerEmail: 'user@example.com',
|
|
87
88
|
customerName: 'John Doe',
|
|
88
89
|
})}
|
|
89
90
|
disabled={isCheckingOut}
|
|
90
91
|
>
|
|
91
|
-
|
|
92
|
+
{/* product.type: 'SUBSCRIPTION' | 'ONE_TIME' | 'CREDITS' | 'DONATION' */}
|
|
93
|
+
{product.type === 'SUBSCRIPTION' ? 'Subscribe to' : 'Buy'} {product.name} - NT${product.price}
|
|
92
94
|
</button>
|
|
93
95
|
))}
|
|
94
96
|
|
|
@@ -301,26 +303,34 @@ document.querySelector('recur-checkout').addEventListener('checkout-error', (e)
|
|
|
301
303
|
|
|
302
304
|
### React Hooks
|
|
303
305
|
|
|
304
|
-
#### `
|
|
306
|
+
#### `useProducts()`
|
|
305
307
|
|
|
306
|
-
Fetch available
|
|
308
|
+
Fetch available products with optional type filtering:
|
|
307
309
|
|
|
308
310
|
```tsx
|
|
309
|
-
import {
|
|
311
|
+
import { useProducts } from 'recur-tw';
|
|
310
312
|
|
|
311
|
-
function
|
|
312
|
-
|
|
313
|
+
function ProductsPage() {
|
|
314
|
+
// Fetch all products
|
|
315
|
+
const { data: products, isLoading, error } = useProducts();
|
|
316
|
+
|
|
317
|
+
// Or filter by type
|
|
318
|
+
// const { data: subscriptions } = useProducts({ type: 'SUBSCRIPTION' });
|
|
319
|
+
// const { data: oneTimeProducts } = useProducts({ type: 'ONE_TIME' });
|
|
313
320
|
|
|
314
321
|
if (isLoading) return <div>Loading...</div>;
|
|
315
322
|
if (error) return <div>Error: {error.message}</div>;
|
|
316
323
|
|
|
317
324
|
return (
|
|
318
325
|
<div>
|
|
319
|
-
{
|
|
320
|
-
<div key={
|
|
321
|
-
<
|
|
322
|
-
|
|
323
|
-
|
|
326
|
+
{products?.map((product) => (
|
|
327
|
+
<div key={product.id}>
|
|
328
|
+
<span className="badge">
|
|
329
|
+
{product.type === 'SUBSCRIPTION' ? 'θ¨ι±' : 'δΈζ¬‘ζ§'}
|
|
330
|
+
</span>
|
|
331
|
+
<h3>{product.name}</h3>
|
|
332
|
+
<p>NT${product.price} {product.billingPeriod && `/ ${product.billingPeriod}`}</p>
|
|
333
|
+
{product.trialDays && product.trialDays > 0 && <p>π {product.trialDays} days free trial</p>}
|
|
324
334
|
</div>
|
|
325
335
|
))}
|
|
326
336
|
</div>
|
|
@@ -328,6 +338,12 @@ function PlansPage() {
|
|
|
328
338
|
}
|
|
329
339
|
```
|
|
330
340
|
|
|
341
|
+
**Product Types:**
|
|
342
|
+
- `SUBSCRIPTION` - Recurring subscription products
|
|
343
|
+
- `ONE_TIME` - One-time purchase products
|
|
344
|
+
- `CREDITS` - Credit/token packages
|
|
345
|
+
- `DONATION` - Donation products
|
|
346
|
+
|
|
331
347
|
#### `useRecur()`
|
|
332
348
|
|
|
333
349
|
Access checkout functionality:
|
|
@@ -354,7 +370,8 @@ function CheckoutButton({ planId }: { planId: string }) {
|
|
|
354
370
|
- `isCheckingOut` - Boolean indicating if checkout is in progress
|
|
355
371
|
- `config` - Current SDK configuration
|
|
356
372
|
- `updateConfig(newConfig)` - Update configuration dynamically
|
|
357
|
-
- `
|
|
373
|
+
- `fetchProducts(options)` - Manually fetch products (optionally filter by type)
|
|
374
|
+
- `fetchPlans()` - Fetch subscription products only (backward compat)
|
|
358
375
|
|
|
359
376
|
---
|
|
360
377
|
|
|
@@ -581,24 +598,25 @@ export function CheckoutForm({ planId }: { planId: string }) {
|
|
|
581
598
|
}
|
|
582
599
|
```
|
|
583
600
|
|
|
584
|
-
#### Dynamic
|
|
601
|
+
#### Dynamic Product Selection
|
|
585
602
|
|
|
586
603
|
```tsx
|
|
587
604
|
'use client';
|
|
588
605
|
|
|
589
606
|
import { useState } from 'react';
|
|
590
|
-
import {
|
|
607
|
+
import { useProducts, useRecur } from 'recur-tw';
|
|
591
608
|
|
|
592
609
|
export function PricingTable() {
|
|
593
|
-
|
|
610
|
+
// Filter to show only subscription products
|
|
611
|
+
const { data: products } = useProducts({ type: 'SUBSCRIPTION' });
|
|
594
612
|
const { checkout, isCheckingOut } = useRecur();
|
|
595
|
-
const [
|
|
613
|
+
const [selectedProduct, setSelectedProduct] = useState<string | null>(null);
|
|
596
614
|
|
|
597
|
-
const
|
|
598
|
-
|
|
615
|
+
const handleSelectProduct = async (productId: string) => {
|
|
616
|
+
setSelectedProduct(productId);
|
|
599
617
|
|
|
600
618
|
await checkout({
|
|
601
|
-
planId,
|
|
619
|
+
planId: productId,
|
|
602
620
|
customerEmail: 'user@example.com',
|
|
603
621
|
customerName: 'John Doe',
|
|
604
622
|
});
|
|
@@ -606,18 +624,18 @@ export function PricingTable() {
|
|
|
606
624
|
|
|
607
625
|
return (
|
|
608
626
|
<div>
|
|
609
|
-
{!
|
|
610
|
-
//
|
|
611
|
-
<div className="
|
|
612
|
-
{
|
|
613
|
-
<div key={
|
|
614
|
-
<h3>{
|
|
615
|
-
<p className="price">NT${
|
|
616
|
-
{
|
|
617
|
-
<p className="trial">π {
|
|
627
|
+
{!selectedProduct ? (
|
|
628
|
+
// Product selection
|
|
629
|
+
<div className="products-grid">
|
|
630
|
+
{products?.map((product) => (
|
|
631
|
+
<div key={product.id} className="product-card">
|
|
632
|
+
<h3>{product.name}</h3>
|
|
633
|
+
<p className="price">NT${product.price} / {product.billingPeriod}</p>
|
|
634
|
+
{product.trialDays && product.trialDays > 0 && (
|
|
635
|
+
<p className="trial">π {product.trialDays} day free trial</p>
|
|
618
636
|
)}
|
|
619
|
-
<button onClick={() =>
|
|
620
|
-
Select
|
|
637
|
+
<button onClick={() => handleSelectProduct(product.id)}>
|
|
638
|
+
Select Product
|
|
621
639
|
</button>
|
|
622
640
|
</div>
|
|
623
641
|
))}
|
|
@@ -625,7 +643,7 @@ export function PricingTable() {
|
|
|
625
643
|
) : (
|
|
626
644
|
// Payment form
|
|
627
645
|
<div>
|
|
628
|
-
<button onClick={() =>
|
|
646
|
+
<button onClick={() => setSelectedProduct(null)}>β Back to products</button>
|
|
629
647
|
<div id="recur-payment-container"></div>
|
|
630
648
|
</div>
|
|
631
649
|
)}
|
|
@@ -700,26 +718,43 @@ import type {
|
|
|
700
718
|
CheckoutOptions,
|
|
701
719
|
CheckoutResult,
|
|
702
720
|
CheckoutError,
|
|
703
|
-
|
|
704
|
-
|
|
721
|
+
Product,
|
|
722
|
+
ProductsResult,
|
|
723
|
+
FetchProductsOptions,
|
|
705
724
|
Subscription,
|
|
706
725
|
SubscriptionResult,
|
|
726
|
+
// Backward compatibility aliases
|
|
727
|
+
Plan, // = Product
|
|
728
|
+
PlansResult, // = ProductsResult
|
|
707
729
|
} from 'recur-tw';
|
|
708
730
|
```
|
|
709
731
|
|
|
710
732
|
### Type Definitions
|
|
711
733
|
|
|
712
734
|
```tsx
|
|
713
|
-
interface
|
|
735
|
+
interface Product {
|
|
714
736
|
id: string;
|
|
715
737
|
name: string;
|
|
738
|
+
slug: string;
|
|
739
|
+
description: string | null;
|
|
740
|
+
// Product type distinguishes between recurring and one-time purchases
|
|
741
|
+
type: 'SUBSCRIPTION' | 'ONE_TIME' | 'CREDITS' | 'DONATION';
|
|
742
|
+
billingPeriod: 'WEEKLY' | 'MONTHLY' | 'YEARLY' | 'ONE_TIME' | 'CUSTOM' | null;
|
|
716
743
|
price: number;
|
|
717
|
-
|
|
718
|
-
trialDays: number;
|
|
719
|
-
|
|
720
|
-
|
|
744
|
+
currency: string;
|
|
745
|
+
trialDays: number | null;
|
|
746
|
+
metadata: ProductMetadata | null;
|
|
747
|
+
productFamily?: string | null;
|
|
748
|
+
displayOrder: number;
|
|
721
749
|
}
|
|
722
750
|
|
|
751
|
+
interface FetchProductsOptions {
|
|
752
|
+
type?: 'SUBSCRIPTION' | 'ONE_TIME' | 'CREDITS' | 'DONATION';
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// Backward compatibility
|
|
756
|
+
type Plan = Product;
|
|
757
|
+
|
|
723
758
|
interface Subscription {
|
|
724
759
|
id: string;
|
|
725
760
|
status: string;
|
|
@@ -819,7 +854,7 @@ POST /v1/checkouts/:id/pay β ε·θ‘δ»ζ¬Ύ
|
|
|
819
854
|
**Breaking Changes:**
|
|
820
855
|
- `organizationId` β `publishableKey` in config
|
|
821
856
|
- Checkout now uses Web Components instead of innerHTML
|
|
822
|
-
- New `
|
|
857
|
+
- New `useProducts()` hook for fetching products (replaces `usePlans()`)
|
|
823
858
|
- Embedded mode is now the default when `containerElementId` is set
|
|
824
859
|
|
|
825
860
|
**Migration:**
|
package/dist/index.cjs
CHANGED
|
@@ -2695,13 +2695,17 @@ function RecurProvider({ children, config: initialConfig = {} }) {
|
|
|
2695
2695
|
},
|
|
2696
2696
|
[config]
|
|
2697
2697
|
);
|
|
2698
|
-
const
|
|
2699
|
-
async () => {
|
|
2698
|
+
const fetchProducts = React.useCallback(
|
|
2699
|
+
async (options) => {
|
|
2700
2700
|
if (!config.publishableKey) {
|
|
2701
2701
|
throw new Error("publishableKey is required");
|
|
2702
2702
|
}
|
|
2703
2703
|
const baseUrl = config.baseUrl || "https://api.recur.tw";
|
|
2704
|
-
const
|
|
2704
|
+
const url = new URL(`${baseUrl}/v1/products`);
|
|
2705
|
+
if (options?.type) {
|
|
2706
|
+
url.searchParams.set("type", options.type);
|
|
2707
|
+
}
|
|
2708
|
+
const response = await fetch(url.toString(), {
|
|
2705
2709
|
method: "GET",
|
|
2706
2710
|
headers: {
|
|
2707
2711
|
"Content-Type": "application/json",
|
|
@@ -2710,21 +2714,29 @@ function RecurProvider({ children, config: initialConfig = {} }) {
|
|
|
2710
2714
|
});
|
|
2711
2715
|
if (!response.ok) {
|
|
2712
2716
|
const errorData = await response.json().catch(() => ({}));
|
|
2713
|
-
throw new Error(errorData.error || "Failed to fetch
|
|
2717
|
+
throw new Error(errorData.error || "Failed to fetch products");
|
|
2714
2718
|
}
|
|
2715
2719
|
return await response.json();
|
|
2716
2720
|
},
|
|
2717
2721
|
[config]
|
|
2718
2722
|
);
|
|
2723
|
+
const fetchPlans = React.useCallback(
|
|
2724
|
+
async () => {
|
|
2725
|
+
const result = await fetchProducts({ type: "SUBSCRIPTION" });
|
|
2726
|
+
return { plans: result.products };
|
|
2727
|
+
},
|
|
2728
|
+
[fetchProducts]
|
|
2729
|
+
);
|
|
2719
2730
|
const value = React.useMemo(
|
|
2720
2731
|
() => ({
|
|
2721
2732
|
config,
|
|
2722
2733
|
checkout,
|
|
2734
|
+
fetchProducts,
|
|
2723
2735
|
fetchPlans,
|
|
2724
2736
|
isCheckingOut,
|
|
2725
2737
|
updateConfig
|
|
2726
2738
|
}),
|
|
2727
|
-
[config, checkout, fetchPlans, isCheckingOut, updateConfig]
|
|
2739
|
+
[config, checkout, fetchProducts, fetchPlans, isCheckingOut, updateConfig]
|
|
2728
2740
|
);
|
|
2729
2741
|
return /* @__PURE__ */ jsxRuntime.jsx(RecurContext.Provider, { value, children });
|
|
2730
2742
|
}
|
|
@@ -2735,9 +2747,9 @@ function useRecur() {
|
|
|
2735
2747
|
}
|
|
2736
2748
|
return context;
|
|
2737
2749
|
}
|
|
2738
|
-
function
|
|
2739
|
-
const { enabled = true, onSuccess, onError } = options;
|
|
2740
|
-
const {
|
|
2750
|
+
function useProducts(options = {}) {
|
|
2751
|
+
const { enabled = true, type, onSuccess, onError } = options;
|
|
2752
|
+
const { fetchProducts } = useRecur();
|
|
2741
2753
|
const [data, setData] = React.useState(void 0);
|
|
2742
2754
|
const [isLoading2, setIsLoading] = React.useState(false);
|
|
2743
2755
|
const [error, setError] = React.useState(null);
|
|
@@ -2746,17 +2758,17 @@ function usePlans(options = {}) {
|
|
|
2746
2758
|
setIsLoading(true);
|
|
2747
2759
|
setError(null);
|
|
2748
2760
|
try {
|
|
2749
|
-
const result = await
|
|
2750
|
-
setData(result.
|
|
2751
|
-
onSuccess?.(result.
|
|
2761
|
+
const result = await fetchProducts({ type });
|
|
2762
|
+
setData(result.products);
|
|
2763
|
+
onSuccess?.(result.products);
|
|
2752
2764
|
} catch (err) {
|
|
2753
|
-
const error2 = err instanceof Error ? err : new Error("Failed to fetch
|
|
2765
|
+
const error2 = err instanceof Error ? err : new Error("Failed to fetch products");
|
|
2754
2766
|
setError(error2);
|
|
2755
2767
|
onError?.(error2);
|
|
2756
2768
|
} finally {
|
|
2757
2769
|
setIsLoading(false);
|
|
2758
2770
|
}
|
|
2759
|
-
}, [enabled,
|
|
2771
|
+
}, [enabled, type, fetchProducts, onSuccess, onError]);
|
|
2760
2772
|
React.useEffect(() => {
|
|
2761
2773
|
fetchData();
|
|
2762
2774
|
}, [fetchData]);
|
|
@@ -2815,6 +2827,7 @@ function useSubscribe(options = {}) {
|
|
|
2815
2827
|
}
|
|
2816
2828
|
|
|
2817
2829
|
exports.RecurProvider = RecurProvider;
|
|
2818
|
-
exports.usePlans =
|
|
2830
|
+
exports.usePlans = useProducts;
|
|
2831
|
+
exports.useProducts = useProducts;
|
|
2819
2832
|
exports.useRecur = useRecur;
|
|
2820
2833
|
exports.useSubscribe = useSubscribe;
|
package/dist/index.d.cts
CHANGED
|
@@ -325,24 +325,34 @@ interface ProductMetadata {
|
|
|
325
325
|
/** Whether this is the primary/default variant */
|
|
326
326
|
isMainVariant?: boolean;
|
|
327
327
|
}
|
|
328
|
-
interface
|
|
328
|
+
interface Product {
|
|
329
329
|
id: string;
|
|
330
330
|
name: string;
|
|
331
331
|
slug: string;
|
|
332
332
|
description: string | null;
|
|
333
|
-
|
|
333
|
+
type: 'SUBSCRIPTION' | 'ONE_TIME' | 'CREDITS' | 'DONATION';
|
|
334
|
+
billingPeriod: 'WEEKLY' | 'MONTHLY' | 'YEARLY' | 'ONE_TIME' | 'CUSTOM' | null;
|
|
334
335
|
price: number;
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
features: Record<string, unknown> | null;
|
|
336
|
+
currency: string;
|
|
337
|
+
trialDays: number | null;
|
|
338
338
|
/** System metadata for grouping and organization */
|
|
339
339
|
metadata: ProductMetadata | null;
|
|
340
340
|
/** Convenience field: extracted from metadata.productFamily for easier access */
|
|
341
341
|
productFamily?: string | null;
|
|
342
342
|
displayOrder: number;
|
|
343
343
|
}
|
|
344
|
+
interface ProductsResult {
|
|
345
|
+
products: Product[];
|
|
346
|
+
}
|
|
347
|
+
type Plan = Product;
|
|
344
348
|
interface PlansResult {
|
|
345
|
-
plans:
|
|
349
|
+
plans: Product[];
|
|
350
|
+
}
|
|
351
|
+
interface FetchProductsOptions {
|
|
352
|
+
/**
|
|
353
|
+
* Filter by product type
|
|
354
|
+
*/
|
|
355
|
+
type?: 'SUBSCRIPTION' | 'ONE_TIME' | 'CREDITS' | 'DONATION';
|
|
346
356
|
}
|
|
347
357
|
interface RecurContextValue {
|
|
348
358
|
/**
|
|
@@ -354,7 +364,12 @@ interface RecurContextValue {
|
|
|
354
364
|
*/
|
|
355
365
|
checkout: (options: CheckoutOptions) => Promise<void>;
|
|
356
366
|
/**
|
|
357
|
-
* Fetch available
|
|
367
|
+
* Fetch available products
|
|
368
|
+
*/
|
|
369
|
+
fetchProducts: (options?: FetchProductsOptions) => Promise<ProductsResult>;
|
|
370
|
+
/**
|
|
371
|
+
* @deprecated Use fetchProducts instead
|
|
372
|
+
* Fetch available subscription plans (backward compatibility)
|
|
358
373
|
*/
|
|
359
374
|
fetchPlans: () => Promise<PlansResult>;
|
|
360
375
|
/**
|
|
@@ -558,28 +573,32 @@ declare function RecurProvider({ children, config: initialConfig }: RecurProvide
|
|
|
558
573
|
*/
|
|
559
574
|
declare function useRecur(): RecurContextValue;
|
|
560
575
|
|
|
561
|
-
interface
|
|
576
|
+
interface UseProductsOptions {
|
|
562
577
|
/**
|
|
563
|
-
* Whether to fetch
|
|
578
|
+
* Whether to fetch products immediately on mount
|
|
564
579
|
* @default true
|
|
565
580
|
*/
|
|
566
581
|
enabled?: boolean;
|
|
567
582
|
/**
|
|
568
|
-
*
|
|
583
|
+
* Filter by product type
|
|
569
584
|
*/
|
|
570
|
-
|
|
585
|
+
type?: 'SUBSCRIPTION' | 'ONE_TIME' | 'CREDITS' | 'DONATION';
|
|
586
|
+
/**
|
|
587
|
+
* Callback when products are fetched successfully
|
|
588
|
+
*/
|
|
589
|
+
onSuccess?: (products: Product[]) => void;
|
|
571
590
|
/**
|
|
572
591
|
* Callback when an error occurs
|
|
573
592
|
*/
|
|
574
593
|
onError?: (error: Error) => void;
|
|
575
594
|
}
|
|
576
|
-
interface
|
|
595
|
+
interface UseProductsResult {
|
|
577
596
|
/**
|
|
578
|
-
* The fetched
|
|
597
|
+
* The fetched products data
|
|
579
598
|
*/
|
|
580
|
-
data:
|
|
599
|
+
data: Product[] | undefined;
|
|
581
600
|
/**
|
|
582
|
-
* Whether the
|
|
601
|
+
* Whether the products are currently being fetched
|
|
583
602
|
*/
|
|
584
603
|
isLoading: boolean;
|
|
585
604
|
/**
|
|
@@ -592,29 +611,38 @@ interface UsePlansResult {
|
|
|
592
611
|
refetch: () => Promise<void>;
|
|
593
612
|
}
|
|
594
613
|
/**
|
|
595
|
-
*
|
|
614
|
+
* useProducts Hook
|
|
596
615
|
*
|
|
597
|
-
* Fetch available
|
|
616
|
+
* Fetch available products with automatic state management
|
|
598
617
|
*
|
|
599
618
|
* @example
|
|
600
619
|
* ```tsx
|
|
601
|
-
* function
|
|
602
|
-
* const { data:
|
|
620
|
+
* function ProductsPage() {
|
|
621
|
+
* const { data: products, isLoading, error } = useProducts();
|
|
603
622
|
*
|
|
604
|
-
* if (isLoading) return <
|
|
623
|
+
* if (isLoading) return <ProductsSkeletonLoader />;
|
|
605
624
|
* if (error) return <ErrorMessage error={error} />;
|
|
606
625
|
*
|
|
607
626
|
* return (
|
|
608
627
|
* <div>
|
|
609
|
-
* {
|
|
610
|
-
* <
|
|
628
|
+
* {products?.map(product => (
|
|
629
|
+
* <ProductCard key={product.id} product={product} />
|
|
611
630
|
* ))}
|
|
612
631
|
* </div>
|
|
613
632
|
* );
|
|
614
633
|
* }
|
|
615
634
|
* ```
|
|
635
|
+
*
|
|
636
|
+
* @example Filter by type
|
|
637
|
+
* ```tsx
|
|
638
|
+
* // Only fetch subscription products
|
|
639
|
+
* const { data: subscriptions } = useProducts({ type: 'SUBSCRIPTION' });
|
|
640
|
+
*
|
|
641
|
+
* // Only fetch one-time products
|
|
642
|
+
* const { data: oneTimeProducts } = useProducts({ type: 'ONE_TIME' });
|
|
643
|
+
* ```
|
|
616
644
|
*/
|
|
617
|
-
declare function
|
|
645
|
+
declare function useProducts(options?: UseProductsOptions): UseProductsResult;
|
|
618
646
|
|
|
619
647
|
interface UseSubscribeOptions {
|
|
620
648
|
/**
|
|
@@ -696,4 +724,4 @@ interface UseSubscribeResult {
|
|
|
696
724
|
*/
|
|
697
725
|
declare function useSubscribe(options?: UseSubscribeOptions): UseSubscribeResult;
|
|
698
726
|
|
|
699
|
-
export { type CheckoutError, type CheckoutOptions, type CheckoutResult, type Plan, type PlansResult, type ProductMetadata, type RecurConfig, type RecurContextValue, RecurProvider, type RecurProviderProps, type SubscriptionResult, type UsePlansOptions, type UsePlansResult, type UseSubscribeOptions, type UseSubscribeResult, usePlans, useRecur, useSubscribe };
|
|
727
|
+
export { type CheckoutError, type CheckoutOptions, type CheckoutResult, type Plan, type PlansResult, type Product, type ProductMetadata, type ProductsResult, type RecurConfig, type RecurContextValue, RecurProvider, type RecurProviderProps, type SubscriptionResult, type UseProductsOptions as UsePlansOptions, type UseProductsResult as UsePlansResult, type UseProductsOptions, type UseProductsResult, type UseSubscribeOptions, type UseSubscribeResult, useProducts as usePlans, useProducts, useRecur, useSubscribe };
|
package/dist/index.d.ts
CHANGED
|
@@ -325,24 +325,34 @@ interface ProductMetadata {
|
|
|
325
325
|
/** Whether this is the primary/default variant */
|
|
326
326
|
isMainVariant?: boolean;
|
|
327
327
|
}
|
|
328
|
-
interface
|
|
328
|
+
interface Product {
|
|
329
329
|
id: string;
|
|
330
330
|
name: string;
|
|
331
331
|
slug: string;
|
|
332
332
|
description: string | null;
|
|
333
|
-
|
|
333
|
+
type: 'SUBSCRIPTION' | 'ONE_TIME' | 'CREDITS' | 'DONATION';
|
|
334
|
+
billingPeriod: 'WEEKLY' | 'MONTHLY' | 'YEARLY' | 'ONE_TIME' | 'CUSTOM' | null;
|
|
334
335
|
price: number;
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
features: Record<string, unknown> | null;
|
|
336
|
+
currency: string;
|
|
337
|
+
trialDays: number | null;
|
|
338
338
|
/** System metadata for grouping and organization */
|
|
339
339
|
metadata: ProductMetadata | null;
|
|
340
340
|
/** Convenience field: extracted from metadata.productFamily for easier access */
|
|
341
341
|
productFamily?: string | null;
|
|
342
342
|
displayOrder: number;
|
|
343
343
|
}
|
|
344
|
+
interface ProductsResult {
|
|
345
|
+
products: Product[];
|
|
346
|
+
}
|
|
347
|
+
type Plan = Product;
|
|
344
348
|
interface PlansResult {
|
|
345
|
-
plans:
|
|
349
|
+
plans: Product[];
|
|
350
|
+
}
|
|
351
|
+
interface FetchProductsOptions {
|
|
352
|
+
/**
|
|
353
|
+
* Filter by product type
|
|
354
|
+
*/
|
|
355
|
+
type?: 'SUBSCRIPTION' | 'ONE_TIME' | 'CREDITS' | 'DONATION';
|
|
346
356
|
}
|
|
347
357
|
interface RecurContextValue {
|
|
348
358
|
/**
|
|
@@ -354,7 +364,12 @@ interface RecurContextValue {
|
|
|
354
364
|
*/
|
|
355
365
|
checkout: (options: CheckoutOptions) => Promise<void>;
|
|
356
366
|
/**
|
|
357
|
-
* Fetch available
|
|
367
|
+
* Fetch available products
|
|
368
|
+
*/
|
|
369
|
+
fetchProducts: (options?: FetchProductsOptions) => Promise<ProductsResult>;
|
|
370
|
+
/**
|
|
371
|
+
* @deprecated Use fetchProducts instead
|
|
372
|
+
* Fetch available subscription plans (backward compatibility)
|
|
358
373
|
*/
|
|
359
374
|
fetchPlans: () => Promise<PlansResult>;
|
|
360
375
|
/**
|
|
@@ -558,28 +573,32 @@ declare function RecurProvider({ children, config: initialConfig }: RecurProvide
|
|
|
558
573
|
*/
|
|
559
574
|
declare function useRecur(): RecurContextValue;
|
|
560
575
|
|
|
561
|
-
interface
|
|
576
|
+
interface UseProductsOptions {
|
|
562
577
|
/**
|
|
563
|
-
* Whether to fetch
|
|
578
|
+
* Whether to fetch products immediately on mount
|
|
564
579
|
* @default true
|
|
565
580
|
*/
|
|
566
581
|
enabled?: boolean;
|
|
567
582
|
/**
|
|
568
|
-
*
|
|
583
|
+
* Filter by product type
|
|
569
584
|
*/
|
|
570
|
-
|
|
585
|
+
type?: 'SUBSCRIPTION' | 'ONE_TIME' | 'CREDITS' | 'DONATION';
|
|
586
|
+
/**
|
|
587
|
+
* Callback when products are fetched successfully
|
|
588
|
+
*/
|
|
589
|
+
onSuccess?: (products: Product[]) => void;
|
|
571
590
|
/**
|
|
572
591
|
* Callback when an error occurs
|
|
573
592
|
*/
|
|
574
593
|
onError?: (error: Error) => void;
|
|
575
594
|
}
|
|
576
|
-
interface
|
|
595
|
+
interface UseProductsResult {
|
|
577
596
|
/**
|
|
578
|
-
* The fetched
|
|
597
|
+
* The fetched products data
|
|
579
598
|
*/
|
|
580
|
-
data:
|
|
599
|
+
data: Product[] | undefined;
|
|
581
600
|
/**
|
|
582
|
-
* Whether the
|
|
601
|
+
* Whether the products are currently being fetched
|
|
583
602
|
*/
|
|
584
603
|
isLoading: boolean;
|
|
585
604
|
/**
|
|
@@ -592,29 +611,38 @@ interface UsePlansResult {
|
|
|
592
611
|
refetch: () => Promise<void>;
|
|
593
612
|
}
|
|
594
613
|
/**
|
|
595
|
-
*
|
|
614
|
+
* useProducts Hook
|
|
596
615
|
*
|
|
597
|
-
* Fetch available
|
|
616
|
+
* Fetch available products with automatic state management
|
|
598
617
|
*
|
|
599
618
|
* @example
|
|
600
619
|
* ```tsx
|
|
601
|
-
* function
|
|
602
|
-
* const { data:
|
|
620
|
+
* function ProductsPage() {
|
|
621
|
+
* const { data: products, isLoading, error } = useProducts();
|
|
603
622
|
*
|
|
604
|
-
* if (isLoading) return <
|
|
623
|
+
* if (isLoading) return <ProductsSkeletonLoader />;
|
|
605
624
|
* if (error) return <ErrorMessage error={error} />;
|
|
606
625
|
*
|
|
607
626
|
* return (
|
|
608
627
|
* <div>
|
|
609
|
-
* {
|
|
610
|
-
* <
|
|
628
|
+
* {products?.map(product => (
|
|
629
|
+
* <ProductCard key={product.id} product={product} />
|
|
611
630
|
* ))}
|
|
612
631
|
* </div>
|
|
613
632
|
* );
|
|
614
633
|
* }
|
|
615
634
|
* ```
|
|
635
|
+
*
|
|
636
|
+
* @example Filter by type
|
|
637
|
+
* ```tsx
|
|
638
|
+
* // Only fetch subscription products
|
|
639
|
+
* const { data: subscriptions } = useProducts({ type: 'SUBSCRIPTION' });
|
|
640
|
+
*
|
|
641
|
+
* // Only fetch one-time products
|
|
642
|
+
* const { data: oneTimeProducts } = useProducts({ type: 'ONE_TIME' });
|
|
643
|
+
* ```
|
|
616
644
|
*/
|
|
617
|
-
declare function
|
|
645
|
+
declare function useProducts(options?: UseProductsOptions): UseProductsResult;
|
|
618
646
|
|
|
619
647
|
interface UseSubscribeOptions {
|
|
620
648
|
/**
|
|
@@ -696,4 +724,4 @@ interface UseSubscribeResult {
|
|
|
696
724
|
*/
|
|
697
725
|
declare function useSubscribe(options?: UseSubscribeOptions): UseSubscribeResult;
|
|
698
726
|
|
|
699
|
-
export { type CheckoutError, type CheckoutOptions, type CheckoutResult, type Plan, type PlansResult, type ProductMetadata, type RecurConfig, type RecurContextValue, RecurProvider, type RecurProviderProps, type SubscriptionResult, type UsePlansOptions, type UsePlansResult, type UseSubscribeOptions, type UseSubscribeResult, usePlans, useRecur, useSubscribe };
|
|
727
|
+
export { type CheckoutError, type CheckoutOptions, type CheckoutResult, type Plan, type PlansResult, type Product, type ProductMetadata, type ProductsResult, type RecurConfig, type RecurContextValue, RecurProvider, type RecurProviderProps, type SubscriptionResult, type UseProductsOptions as UsePlansOptions, type UseProductsResult as UsePlansResult, type UseProductsOptions, type UseProductsResult, type UseSubscribeOptions, type UseSubscribeResult, useProducts as usePlans, useProducts, useRecur, useSubscribe };
|