@blocklet/payment-react-headless 1.26.1 → 1.26.3
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/es/checkout/context/CheckoutProvider.js +26 -10
- package/es/checkout/context/SessionContext.d.ts +3 -1
- package/es/checkout/core/crossSell.d.ts +3 -2
- package/es/checkout/core/crossSell.js +24 -8
- package/es/checkout/core/customerForm.js +0 -6
- package/es/checkout/core/lineItems.d.ts +6 -5
- package/es/checkout/core/lineItems.js +48 -17
- package/es/checkout/core/paymentMethod.js +15 -7
- package/es/checkout/core/pricing.js +1 -2
- package/es/checkout/core/promotion.d.ts +1 -1
- package/es/checkout/core/promotion.js +2 -1
- package/es/checkout/hooks/useBillingInterval.js +8 -5
- package/es/checkout/hooks/useCheckout.js +17 -11
- package/es/checkout/hooks/useCheckoutSession.d.ts +2 -2
- package/es/checkout/hooks/useCheckoutSession.js +7 -0
- package/es/checkout/hooks/useCheckoutStatus.d.ts +1 -1
- package/es/checkout/hooks/useCrossSell.js +11 -3
- package/es/checkout/hooks/useCustomerForm.d.ts +4 -0
- package/es/checkout/hooks/useCustomerForm.js +6 -0
- package/es/checkout/hooks/useLineItems.js +36 -9
- package/es/checkout/hooks/usePaymentMethod.d.ts +1 -1
- package/es/checkout/hooks/usePaymentMethod.js +8 -4
- package/es/checkout/hooks/usePricing.d.ts +1 -1
- package/es/checkout/hooks/usePricing.js +4 -4
- package/es/checkout/hooks/useSubmit.js +6 -1
- package/es/checkout/hooks/useUpsell.js +3 -3
- package/es/checkout/types.d.ts +5 -3
- package/lib/checkout/context/CheckoutProvider.js +21 -12
- package/lib/checkout/context/SessionContext.d.ts +3 -1
- package/lib/checkout/core/crossSell.d.ts +3 -2
- package/lib/checkout/core/crossSell.js +36 -8
- package/lib/checkout/core/customerForm.js +0 -5
- package/lib/checkout/core/lineItems.d.ts +6 -5
- package/lib/checkout/core/lineItems.js +72 -18
- package/lib/checkout/core/paymentMethod.js +14 -8
- package/lib/checkout/core/pricing.js +1 -2
- package/lib/checkout/core/promotion.d.ts +1 -1
- package/lib/checkout/core/promotion.js +4 -1
- package/lib/checkout/hooks/useBillingInterval.js +10 -5
- package/lib/checkout/hooks/useCheckout.js +16 -11
- package/lib/checkout/hooks/useCheckoutSession.d.ts +2 -2
- package/lib/checkout/hooks/useCheckoutSession.js +7 -0
- package/lib/checkout/hooks/useCheckoutStatus.d.ts +1 -1
- package/lib/checkout/hooks/useCrossSell.js +5 -3
- package/lib/checkout/hooks/useCustomerForm.d.ts +4 -0
- package/lib/checkout/hooks/useCustomerForm.js +6 -0
- package/lib/checkout/hooks/useLineItems.js +10 -8
- package/lib/checkout/hooks/usePaymentMethod.d.ts +1 -1
- package/lib/checkout/hooks/usePaymentMethod.js +14 -4
- package/lib/checkout/hooks/usePricing.d.ts +1 -1
- package/lib/checkout/hooks/usePricing.js +4 -4
- package/lib/checkout/hooks/useSubmit.js +8 -1
- package/lib/checkout/hooks/useUpsell.js +5 -3
- package/lib/checkout/types.d.ts +5 -3
- package/package.json +3 -3
- package/src/checkout/context/CheckoutProvider.tsx +38 -17
- package/src/checkout/context/SessionContext.ts +3 -1
- package/src/checkout/core/crossSell.ts +29 -8
- package/src/checkout/core/customerForm.ts +0 -6
- package/src/checkout/core/lineItems.ts +62 -18
- package/src/checkout/core/paymentMethod.ts +24 -7
- package/src/checkout/core/pricing.ts +1 -2
- package/src/checkout/core/promotion.ts +6 -2
- package/src/checkout/hooks/useBillingInterval.ts +8 -5
- package/src/checkout/hooks/useCheckout.ts +20 -12
- package/src/checkout/hooks/useCheckoutSession.ts +12 -3
- package/src/checkout/hooks/useCheckoutStatus.ts +1 -1
- package/src/checkout/hooks/useCrossSell.ts +11 -3
- package/src/checkout/hooks/useCustomerForm.ts +12 -0
- package/src/checkout/hooks/useLineItems.ts +42 -9
- package/src/checkout/hooks/usePaymentMethod.ts +13 -5
- package/src/checkout/hooks/usePricing.ts +5 -4
- package/src/checkout/hooks/useSubmit.ts +13 -1
- package/src/checkout/hooks/useUpsell.ts +3 -3
- package/src/checkout/types.ts +4 -2
|
@@ -82,7 +82,6 @@ function calculateAmounts(items, currency, session, exchangeRate, hasDynamicPric
|
|
|
82
82
|
exchangeRate: hasDynamicPricing ? exchangeRate : null
|
|
83
83
|
});
|
|
84
84
|
const subtotalBN = new _util.BN(result.total);
|
|
85
|
-
const subtotalFormatted = (0, _format.formatDynamicPrice)((0, _util.fromUnitToToken)(subtotalBN, currency.decimal), hasDynamicPricing);
|
|
86
85
|
const discountBN = calculateCouponDiscount(items, currency, session, hasDynamicPricing, exchangeRate, trialing, false);
|
|
87
86
|
const discount = discountBN.gt(new _util.BN(0)) ? (0, _format.formatDynamicPrice)((0, _util.fromUnitToToken)(discountBN.toString(), currency.decimal), hasDynamicPricing) : null;
|
|
88
87
|
const taxAmount = session?.total_details?.amount_tax;
|
|
@@ -124,7 +123,7 @@ function calculateAmounts(items, currency, session, exchangeRate, hasDynamicPric
|
|
|
124
123
|
const stakingFormatted = stakingBN.gt(new _util.BN(0)) ? `${(0, _format.formatDynamicPrice)((0, _util.fromUnitToToken)(stakingBN.toString(), currency.decimal), hasDynamicPricing)} ${currency.symbol}` : null;
|
|
125
124
|
return {
|
|
126
125
|
subtotal: `${displaySubtotalFormatted} ${currency.symbol}`,
|
|
127
|
-
paymentAmount: `${
|
|
126
|
+
paymentAmount: `${totalFormatted} ${currency.symbol}`,
|
|
128
127
|
total: `${totalFormatted} ${currency.symbol}`,
|
|
129
128
|
discount: discount ? `${discount} ${currency.symbol}` : null,
|
|
130
129
|
tax,
|
|
@@ -5,6 +5,6 @@ export declare function applyPromotionCode(sessionId: string, code: string, curr
|
|
|
5
5
|
error?: string;
|
|
6
6
|
}>;
|
|
7
7
|
export declare function removePromotionCode(sessionId: string): Promise<void>;
|
|
8
|
-
export declare function recalculatePromotion(sessionId: string, currencyId: string | null | undefined): Promise<
|
|
8
|
+
export declare function recalculatePromotion(sessionId: string, currencyId: string | null | undefined): Promise<TCheckoutSessionExpanded>;
|
|
9
9
|
export declare function isPromotionActive(session: TCheckoutSessionExpanded | undefined | null): boolean;
|
|
10
10
|
export declare function hasAppliedDiscounts(session: TCheckoutSessionExpanded | undefined | null): boolean;
|
|
@@ -49,9 +49,12 @@ async function removePromotionCode(sessionId) {
|
|
|
49
49
|
await _api.default.delete(_api.API.REMOVE_PROMOTION(sessionId));
|
|
50
50
|
}
|
|
51
51
|
async function recalculatePromotion(sessionId, currencyId) {
|
|
52
|
-
|
|
52
|
+
const {
|
|
53
|
+
data
|
|
54
|
+
} = await _api.default.post(_api.API.RECALCULATE_PROMOTION_SESSION(sessionId), {
|
|
53
55
|
currency_id: currencyId
|
|
54
56
|
});
|
|
57
|
+
return data;
|
|
55
58
|
}
|
|
56
59
|
function isPromotionActive(session) {
|
|
57
60
|
return session?.allow_promotion_codes !== false;
|
|
@@ -16,23 +16,26 @@ function useBillingInterval() {
|
|
|
16
16
|
items,
|
|
17
17
|
session,
|
|
18
18
|
effectiveSessionId,
|
|
19
|
-
refresh
|
|
19
|
+
refresh,
|
|
20
|
+
sessionData,
|
|
21
|
+
setSessionData
|
|
20
22
|
} = (0, _SessionContext.useSessionContext)();
|
|
21
23
|
const {
|
|
22
24
|
currency
|
|
23
25
|
} = (0, _PaymentMethodContext.usePaymentMethodContext)();
|
|
24
26
|
const currencyId = currency?.id || null;
|
|
25
27
|
const [switching, setSwitching] = (0, _react.useState)(false);
|
|
28
|
+
const [pendingInterval, setPendingInterval] = (0, _react.useState)(null);
|
|
26
29
|
const upsell = (0, _ahooks.useMemoizedFn)(async (fromId, toId) => {
|
|
27
30
|
try {
|
|
28
|
-
await (0, _lineItems.performUpsell)(effectiveSessionId, fromId, toId, session, currencyId, refresh);
|
|
31
|
+
await (0, _lineItems.performUpsell)(effectiveSessionId, fromId, toId, session, currencyId, refresh, sessionData, setSessionData);
|
|
29
32
|
} catch (err) {
|
|
30
33
|
console.error("Failed to upsell:", (0, _checkoutAugmented.getErrorMessage)(err));
|
|
31
34
|
}
|
|
32
35
|
});
|
|
33
36
|
const downsell = (0, _ahooks.useMemoizedFn)(async priceId => {
|
|
34
37
|
try {
|
|
35
|
-
await (0, _lineItems.performDownsell)(effectiveSessionId, priceId, session, currencyId, refresh);
|
|
38
|
+
await (0, _lineItems.performDownsell)(effectiveSessionId, priceId, session, currencyId, refresh, sessionData, setSessionData);
|
|
36
39
|
} catch (err) {
|
|
37
40
|
console.error("Failed to downsell:", (0, _checkoutAugmented.getErrorMessage)(err));
|
|
38
41
|
}
|
|
@@ -41,12 +44,13 @@ function useBillingInterval() {
|
|
|
41
44
|
const parsed = (0, _billingInterval.parseBillingInterval)(items);
|
|
42
45
|
if (!parsed) return null;
|
|
43
46
|
return {
|
|
44
|
-
current: parsed.current,
|
|
47
|
+
current: pendingInterval || parsed.current,
|
|
45
48
|
available: parsed.available,
|
|
46
49
|
switching,
|
|
47
50
|
switch: async interval => {
|
|
48
51
|
const target = parsed.available.find(a => a.interval === interval);
|
|
49
52
|
if (!target || switching) return;
|
|
53
|
+
setPendingInterval(interval);
|
|
50
54
|
setSwitching(true);
|
|
51
55
|
try {
|
|
52
56
|
if (!parsed.firstItem.upsell_price_id && target.priceId) {
|
|
@@ -56,8 +60,9 @@ function useBillingInterval() {
|
|
|
56
60
|
}
|
|
57
61
|
} finally {
|
|
58
62
|
setSwitching(false);
|
|
63
|
+
setPendingInterval(null);
|
|
59
64
|
}
|
|
60
65
|
}
|
|
61
66
|
};
|
|
62
|
-
}, [items, effectiveSessionId, switching]);
|
|
67
|
+
}, [items, effectiveSessionId, switching, pendingInterval]);
|
|
63
68
|
}
|
|
@@ -21,6 +21,7 @@ function useCheckout(sessionId) {
|
|
|
21
21
|
error,
|
|
22
22
|
errorCode,
|
|
23
23
|
refresh,
|
|
24
|
+
setSessionData,
|
|
24
25
|
sessionData,
|
|
25
26
|
resolvedSessionId,
|
|
26
27
|
vendorCount,
|
|
@@ -30,39 +31,43 @@ function useCheckout(sessionId) {
|
|
|
30
31
|
} = (0, _useCheckoutSession.useCheckoutSession)(sessionId);
|
|
31
32
|
const session = sessionData?.checkoutSession;
|
|
32
33
|
const effectiveSessionId = resolvedSessionId || sessionId;
|
|
33
|
-
const paymentMethodHook = (0, _usePaymentMethod.usePaymentMethod)(sessionData, effectiveSessionId, refresh);
|
|
34
|
-
const pricingHook = (0, _usePricing.usePricing)(sessionData, effectiveSessionId, paymentMethodHook.currency, paymentMethodHook.isStripe, refresh, paymentMethodHook.current?.type || null);
|
|
34
|
+
const paymentMethodHook = (0, _usePaymentMethod.usePaymentMethod)(sessionData, effectiveSessionId, refresh, setSessionData);
|
|
35
|
+
const pricingHook = (0, _usePricing.usePricing)(sessionData, effectiveSessionId, paymentMethodHook.currency, paymentMethodHook.isStripe, refresh, paymentMethodHook.current?.type || null, paymentMethodHook.switching);
|
|
35
36
|
const formHook = (0, _useCustomerForm.useCustomerForm)(sessionData, paymentMethodHook.currency?.id || null, paymentMethodHook.current?.id || null);
|
|
36
37
|
const isDonation = session?.submit_type === "donate";
|
|
37
38
|
const submitHook = (0, _useSubmit.useSubmit)(sessionData, effectiveSessionId, paymentMethodHook.currency?.id || null, paymentMethodHook.isStripe, paymentMethodHook.isCredit, isDonation, formHook.values, formHook.validate, refresh);
|
|
38
|
-
const items = session?.line_items || [];
|
|
39
|
+
const items = (0, _react.useMemo)(() => session?.line_items || [], [session?.line_items]);
|
|
39
40
|
const currencyId = paymentMethodHook.currency?.id || null;
|
|
40
41
|
const prevCurrencyRef = (0, _react.useRef)(null);
|
|
41
42
|
(0, _react.useEffect)(() => {
|
|
42
43
|
const currId = paymentMethodHook.currency?.id || null;
|
|
43
|
-
if (!currId || !session) return;
|
|
44
|
+
if (!currId || !session || session.status === "complete") return;
|
|
44
45
|
if (prevCurrencyRef.current === null || currId !== prevCurrencyRef.current) {
|
|
45
46
|
prevCurrencyRef.current = currId;
|
|
46
|
-
(0, _lineItems.recalculatePromotionIfNeeded)(session, effectiveSessionId, currId).then(
|
|
47
|
+
(0, _lineItems.recalculatePromotionIfNeeded)(session, effectiveSessionId, currId).then(recalculated => {
|
|
48
|
+
if (recalculated) {
|
|
49
|
+
refresh(true);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
47
52
|
}
|
|
48
53
|
}, [paymentMethodHook.currency?.id, session?.id]);
|
|
49
54
|
const updateQuantity = (0, _ahooks.useMemoizedFn)(async (itemId, qty) => {
|
|
50
55
|
try {
|
|
51
|
-
await (0, _lineItems.adjustQuantity)(effectiveSessionId, itemId, qty, currencyId, session, refresh);
|
|
56
|
+
await (0, _lineItems.adjustQuantity)(effectiveSessionId, itemId, qty, currencyId, session, refresh, sessionData, setSessionData);
|
|
52
57
|
} catch (err) {
|
|
53
58
|
console.error("Failed to update quantity:", (0, _checkoutAugmented.getErrorMessage)(err));
|
|
54
59
|
}
|
|
55
60
|
});
|
|
56
61
|
const upsell = (0, _ahooks.useMemoizedFn)(async (fromId, toId) => {
|
|
57
62
|
try {
|
|
58
|
-
await (0, _lineItems.performUpsell)(effectiveSessionId, fromId, toId, session, currencyId, refresh);
|
|
63
|
+
await (0, _lineItems.performUpsell)(effectiveSessionId, fromId, toId, session, currencyId, refresh, sessionData, setSessionData);
|
|
59
64
|
} catch (err) {
|
|
60
65
|
console.error("Failed to upsell:", (0, _checkoutAugmented.getErrorMessage)(err));
|
|
61
66
|
}
|
|
62
67
|
});
|
|
63
68
|
const downsell = (0, _ahooks.useMemoizedFn)(async priceId => {
|
|
64
69
|
try {
|
|
65
|
-
await (0, _lineItems.performDownsell)(effectiveSessionId, priceId, session, currencyId, refresh);
|
|
70
|
+
await (0, _lineItems.performDownsell)(effectiveSessionId, priceId, session, currencyId, refresh, sessionData, setSessionData);
|
|
66
71
|
} catch (err) {
|
|
67
72
|
console.error("Failed to downsell:", (0, _checkoutAugmented.getErrorMessage)(err));
|
|
68
73
|
}
|
|
@@ -87,14 +92,14 @@ function useCheckout(sessionId) {
|
|
|
87
92
|
const addCrossSell = (0, _ahooks.useMemoizedFn)(async () => {
|
|
88
93
|
if (!crossSellItem) return;
|
|
89
94
|
try {
|
|
90
|
-
await (0, _crossSell.addCrossSellItem)(effectiveSessionId, crossSellItem.id, session, currencyId, refresh);
|
|
95
|
+
await (0, _crossSell.addCrossSellItem)(effectiveSessionId, crossSellItem.id, session, currencyId, refresh, sessionData, setSessionData);
|
|
91
96
|
} catch (err) {
|
|
92
97
|
console.error("Failed to add cross-sell:", (0, _checkoutAugmented.getErrorMessage)(err));
|
|
93
98
|
}
|
|
94
99
|
});
|
|
95
100
|
const removeCrossSell = (0, _ahooks.useMemoizedFn)(async () => {
|
|
96
101
|
try {
|
|
97
|
-
await (0, _crossSell.removeCrossSellItem)(effectiveSessionId, session, currencyId, refresh);
|
|
102
|
+
await (0, _crossSell.removeCrossSellItem)(effectiveSessionId, session, currencyId, refresh, sessionData, setSessionData);
|
|
98
103
|
} catch (err) {
|
|
99
104
|
console.error("Failed to remove cross-sell:", (0, _checkoutAugmented.getErrorMessage)(err));
|
|
100
105
|
}
|
|
@@ -119,7 +124,7 @@ function useCheckout(sessionId) {
|
|
|
119
124
|
const setDonationAmount = (0, _ahooks.useMemoizedFn)(async (priceId, amount) => {
|
|
120
125
|
if (!isDonation) return;
|
|
121
126
|
try {
|
|
122
|
-
await (0, _lineItems.changeDonationAmount)(effectiveSessionId, priceId, amount, session, currencyId, refresh);
|
|
127
|
+
await (0, _lineItems.changeDonationAmount)(effectiveSessionId, priceId, amount, session, currencyId, refresh, sessionData, setSessionData);
|
|
123
128
|
} catch (err) {
|
|
124
129
|
console.error("Failed to change amount:", (0, _checkoutAugmented.getErrorMessage)(err));
|
|
125
130
|
}
|
|
@@ -18,8 +18,8 @@ export interface SessionData {
|
|
|
18
18
|
export interface UseCheckoutSessionReturn {
|
|
19
19
|
isLoading: boolean;
|
|
20
20
|
error: string | null;
|
|
21
|
-
/** Error code for structured error handling
|
|
22
|
-
errorCode: 'SESSION_EXPIRED' | 'EMPTY_LINE_ITEMS' | null;
|
|
21
|
+
/** Error code for structured error handling */
|
|
22
|
+
errorCode: 'SESSION_EXPIRED' | 'EMPTY_LINE_ITEMS' | 'STOP_ACCEPTING_ORDERS' | null;
|
|
23
23
|
refresh: (forceRefresh?: boolean) => Promise<void>;
|
|
24
24
|
/** Directly update session data (e.g. after completion polling returns fresh data) */
|
|
25
25
|
setSessionData: (data: SessionData) => void;
|
|
@@ -66,6 +66,11 @@ function useCheckoutSession(sessionId) {
|
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
+
if (data?.stopAcceptingOrders && cs?.status !== "complete") {
|
|
70
|
+
setError("STOP_ACCEPTING_ORDERS");
|
|
71
|
+
setIsLoading(false);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
69
74
|
setSessionData(data);
|
|
70
75
|
setIsLoading(false);
|
|
71
76
|
} catch (err) {
|
|
@@ -99,6 +104,8 @@ function useCheckoutSession(sessionId) {
|
|
|
99
104
|
errorCode = "SESSION_EXPIRED";
|
|
100
105
|
} else if (error === "EMPTY_LINE_ITEMS") {
|
|
101
106
|
errorCode = "EMPTY_LINE_ITEMS";
|
|
107
|
+
} else if (error === "STOP_ACCEPTING_ORDERS") {
|
|
108
|
+
errorCode = "STOP_ACCEPTING_ORDERS";
|
|
102
109
|
}
|
|
103
110
|
const vendorCount = session?.line_items?.reduce((count, item) => {
|
|
104
111
|
return count + (item?.price?.product?.vendor_config?.length || 0);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export interface UseCheckoutStatusReturn {
|
|
2
2
|
isLoading: boolean;
|
|
3
3
|
error: string | null;
|
|
4
|
-
errorCode: 'SESSION_EXPIRED' | 'EMPTY_LINE_ITEMS' | null;
|
|
4
|
+
errorCode: 'SESSION_EXPIRED' | 'EMPTY_LINE_ITEMS' | 'STOP_ACCEPTING_ORDERS' | null;
|
|
5
5
|
canSubmit: boolean;
|
|
6
6
|
isCompleted: boolean;
|
|
7
7
|
isDonation: boolean;
|
|
@@ -16,7 +16,9 @@ function useCrossSell() {
|
|
|
16
16
|
items,
|
|
17
17
|
session,
|
|
18
18
|
effectiveSessionId,
|
|
19
|
-
refresh
|
|
19
|
+
refresh,
|
|
20
|
+
sessionData,
|
|
21
|
+
setSessionData
|
|
20
22
|
} = (0, _SessionContext.useSessionContext)();
|
|
21
23
|
const {
|
|
22
24
|
currency
|
|
@@ -53,7 +55,7 @@ function useCrossSell() {
|
|
|
53
55
|
const crossSellItemPrice = (0, _lineItems.getCrossSellItem)(items);
|
|
54
56
|
if (!crossSellItemPrice) return;
|
|
55
57
|
try {
|
|
56
|
-
await (0, _crossSell.addCrossSellItem)(effectiveSessionId, crossSellItemPrice.id, session, currencyId, refresh);
|
|
58
|
+
await (0, _crossSell.addCrossSellItem)(effectiveSessionId, crossSellItemPrice.id, session, currencyId, refresh, sessionData, setSessionData);
|
|
57
59
|
} catch (err) {
|
|
58
60
|
console.error("Failed to add cross-sell:", (0, _checkoutAugmented.getErrorMessage)(err));
|
|
59
61
|
}
|
|
@@ -61,7 +63,7 @@ function useCrossSell() {
|
|
|
61
63
|
const remove = (0, _ahooks.useMemoizedFn)(async () => {
|
|
62
64
|
if (session?.status === "complete") return;
|
|
63
65
|
try {
|
|
64
|
-
await (0, _crossSell.removeCrossSellItem)(effectiveSessionId, session, currencyId, refresh);
|
|
66
|
+
await (0, _crossSell.removeCrossSellItem)(effectiveSessionId, session, currencyId, refresh, sessionData, setSessionData);
|
|
65
67
|
} catch (err) {
|
|
66
68
|
console.error("Failed to remove cross-sell:", (0, _checkoutAugmented.getErrorMessage)(err));
|
|
67
69
|
}
|
|
@@ -7,7 +7,11 @@ export interface UseCustomerFormReturn {
|
|
|
7
7
|
errors: Partial<Record<string, string>>;
|
|
8
8
|
touched: Record<string, boolean>;
|
|
9
9
|
validate: () => Promise<boolean>;
|
|
10
|
+
/** Silent validation — returns valid/invalid without setting error messages on the form */
|
|
11
|
+
checkValid: () => Promise<boolean>;
|
|
10
12
|
validateField: (field: string) => Promise<void>;
|
|
13
|
+
/** Whether customer info has been prefetched from backend */
|
|
14
|
+
prefetched: boolean;
|
|
11
15
|
/** Re-fetch customer info from backend and update form values (e.g. after login) */
|
|
12
16
|
refetchCustomer: () => Promise<void>;
|
|
13
17
|
}
|
|
@@ -83,6 +83,10 @@ function useCustomerForm(sessionData, currencyId, methodId) {
|
|
|
83
83
|
setErrors(result.errors);
|
|
84
84
|
return result.valid;
|
|
85
85
|
});
|
|
86
|
+
const checkValid = (0, _ahooks.useMemoizedFn)(async () => {
|
|
87
|
+
const result = await (0, _validation.validateForm)(values, getValidateOptions());
|
|
88
|
+
return result.valid;
|
|
89
|
+
});
|
|
86
90
|
const validateField = (0, _ahooks.useMemoizedFn)(async field => {
|
|
87
91
|
const result = await (0, _validation.validateForm)(values, getValidateOptions());
|
|
88
92
|
setErrors(prev => {
|
|
@@ -129,7 +133,9 @@ function useCustomerForm(sessionData, currencyId, methodId) {
|
|
|
129
133
|
errors,
|
|
130
134
|
touched,
|
|
131
135
|
validate,
|
|
136
|
+
checkValid,
|
|
132
137
|
validateField,
|
|
138
|
+
prefetched,
|
|
133
139
|
refetchCustomer
|
|
134
140
|
};
|
|
135
141
|
}
|
|
@@ -17,7 +17,9 @@ function useLineItems() {
|
|
|
17
17
|
session,
|
|
18
18
|
effectiveSessionId,
|
|
19
19
|
isDonation,
|
|
20
|
-
refresh
|
|
20
|
+
refresh,
|
|
21
|
+
sessionData,
|
|
22
|
+
setSessionData
|
|
21
23
|
} = (0, _SessionContext.useSessionContext)();
|
|
22
24
|
const {
|
|
23
25
|
currency
|
|
@@ -34,7 +36,7 @@ function useLineItems() {
|
|
|
34
36
|
const qty = Math.max(1, parseInt(qtyStr, 10));
|
|
35
37
|
if (Number.isFinite(qty) && qty !== item.quantity) {
|
|
36
38
|
defaultQtyApplied.current = true;
|
|
37
|
-
(0, _lineItems.adjustQuantity)(effectiveSessionId, item.price_id, qty, currencyId, session, refresh);
|
|
39
|
+
(0, _lineItems.adjustQuantity)(effectiveSessionId, item.price_id, qty, currencyId, session, refresh, sessionData, setSessionData);
|
|
38
40
|
break;
|
|
39
41
|
}
|
|
40
42
|
}
|
|
@@ -44,18 +46,18 @@ function useLineItems() {
|
|
|
44
46
|
const updateQuantity = (0, _ahooks.useMemoizedFn)(async (itemId, qty) => {
|
|
45
47
|
if (session?.status === "complete") return;
|
|
46
48
|
try {
|
|
47
|
-
await (0, _lineItems.adjustQuantity)(effectiveSessionId, itemId, qty, currencyId, session, refresh);
|
|
49
|
+
await (0, _lineItems.adjustQuantity)(effectiveSessionId, itemId, qty, currencyId, session, refresh, sessionData, setSessionData);
|
|
48
50
|
} catch (err) {
|
|
49
51
|
console.error("Failed to update quantity:", (0, _checkoutAugmented.getErrorMessage)(err));
|
|
50
52
|
}
|
|
51
53
|
});
|
|
52
54
|
const upsell = (0, _ahooks.useMemoizedFn)(async (fromId, toId) => {
|
|
53
55
|
if (session?.status === "complete") return;
|
|
54
|
-
await (0, _lineItems.performUpsell)(effectiveSessionId, fromId, toId, session, currencyId, refresh);
|
|
56
|
+
await (0, _lineItems.performUpsell)(effectiveSessionId, fromId, toId, session, currencyId, refresh, sessionData, setSessionData);
|
|
55
57
|
});
|
|
56
58
|
const downsell = (0, _ahooks.useMemoizedFn)(async priceId => {
|
|
57
59
|
if (session?.status === "complete") return;
|
|
58
|
-
await (0, _lineItems.performDownsell)(effectiveSessionId, priceId, session, currencyId, refresh);
|
|
60
|
+
await (0, _lineItems.performDownsell)(effectiveSessionId, priceId, session, currencyId, refresh, sessionData, setSessionData);
|
|
59
61
|
});
|
|
60
62
|
const embeddedCrossSellItem = (0, _react.useMemo)(() => (0, _lineItems.getCrossSellItem)(items), [items]);
|
|
61
63
|
const [fetchedCrossSellItem, setFetchedCrossSellItem] = (0, _react.useState)(null);
|
|
@@ -87,7 +89,7 @@ function useLineItems() {
|
|
|
87
89
|
if (session?.status === "complete") return;
|
|
88
90
|
if (!crossSellItem) return;
|
|
89
91
|
try {
|
|
90
|
-
await (0, _crossSell.addCrossSellItem)(effectiveSessionId, crossSellItem.id, session, currencyId, refresh);
|
|
92
|
+
await (0, _crossSell.addCrossSellItem)(effectiveSessionId, crossSellItem.id, session, currencyId, refresh, sessionData, setSessionData);
|
|
91
93
|
} catch (err) {
|
|
92
94
|
console.error("Failed to add cross-sell:", (0, _checkoutAugmented.getErrorMessage)(err));
|
|
93
95
|
}
|
|
@@ -95,7 +97,7 @@ function useLineItems() {
|
|
|
95
97
|
const removeCrossSell = (0, _ahooks.useMemoizedFn)(async () => {
|
|
96
98
|
if (session?.status === "complete") return;
|
|
97
99
|
try {
|
|
98
|
-
await (0, _crossSell.removeCrossSellItem)(effectiveSessionId, session, currencyId, refresh);
|
|
100
|
+
await (0, _crossSell.removeCrossSellItem)(effectiveSessionId, session, currencyId, refresh, sessionData, setSessionData);
|
|
99
101
|
} catch (err) {
|
|
100
102
|
console.error("Failed to remove cross-sell:", (0, _checkoutAugmented.getErrorMessage)(err));
|
|
101
103
|
}
|
|
@@ -104,7 +106,7 @@ function useLineItems() {
|
|
|
104
106
|
if (session?.status === "complete") return;
|
|
105
107
|
if (!isDonation) return;
|
|
106
108
|
try {
|
|
107
|
-
await (0, _lineItems.changeDonationAmount)(effectiveSessionId, priceId, amount, session, currencyId, refresh);
|
|
109
|
+
await (0, _lineItems.changeDonationAmount)(effectiveSessionId, priceId, amount, session, currencyId, refresh, sessionData, setSessionData);
|
|
108
110
|
} catch (err) {
|
|
109
111
|
console.error("Failed to change amount:", (0, _checkoutAugmented.getErrorMessage)(err));
|
|
110
112
|
}
|
|
@@ -23,4 +23,4 @@ export interface UsePaymentMethodReturn {
|
|
|
23
23
|
status: 'idle' | 'ready' | 'processing' | 'succeeded' | 'failed';
|
|
24
24
|
} | null;
|
|
25
25
|
}
|
|
26
|
-
export declare function usePaymentMethod(sessionData: SessionData | null, sessionId: string, refreshSession: (force?: boolean) => Promise<void
|
|
26
|
+
export declare function usePaymentMethod(sessionData: SessionData | null, sessionId: string, refreshSession: (force?: boolean) => Promise<void>, setSessionData?: (data: SessionData) => void): UsePaymentMethodReturn;
|
|
@@ -11,8 +11,8 @@ var _api = _interopRequireWildcard(require("../../shared/api"));
|
|
|
11
11
|
var _paymentMethod = require("../core/paymentMethod");
|
|
12
12
|
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
13
13
|
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
14
|
-
function usePaymentMethod(sessionData, sessionId, refreshSession) {
|
|
15
|
-
const methods = sessionData?.paymentMethods || [];
|
|
14
|
+
function usePaymentMethod(sessionData, sessionId, refreshSession, setSessionData) {
|
|
15
|
+
const methods = (0, _react.useMemo)(() => sessionData?.paymentMethods || [], [sessionData?.paymentMethods]);
|
|
16
16
|
const session = sessionData?.checkoutSession;
|
|
17
17
|
const [currencyId, setCurrencyId] = (0, _react.useState)(() => (0, _paymentMethod.getInitialCurrencyId)(session, methods));
|
|
18
18
|
const [switching, setSwitching] = (0, _react.useState)(false);
|
|
@@ -47,7 +47,9 @@ function usePaymentMethod(sessionData, sessionId, refreshSession) {
|
|
|
47
47
|
if (!method || !sessionId) return;
|
|
48
48
|
setSwitching(true);
|
|
49
49
|
try {
|
|
50
|
-
|
|
50
|
+
const {
|
|
51
|
+
data
|
|
52
|
+
} = await _api.default.put(_api.API.SWITCH_CURRENCY(sessionId), {
|
|
51
53
|
currency_id: newCurrencyId,
|
|
52
54
|
payment_method_id: method.id
|
|
53
55
|
});
|
|
@@ -55,7 +57,15 @@ function usePaymentMethod(sessionData, sessionId, refreshSession) {
|
|
|
55
57
|
try {
|
|
56
58
|
localStorage.setItem((0, _paymentMethod.getCurrencyStorageKey)(session?.user?.did), newCurrencyId);
|
|
57
59
|
} catch {}
|
|
58
|
-
|
|
60
|
+
if (sessionData && setSessionData) {
|
|
61
|
+
setSessionData({
|
|
62
|
+
...sessionData,
|
|
63
|
+
checkoutSession: data,
|
|
64
|
+
quotes: void 0
|
|
65
|
+
});
|
|
66
|
+
} else {
|
|
67
|
+
await refreshSession(true);
|
|
68
|
+
}
|
|
59
69
|
} catch (err) {
|
|
60
70
|
console.error("Failed to switch currency:", (0, _checkoutAugmented.getErrorMessage)(err));
|
|
61
71
|
if (session?.currency_id) {
|
|
@@ -54,4 +54,4 @@ export interface UsePricingReturn {
|
|
|
54
54
|
afterTrialInterval: string | null;
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
|
-
export declare function usePricing(sessionData: SessionData | null, sessionId: string, currency: TPaymentCurrency | null, isStripe: boolean, refreshSession: (force?: boolean) => Promise<void>, paymentMethodType?: string | null): UsePricingReturn;
|
|
57
|
+
export declare function usePricing(sessionData: SessionData | null, sessionId: string, currency: TPaymentCurrency | null, isStripe: boolean, refreshSession: (force?: boolean) => Promise<void>, paymentMethodType?: string | null, switching?: boolean): UsePricingReturn;
|
|
@@ -12,7 +12,7 @@ var _exchangeRate = require("../core/exchangeRate");
|
|
|
12
12
|
var _pricing = require("../core/pricing");
|
|
13
13
|
var _promotion = require("../core/promotion");
|
|
14
14
|
var _submit = require("../core/submit");
|
|
15
|
-
function usePricing(sessionData, sessionId, currency, isStripe, refreshSession, paymentMethodType) {
|
|
15
|
+
function usePricing(sessionData, sessionId, currency, isStripe, refreshSession, paymentMethodType, switching) {
|
|
16
16
|
const session = sessionData?.checkoutSession;
|
|
17
17
|
const items = session?.line_items || [];
|
|
18
18
|
const [exchangeRate, setExchangeRate] = (0, _react.useState)(null);
|
|
@@ -31,7 +31,7 @@ function usePricing(sessionData, sessionId, currency, isStripe, refreshSession,
|
|
|
31
31
|
const mountedRef = (0, _react.useRef)(true);
|
|
32
32
|
const hasDynamicPricing = (0, _react.useMemo)(() => (0, _exchangeRate.checkHasDynamicPricing)(items), [items]);
|
|
33
33
|
const fetchRate = (0, _ahooks.useMemoizedFn)(async () => {
|
|
34
|
-
if (!sessionId || !hasDynamicPricing || isStripe) {
|
|
34
|
+
if (!sessionId || !hasDynamicPricing || isStripe || switching) {
|
|
35
35
|
setRateStatus(hasDynamicPricing ? "unavailable" : "available");
|
|
36
36
|
if (isStripe) {
|
|
37
37
|
setExchangeRate(null);
|
|
@@ -63,7 +63,7 @@ function usePricing(sessionData, sessionId, currency, isStripe, refreshSession,
|
|
|
63
63
|
});
|
|
64
64
|
(0, _react.useEffect)(() => {
|
|
65
65
|
mountedRef.current = true;
|
|
66
|
-
if (!hasDynamicPricing || isStripe || !sessionId || session?.status === "complete") {
|
|
66
|
+
if (!hasDynamicPricing || isStripe || switching || !sessionId || session?.status === "complete") {
|
|
67
67
|
if (isStripe) {
|
|
68
68
|
setExchangeRate(null);
|
|
69
69
|
setRateProvider(null);
|
|
@@ -96,7 +96,7 @@ function usePricing(sessionData, sessionId, currency, isStripe, refreshSession,
|
|
|
96
96
|
if (pollingRef.current) clearTimeout(pollingRef.current);
|
|
97
97
|
document.removeEventListener("visibilitychange", handleVisibility);
|
|
98
98
|
};
|
|
99
|
-
}, [hasDynamicPricing, isStripe, sessionId, currency?.id, session?.status]);
|
|
99
|
+
}, [hasDynamicPricing, isStripe, switching, sessionId, currency?.id, session?.status]);
|
|
100
100
|
const amounts = (0, _react.useMemo)(() => (0, _pricing.calculateAmounts)(items, currency, session, exchangeRate, hasDynamicPricing, paymentMethodType), [items, currency, exchangeRate, hasDynamicPricing, session, paymentMethodType]);
|
|
101
101
|
const quoteMeta = (0, _react.useMemo)(() => (0, _pricing.calculateQuoteMeta)(items, hasDynamicPricing, sessionData), [items, hasDynamicPricing, sessionData]);
|
|
102
102
|
const setSlippage = (0, _ahooks.useMemoizedFn)(async config => {
|
|
@@ -330,6 +330,13 @@ function useSubmit(sessionData, sessionId, currencyId, isStripe, isCredit, isDon
|
|
|
330
330
|
});
|
|
331
331
|
return;
|
|
332
332
|
}
|
|
333
|
+
if (errorCode === "STOP_ACCEPTING_ORDERS") {
|
|
334
|
+
setStatus("service_suspended");
|
|
335
|
+
setContext({
|
|
336
|
+
type: "service_suspended"
|
|
337
|
+
});
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
333
340
|
if (errorCode === "UNIFIED_APP_REQUIRED" || errorCode === "CUSTOMER_LIMITED") {
|
|
334
341
|
setStatus("failed");
|
|
335
342
|
setContext({
|
|
@@ -454,7 +461,7 @@ function useSubmit(sessionData, sessionId, currencyId, isStripe, isCredit, isDon
|
|
|
454
461
|
}
|
|
455
462
|
});
|
|
456
463
|
const cancel = (0, _ahooks.useMemoizedFn)(() => {
|
|
457
|
-
if (status === "confirming_price" || status === "confirming_fast_pay" || status === "credit_insufficient") {
|
|
464
|
+
if (status === "confirming_price" || status === "confirming_fast_pay" || status === "credit_insufficient" || status === "service_suspended") {
|
|
458
465
|
setStatus("idle");
|
|
459
466
|
setContext(null);
|
|
460
467
|
unlock();
|
|
@@ -13,7 +13,9 @@ function useUpsell() {
|
|
|
13
13
|
const {
|
|
14
14
|
session,
|
|
15
15
|
effectiveSessionId,
|
|
16
|
-
refresh
|
|
16
|
+
refresh,
|
|
17
|
+
sessionData,
|
|
18
|
+
setSessionData
|
|
17
19
|
} = (0, _SessionContext.useSessionContext)();
|
|
18
20
|
const {
|
|
19
21
|
currency
|
|
@@ -21,14 +23,14 @@ function useUpsell() {
|
|
|
21
23
|
const currencyId = currency?.id || null;
|
|
22
24
|
const upsell = (0, _ahooks.useMemoizedFn)(async (fromId, toId) => {
|
|
23
25
|
try {
|
|
24
|
-
await (0, _lineItems.performUpsell)(effectiveSessionId, fromId, toId, session, currencyId, refresh);
|
|
26
|
+
await (0, _lineItems.performUpsell)(effectiveSessionId, fromId, toId, session, currencyId, refresh, sessionData, setSessionData);
|
|
25
27
|
} catch (err) {
|
|
26
28
|
console.error("Failed to upsell:", (0, _checkoutAugmented.getErrorMessage)(err));
|
|
27
29
|
}
|
|
28
30
|
});
|
|
29
31
|
const downsell = (0, _ahooks.useMemoizedFn)(async priceId => {
|
|
30
32
|
try {
|
|
31
|
-
await (0, _lineItems.performDownsell)(effectiveSessionId, priceId, session, currencyId, refresh);
|
|
33
|
+
await (0, _lineItems.performDownsell)(effectiveSessionId, priceId, session, currencyId, refresh, sessionData, setSessionData);
|
|
32
34
|
} catch (err) {
|
|
33
35
|
console.error("Failed to downsell:", (0, _checkoutAugmented.getErrorMessage)(err));
|
|
34
36
|
}
|
package/lib/checkout/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TLineItemExpanded, TPaymentMethodExpanded, TPaymentCurrency, TPrice, TCheckoutSessionExpanded, TPaymentIntent, TCustomer } from '@blocklet/payment-types';
|
|
2
|
-
export type SubmitStatus = 'idle' | 'submitting' | 'confirming_price' | 'confirming_fast_pay' | 'credit_insufficient' | 'waiting_did' | 'waiting_stripe' | 'completed' | 'failed';
|
|
2
|
+
export type SubmitStatus = 'idle' | 'submitting' | 'confirming_price' | 'confirming_fast_pay' | 'credit_insufficient' | 'service_suspended' | 'waiting_did' | 'waiting_stripe' | 'completed' | 'failed';
|
|
3
3
|
export type SubmitContext = {
|
|
4
4
|
type: 'price_change';
|
|
5
5
|
changePercent: number;
|
|
@@ -25,6 +25,8 @@ export type SubmitContext = {
|
|
|
25
25
|
type: 'error';
|
|
26
26
|
message: string;
|
|
27
27
|
code?: string;
|
|
28
|
+
} | {
|
|
29
|
+
type: 'service_suspended';
|
|
28
30
|
} | null;
|
|
29
31
|
export interface FieldConfig {
|
|
30
32
|
name: string;
|
|
@@ -69,8 +71,8 @@ export type CheckoutResult = {
|
|
|
69
71
|
export interface UseCheckoutReturn {
|
|
70
72
|
isLoading: boolean;
|
|
71
73
|
error: string | null;
|
|
72
|
-
/** Structured error code
|
|
73
|
-
errorCode: 'SESSION_EXPIRED' | 'EMPTY_LINE_ITEMS' | null;
|
|
74
|
+
/** Structured error code */
|
|
75
|
+
errorCode: 'SESSION_EXPIRED' | 'EMPTY_LINE_ITEMS' | 'STOP_ACCEPTING_ORDERS' | null;
|
|
74
76
|
refresh: () => Promise<void>;
|
|
75
77
|
vendorCount: number;
|
|
76
78
|
product: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blocklet/payment-react-headless",
|
|
3
|
-
"version": "1.26.
|
|
3
|
+
"version": "1.26.3",
|
|
4
4
|
"description": "Headless React hooks for payment-kit checkout",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@arcblock/ws": "^1.28.5",
|
|
37
37
|
"@blocklet/js-sdk": "workspace:*",
|
|
38
|
-
"@blocklet/payment-types": "1.26.
|
|
38
|
+
"@blocklet/payment-types": "1.26.3",
|
|
39
39
|
"@ocap/util": "^1.28.5",
|
|
40
40
|
"ahooks": "^3.8.5",
|
|
41
41
|
"google-libphonenumber": "^3.2.42",
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"publishConfig": {
|
|
61
61
|
"access": "public"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "18c5d045139c572b52465e15c4c63b3e327efab5"
|
|
64
64
|
}
|