recur-tw 0.10.7 → 0.11.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 +110 -6
- package/dist/index.cjs +204 -7
- package/dist/index.d.cts +380 -6
- package/dist/index.d.ts +380 -6
- package/dist/index.js +204 -8
- package/dist/recur.umd.js +12 -3
- package/dist/server.cjs +174 -1
- package/dist/server.d.cts +245 -1
- package/dist/server.d.ts +245 -1
- package/dist/server.js +174 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -211,6 +211,7 @@ function ProductsPage() {
|
|
|
211
211
|
|
|
212
212
|
- `useProducts(options?)` - 取得商品列表
|
|
213
213
|
- `useRecur()` - 取得 checkout 函式與狀態
|
|
214
|
+
- `useCustomer()` - 檢查客戶權限與訂閱狀態
|
|
214
215
|
|
|
215
216
|
```tsx
|
|
216
217
|
// 篩選商品類型
|
|
@@ -218,6 +219,57 @@ const { data: subscriptions } = useProducts({ type: 'SUBSCRIPTION' });
|
|
|
218
219
|
const { data: oneTimeProducts } = useProducts({ type: 'ONE_TIME' });
|
|
219
220
|
```
|
|
220
221
|
|
|
222
|
+
#### 權限檢查(useCustomer)
|
|
223
|
+
|
|
224
|
+
檢查客戶是否有特定產品的訂閱權限,無需自建資料庫或處理 Webhook:
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
import { RecurProvider, useCustomer } from 'recur-tw';
|
|
228
|
+
|
|
229
|
+
// 1. Provider 需傳入 customer 識別資訊
|
|
230
|
+
function App() {
|
|
231
|
+
const user = useAuth(); // 你的認證系統
|
|
232
|
+
return (
|
|
233
|
+
<RecurProvider
|
|
234
|
+
config={{ publishableKey: 'pk_live_xxx' }}
|
|
235
|
+
customer={{ email: user?.email }}
|
|
236
|
+
>
|
|
237
|
+
<PremiumFeature />
|
|
238
|
+
</RecurProvider>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// 2. 使用 check() 檢查權限
|
|
243
|
+
function PremiumFeature() {
|
|
244
|
+
const { check, isLoading } = useCustomer();
|
|
245
|
+
|
|
246
|
+
if (isLoading) return <Spinner />;
|
|
247
|
+
|
|
248
|
+
const { allowed } = check({ product: 'pro-plan' });
|
|
249
|
+
|
|
250
|
+
if (!allowed) {
|
|
251
|
+
return <UpgradePrompt />;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return <PremiumContent />;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// 3. 顯示訂閱狀態
|
|
258
|
+
function AccountPage() {
|
|
259
|
+
const { customer, subscription, entitlements } = useCustomer();
|
|
260
|
+
|
|
261
|
+
return (
|
|
262
|
+
<div>
|
|
263
|
+
<p>方案:{subscription?.product.name}</p>
|
|
264
|
+
<p>到期日:{subscription?.currentPeriodEnd}</p>
|
|
265
|
+
<p>擁有權限:{entitlements.map(e => e.product).join(', ')}</p>
|
|
266
|
+
</div>
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
> **安全提醒:** `check()` 從本地快取讀取,可被繞過。僅用於 UI 控制,敏感操作請在後端驗證。
|
|
272
|
+
|
|
221
273
|
---
|
|
222
274
|
|
|
223
275
|
### 方式五:`<recur-checkout>` Web Component
|
|
@@ -244,15 +296,13 @@ const { data: oneTimeProducts } = useProducts({ type: 'ONE_TIME' });
|
|
|
244
296
|
後端驗證與管理:
|
|
245
297
|
|
|
246
298
|
```typescript
|
|
247
|
-
import {
|
|
299
|
+
import { Recur } from 'recur-tw/server';
|
|
248
300
|
|
|
249
|
-
const recur = new
|
|
250
|
-
secretKey: 'sk_live_xxx'
|
|
251
|
-
});
|
|
301
|
+
const recur = new Recur(process.env.RECUR_SECRET_KEY!);
|
|
252
302
|
|
|
253
303
|
// 建立 Portal Session(讓客戶管理訂閱)
|
|
254
|
-
const session = await recur.
|
|
255
|
-
|
|
304
|
+
const session = await recur.portal.sessions.create({
|
|
305
|
+
customer: 'cus_xxx',
|
|
256
306
|
returnUrl: 'https://yoursite.com/account'
|
|
257
307
|
});
|
|
258
308
|
|
|
@@ -260,6 +310,60 @@ const session = await recur.portalSessions.create({
|
|
|
260
310
|
res.redirect(session.url);
|
|
261
311
|
```
|
|
262
312
|
|
|
313
|
+
### 權限檢查(Server-side)
|
|
314
|
+
|
|
315
|
+
在 API 路由中驗證客戶權限,確保安全:
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
import { Recur } from 'recur-tw/server';
|
|
319
|
+
|
|
320
|
+
const recur = new Recur(process.env.RECUR_SECRET_KEY!);
|
|
321
|
+
|
|
322
|
+
// API 路由範例
|
|
323
|
+
export async function GET(request: Request) {
|
|
324
|
+
const user = await getUser(request);
|
|
325
|
+
|
|
326
|
+
// 檢查單一產品權限
|
|
327
|
+
const { allowed } = await recur.entitlements.check({
|
|
328
|
+
product: 'pro-plan',
|
|
329
|
+
customer: { email: user.email },
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
if (!allowed) {
|
|
333
|
+
return Response.json(
|
|
334
|
+
{ error: '請升級到 Pro 方案' },
|
|
335
|
+
{ status: 403 }
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return Response.json(protectedData);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// 列出所有權限
|
|
343
|
+
const { entitlements } = await recur.entitlements.list({
|
|
344
|
+
customer: { email: 'user@example.com' },
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
console.log(entitlements);
|
|
348
|
+
// [
|
|
349
|
+
// { product: 'pro-plan', status: 'active', subscriptionId: 'sub_xxx' },
|
|
350
|
+
// { product: 'addon-ai', status: 'active', subscriptionId: 'sub_yyy' },
|
|
351
|
+
// ]
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
**客戶識別方式(擇一):**
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
// 方式一:Email(推薦)
|
|
358
|
+
customer: { email: 'user@example.com' }
|
|
359
|
+
|
|
360
|
+
// 方式二:External ID(你的系統 User ID)
|
|
361
|
+
customer: { externalId: 'usr_12345' }
|
|
362
|
+
|
|
363
|
+
// 方式三:Recur Customer ID
|
|
364
|
+
customer: { id: 'cus_xxx' }
|
|
365
|
+
```
|
|
366
|
+
|
|
263
367
|
---
|
|
264
368
|
|
|
265
369
|
## API Key
|
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
|
-
|
|
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,16 +2674,27 @@ function toCamelCase(obj) {
|
|
|
2653
2674
|
|
|
2654
2675
|
// package.json
|
|
2655
2676
|
var package_default = {
|
|
2656
|
-
version: "0.
|
|
2677
|
+
version: "0.11.0"};
|
|
2657
2678
|
var SDK_VERSION = package_default.version;
|
|
2658
2679
|
var SDK_TYPE = "react";
|
|
2659
2680
|
var RecurContext = React.createContext(null);
|
|
2660
|
-
|
|
2681
|
+
var CustomerContext = React.createContext(null);
|
|
2682
|
+
function RecurProvider({ children, config: initialConfig = {}, customer: customerIdentifier }) {
|
|
2661
2683
|
const [config, setConfig] = React.useState({
|
|
2662
2684
|
checkoutMode: "embedded",
|
|
2663
2685
|
...initialConfig
|
|
2664
2686
|
});
|
|
2665
2687
|
const [isCheckingOut, setIsCheckingOut] = React.useState(false);
|
|
2688
|
+
const [customerData, setCustomerData] = React.useState({
|
|
2689
|
+
customer: null,
|
|
2690
|
+
subscription: null,
|
|
2691
|
+
entitlements: []
|
|
2692
|
+
});
|
|
2693
|
+
const [customerIsLoading, setCustomerIsLoading] = React.useState(false);
|
|
2694
|
+
const [customerError, setCustomerError] = React.useState(null);
|
|
2695
|
+
const lastSuccessfulCache = React__default.default.useRef(null);
|
|
2696
|
+
const isRefetching = React__default.default.useRef(false);
|
|
2697
|
+
const refetchDebounceTimer = React__default.default.useRef(null);
|
|
2666
2698
|
React__default.default.useEffect(() => {
|
|
2667
2699
|
setConfig({
|
|
2668
2700
|
checkoutMode: "embedded",
|
|
@@ -2672,6 +2704,89 @@ function RecurProvider({ children, config: initialConfig = {} }) {
|
|
|
2672
2704
|
React__default.default.useEffect(() => {
|
|
2673
2705
|
setIsCheckingOut(false);
|
|
2674
2706
|
}, [config.checkoutMode]);
|
|
2707
|
+
const fetchEntitlements = React.useCallback(async () => {
|
|
2708
|
+
if (!config.publishableKey) {
|
|
2709
|
+
console.warn("[Recur SDK] Cannot fetch entitlements: publishableKey is required");
|
|
2710
|
+
return;
|
|
2711
|
+
}
|
|
2712
|
+
if (!customerIdentifier?.email && !customerIdentifier?.externalId && !customerIdentifier?.id) {
|
|
2713
|
+
setCustomerData({ customer: null, subscription: null, entitlements: [] });
|
|
2714
|
+
return;
|
|
2715
|
+
}
|
|
2716
|
+
setCustomerIsLoading(true);
|
|
2717
|
+
setCustomerError(null);
|
|
2718
|
+
try {
|
|
2719
|
+
const baseUrl = config.baseUrl || "https://api.recur.tw";
|
|
2720
|
+
const params = new URLSearchParams();
|
|
2721
|
+
if (customerIdentifier.email) params.set("email", customerIdentifier.email);
|
|
2722
|
+
if (customerIdentifier.externalId) params.set("external_id", customerIdentifier.externalId);
|
|
2723
|
+
if (customerIdentifier.id) params.set("customer_id", customerIdentifier.id);
|
|
2724
|
+
const response = await fetch(`${baseUrl}/v1/customers/entitlements?${params.toString()}`, {
|
|
2725
|
+
method: "GET",
|
|
2726
|
+
headers: {
|
|
2727
|
+
"X-Recur-Publishable-Key": config.publishableKey,
|
|
2728
|
+
"X-Recur-SDK-Type": SDK_TYPE,
|
|
2729
|
+
"X-Recur-SDK-Version": SDK_VERSION
|
|
2730
|
+
}
|
|
2731
|
+
});
|
|
2732
|
+
if (!response.ok) {
|
|
2733
|
+
const errorData = await response.json().catch(() => ({}));
|
|
2734
|
+
throw new Error(errorData.error?.message || "Failed to fetch entitlements");
|
|
2735
|
+
}
|
|
2736
|
+
const rawResult = await response.json();
|
|
2737
|
+
const result = toCamelCase(rawResult);
|
|
2738
|
+
const newData = {
|
|
2739
|
+
customer: result.customer || null,
|
|
2740
|
+
subscription: result.subscription || null,
|
|
2741
|
+
entitlements: result.entitlements || []
|
|
2742
|
+
};
|
|
2743
|
+
setCustomerData(newData);
|
|
2744
|
+
lastSuccessfulCache.current = newData;
|
|
2745
|
+
} catch (error) {
|
|
2746
|
+
console.error("[Recur SDK] Failed to fetch entitlements:", error);
|
|
2747
|
+
setCustomerError(error instanceof Error ? error : new Error("Failed to fetch entitlements"));
|
|
2748
|
+
if (lastSuccessfulCache.current) {
|
|
2749
|
+
setCustomerData(lastSuccessfulCache.current);
|
|
2750
|
+
}
|
|
2751
|
+
} finally {
|
|
2752
|
+
setCustomerIsLoading(false);
|
|
2753
|
+
}
|
|
2754
|
+
}, [config.publishableKey, config.baseUrl, customerIdentifier?.email, customerIdentifier?.externalId, customerIdentifier?.id]);
|
|
2755
|
+
React__default.default.useEffect(() => {
|
|
2756
|
+
fetchEntitlements();
|
|
2757
|
+
}, [fetchEntitlements]);
|
|
2758
|
+
const refetch = React.useCallback(async () => {
|
|
2759
|
+
if (isRefetching.current) {
|
|
2760
|
+
console.log("[Recur SDK] Refetch skipped - already in progress");
|
|
2761
|
+
return;
|
|
2762
|
+
}
|
|
2763
|
+
if (refetchDebounceTimer.current) {
|
|
2764
|
+
clearTimeout(refetchDebounceTimer.current);
|
|
2765
|
+
}
|
|
2766
|
+
isRefetching.current = true;
|
|
2767
|
+
try {
|
|
2768
|
+
await fetchEntitlements();
|
|
2769
|
+
} finally {
|
|
2770
|
+
refetchDebounceTimer.current = setTimeout(() => {
|
|
2771
|
+
isRefetching.current = false;
|
|
2772
|
+
}, 300);
|
|
2773
|
+
}
|
|
2774
|
+
}, [fetchEntitlements]);
|
|
2775
|
+
React__default.default.useEffect(() => {
|
|
2776
|
+
return () => {
|
|
2777
|
+
if (refetchDebounceTimer.current) {
|
|
2778
|
+
clearTimeout(refetchDebounceTimer.current);
|
|
2779
|
+
}
|
|
2780
|
+
};
|
|
2781
|
+
}, []);
|
|
2782
|
+
const customerContextValue = React.useMemo(() => ({
|
|
2783
|
+
customer: customerData.customer,
|
|
2784
|
+
subscription: customerData.subscription,
|
|
2785
|
+
entitlements: customerData.entitlements,
|
|
2786
|
+
isLoading: customerIsLoading,
|
|
2787
|
+
error: customerError,
|
|
2788
|
+
refetch
|
|
2789
|
+
}), [customerData, customerIsLoading, customerError, refetch]);
|
|
2675
2790
|
const updateConfig = React.useCallback((newConfig) => {
|
|
2676
2791
|
setConfig((prev) => ({ ...prev, ...newConfig }));
|
|
2677
2792
|
}, []);
|
|
@@ -3051,6 +3166,46 @@ function RecurProvider({ children, config: initialConfig = {} }) {
|
|
|
3051
3166
|
const rawPaymentResult = await paymentResponse.json();
|
|
3052
3167
|
const paymentResult = toCamelCase(rawPaymentResult);
|
|
3053
3168
|
console.log("[Recur SDK] Payment executed:", paymentResult);
|
|
3169
|
+
if (paymentResult.success === false && paymentResult.failure) {
|
|
3170
|
+
console.log("[Recur SDK] Payment failed:", paymentResult.failure);
|
|
3171
|
+
const failureError = {
|
|
3172
|
+
code: "PAYMENT_FAILED",
|
|
3173
|
+
message: paymentResult.failure.message || "\u4ED8\u6B3E\u5931\u6557",
|
|
3174
|
+
details: {
|
|
3175
|
+
failure_code: paymentResult.failure.code,
|
|
3176
|
+
failure_message: paymentResult.failure.message,
|
|
3177
|
+
can_retry: paymentResult.failure.canRetry
|
|
3178
|
+
}
|
|
3179
|
+
};
|
|
3180
|
+
let action;
|
|
3181
|
+
if (options.onPaymentFailed) {
|
|
3182
|
+
action = options.onPaymentFailed(failureError);
|
|
3183
|
+
}
|
|
3184
|
+
if (!action) {
|
|
3185
|
+
action = paymentResult.failure.canRetry ? { action: "retry" } : { action: "retry" };
|
|
3186
|
+
}
|
|
3187
|
+
if (action.action === "close") {
|
|
3188
|
+
options.onError?.(failureError);
|
|
3189
|
+
paymentForm.resetButton?.();
|
|
3190
|
+
closeDialog();
|
|
3191
|
+
setIsCheckingOut(false);
|
|
3192
|
+
return;
|
|
3193
|
+
} else if (action.action === "custom") {
|
|
3194
|
+
paymentForm.showError?.({
|
|
3195
|
+
title: action.customTitle || "\u4ED8\u6B3E\u5931\u6557",
|
|
3196
|
+
message: action.customMessage || failureError.message
|
|
3197
|
+
});
|
|
3198
|
+
paymentForm.resetButton?.();
|
|
3199
|
+
return;
|
|
3200
|
+
} else {
|
|
3201
|
+
paymentForm.showError?.({
|
|
3202
|
+
title: "\u4ED8\u6B3E\u5931\u6557",
|
|
3203
|
+
message: failureError.details?.failure_message || failureError.message
|
|
3204
|
+
});
|
|
3205
|
+
paymentForm.resetButton?.();
|
|
3206
|
+
return;
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3054
3209
|
if (paymentResult.requires3D && paymentResult.redirectUrl) {
|
|
3055
3210
|
console.log("[Recur SDK] 3D verification required");
|
|
3056
3211
|
const isMobileOrWebView = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || // Detect common WebView user agents
|
|
@@ -3359,7 +3514,7 @@ function RecurProvider({ children, config: initialConfig = {} }) {
|
|
|
3359
3514
|
}),
|
|
3360
3515
|
[config, checkout, fetchProducts, fetchPlans, isCheckingOut, updateConfig, getCheckoutStatus]
|
|
3361
3516
|
);
|
|
3362
|
-
return /* @__PURE__ */ jsxRuntime.jsx(RecurContext.Provider, { value, children });
|
|
3517
|
+
return /* @__PURE__ */ jsxRuntime.jsx(RecurContext.Provider, { value, children: /* @__PURE__ */ jsxRuntime.jsx(CustomerContext.Provider, { value: customerContextValue, children }) });
|
|
3363
3518
|
}
|
|
3364
3519
|
function useRecur() {
|
|
3365
3520
|
const context = React.useContext(RecurContext);
|
|
@@ -3401,7 +3556,7 @@ function useProducts(options = {}) {
|
|
|
3401
3556
|
};
|
|
3402
3557
|
}
|
|
3403
3558
|
function useSubscribe(options = {}) {
|
|
3404
|
-
const { onSuccess, onError, onPaymentComplete, onPaymentCancel } = options;
|
|
3559
|
+
const { onSuccess, onError, onPaymentComplete, onPaymentCancel, onPaymentFailed } = options;
|
|
3405
3560
|
const { checkout, isCheckingOut } = useRecur();
|
|
3406
3561
|
const [error, setError] = React.useState(null);
|
|
3407
3562
|
const mutate = React.useCallback(
|
|
@@ -3420,6 +3575,9 @@ function useSubscribe(options = {}) {
|
|
|
3420
3575
|
setError(err);
|
|
3421
3576
|
onError?.(err);
|
|
3422
3577
|
},
|
|
3578
|
+
onPaymentFailed: (err) => {
|
|
3579
|
+
return onPaymentFailed?.(err);
|
|
3580
|
+
},
|
|
3423
3581
|
onPaymentCancel: () => {
|
|
3424
3582
|
onPaymentCancel?.();
|
|
3425
3583
|
}
|
|
@@ -3433,7 +3591,7 @@ function useSubscribe(options = {}) {
|
|
|
3433
3591
|
onError?.(checkoutError);
|
|
3434
3592
|
}
|
|
3435
3593
|
},
|
|
3436
|
-
[checkout, onSuccess, onError, onPaymentComplete, onPaymentCancel]
|
|
3594
|
+
[checkout, onSuccess, onError, onPaymentComplete, onPaymentCancel, onPaymentFailed]
|
|
3437
3595
|
);
|
|
3438
3596
|
const reset = React.useCallback(() => {
|
|
3439
3597
|
setError(null);
|
|
@@ -3446,8 +3604,47 @@ function useSubscribe(options = {}) {
|
|
|
3446
3604
|
reset
|
|
3447
3605
|
};
|
|
3448
3606
|
}
|
|
3607
|
+
function useCustomer() {
|
|
3608
|
+
const context = React.useContext(CustomerContext);
|
|
3609
|
+
if (!context) {
|
|
3610
|
+
throw new Error("useCustomer must be used within a RecurProvider");
|
|
3611
|
+
}
|
|
3612
|
+
const { customer, subscription, entitlements, isLoading: isLoading2, error, refetch } = context;
|
|
3613
|
+
const check = React.useCallback(
|
|
3614
|
+
(options) => {
|
|
3615
|
+
if (!customer) {
|
|
3616
|
+
return { allowed: false, reason: "no_customer" };
|
|
3617
|
+
}
|
|
3618
|
+
if (entitlements.length === 0) {
|
|
3619
|
+
return { allowed: false, reason: "no_subscription" };
|
|
3620
|
+
}
|
|
3621
|
+
const entitlement = entitlements.find(
|
|
3622
|
+
(e) => e.product === options.product || e.productId === options.product
|
|
3623
|
+
);
|
|
3624
|
+
if (!entitlement) {
|
|
3625
|
+
return { allowed: false, reason: "wrong_product" };
|
|
3626
|
+
}
|
|
3627
|
+
let matchingSubscription;
|
|
3628
|
+
if (subscription && (subscription.product.slug === options.product || subscription.product.id === options.product)) {
|
|
3629
|
+
matchingSubscription = subscription;
|
|
3630
|
+
}
|
|
3631
|
+
return { allowed: true, subscription: matchingSubscription };
|
|
3632
|
+
},
|
|
3633
|
+
[customer, entitlements, subscription]
|
|
3634
|
+
);
|
|
3635
|
+
return {
|
|
3636
|
+
customer,
|
|
3637
|
+
subscription,
|
|
3638
|
+
entitlements,
|
|
3639
|
+
check,
|
|
3640
|
+
refetch,
|
|
3641
|
+
isLoading: isLoading2,
|
|
3642
|
+
error
|
|
3643
|
+
};
|
|
3644
|
+
}
|
|
3449
3645
|
|
|
3450
3646
|
exports.RecurProvider = RecurProvider;
|
|
3647
|
+
exports.useCustomer = useCustomer;
|
|
3451
3648
|
exports.usePlans = useProducts;
|
|
3452
3649
|
exports.useProducts = useProducts;
|
|
3453
3650
|
exports.useRecur = useRecur;
|