@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
|
@@ -12,12 +12,13 @@ import {
|
|
|
12
12
|
} from "../core/lineItems.js";
|
|
13
13
|
import { addCrossSellItem, removeCrossSellItem, fetchCrossSellItem } from "../core/crossSell.js";
|
|
14
14
|
export function useLineItems() {
|
|
15
|
-
const { items, session, effectiveSessionId, isDonation, refresh } = useSessionContext();
|
|
15
|
+
const { items, session, effectiveSessionId, isDonation, refresh, sessionData, setSessionData } = useSessionContext();
|
|
16
16
|
const { currency } = usePaymentMethodContext();
|
|
17
17
|
const currencyId = currency?.id || null;
|
|
18
18
|
const defaultQtyApplied = useRef(false);
|
|
19
19
|
useEffect(() => {
|
|
20
|
-
if (defaultQtyApplied.current || !effectiveSessionId || !items.length || !currencyId || session?.status === "complete")
|
|
20
|
+
if (defaultQtyApplied.current || !effectiveSessionId || !items.length || !currencyId || session?.status === "complete")
|
|
21
|
+
return;
|
|
21
22
|
try {
|
|
22
23
|
const params = new URLSearchParams(window.location.search);
|
|
23
24
|
for (const item of items) {
|
|
@@ -26,7 +27,16 @@ export function useLineItems() {
|
|
|
26
27
|
const qty = Math.max(1, parseInt(qtyStr, 10));
|
|
27
28
|
if (Number.isFinite(qty) && qty !== item.quantity) {
|
|
28
29
|
defaultQtyApplied.current = true;
|
|
29
|
-
adjustQuantity(
|
|
30
|
+
adjustQuantity(
|
|
31
|
+
effectiveSessionId,
|
|
32
|
+
item.price_id,
|
|
33
|
+
qty,
|
|
34
|
+
currencyId,
|
|
35
|
+
session,
|
|
36
|
+
refresh,
|
|
37
|
+
sessionData,
|
|
38
|
+
setSessionData
|
|
39
|
+
);
|
|
30
40
|
break;
|
|
31
41
|
}
|
|
32
42
|
}
|
|
@@ -37,18 +47,18 @@ export function useLineItems() {
|
|
|
37
47
|
const updateQuantity = useMemoizedFn(async (itemId, qty) => {
|
|
38
48
|
if (session?.status === "complete") return;
|
|
39
49
|
try {
|
|
40
|
-
await adjustQuantity(effectiveSessionId, itemId, qty, currencyId, session, refresh);
|
|
50
|
+
await adjustQuantity(effectiveSessionId, itemId, qty, currencyId, session, refresh, sessionData, setSessionData);
|
|
41
51
|
} catch (err) {
|
|
42
52
|
console.error("Failed to update quantity:", getErrorMessage(err));
|
|
43
53
|
}
|
|
44
54
|
});
|
|
45
55
|
const upsell = useMemoizedFn(async (fromId, toId) => {
|
|
46
56
|
if (session?.status === "complete") return;
|
|
47
|
-
await performUpsell(effectiveSessionId, fromId, toId, session, currencyId, refresh);
|
|
57
|
+
await performUpsell(effectiveSessionId, fromId, toId, session, currencyId, refresh, sessionData, setSessionData);
|
|
48
58
|
});
|
|
49
59
|
const downsell = useMemoizedFn(async (priceId) => {
|
|
50
60
|
if (session?.status === "complete") return;
|
|
51
|
-
await performDownsell(effectiveSessionId, priceId, session, currencyId, refresh);
|
|
61
|
+
await performDownsell(effectiveSessionId, priceId, session, currencyId, refresh, sessionData, setSessionData);
|
|
52
62
|
});
|
|
53
63
|
const embeddedCrossSellItem = useMemo(() => getCrossSellItem(items), [items]);
|
|
54
64
|
const [fetchedCrossSellItem, setFetchedCrossSellItem] = useState(null);
|
|
@@ -80,7 +90,15 @@ export function useLineItems() {
|
|
|
80
90
|
if (session?.status === "complete") return;
|
|
81
91
|
if (!crossSellItem) return;
|
|
82
92
|
try {
|
|
83
|
-
await addCrossSellItem(
|
|
93
|
+
await addCrossSellItem(
|
|
94
|
+
effectiveSessionId,
|
|
95
|
+
crossSellItem.id,
|
|
96
|
+
session,
|
|
97
|
+
currencyId,
|
|
98
|
+
refresh,
|
|
99
|
+
sessionData,
|
|
100
|
+
setSessionData
|
|
101
|
+
);
|
|
84
102
|
} catch (err) {
|
|
85
103
|
console.error("Failed to add cross-sell:", getErrorMessage(err));
|
|
86
104
|
}
|
|
@@ -88,7 +106,7 @@ export function useLineItems() {
|
|
|
88
106
|
const removeCrossSell = useMemoizedFn(async () => {
|
|
89
107
|
if (session?.status === "complete") return;
|
|
90
108
|
try {
|
|
91
|
-
await removeCrossSellItem(effectiveSessionId, session, currencyId, refresh);
|
|
109
|
+
await removeCrossSellItem(effectiveSessionId, session, currencyId, refresh, sessionData, setSessionData);
|
|
92
110
|
} catch (err) {
|
|
93
111
|
console.error("Failed to remove cross-sell:", getErrorMessage(err));
|
|
94
112
|
}
|
|
@@ -97,7 +115,16 @@ export function useLineItems() {
|
|
|
97
115
|
if (session?.status === "complete") return;
|
|
98
116
|
if (!isDonation) return;
|
|
99
117
|
try {
|
|
100
|
-
await changeDonationAmount(
|
|
118
|
+
await changeDonationAmount(
|
|
119
|
+
effectiveSessionId,
|
|
120
|
+
priceId,
|
|
121
|
+
amount,
|
|
122
|
+
session,
|
|
123
|
+
currencyId,
|
|
124
|
+
refresh,
|
|
125
|
+
sessionData,
|
|
126
|
+
setSessionData
|
|
127
|
+
);
|
|
101
128
|
} catch (err) {
|
|
102
129
|
console.error("Failed to change amount:", getErrorMessage(err));
|
|
103
130
|
}
|
|
@@ -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;
|
|
@@ -9,8 +9,8 @@ import {
|
|
|
9
9
|
findMethodAndCurrency,
|
|
10
10
|
buildPaymentTypes
|
|
11
11
|
} from "../core/paymentMethod.js";
|
|
12
|
-
export function usePaymentMethod(sessionData, sessionId, refreshSession) {
|
|
13
|
-
const methods = sessionData?.paymentMethods || [];
|
|
12
|
+
export function usePaymentMethod(sessionData, sessionId, refreshSession, setSessionData) {
|
|
13
|
+
const methods = useMemo(() => sessionData?.paymentMethods || [], [sessionData?.paymentMethods]);
|
|
14
14
|
const session = sessionData?.checkoutSession;
|
|
15
15
|
const [currencyId, setCurrencyId] = useState(() => getInitialCurrencyId(session, methods));
|
|
16
16
|
const [switching, setSwitching] = useState(false);
|
|
@@ -43,7 +43,7 @@ export function usePaymentMethod(sessionData, sessionId, refreshSession) {
|
|
|
43
43
|
if (!method || !sessionId) return;
|
|
44
44
|
setSwitching(true);
|
|
45
45
|
try {
|
|
46
|
-
await api.put(API.SWITCH_CURRENCY(sessionId), {
|
|
46
|
+
const { data } = await api.put(API.SWITCH_CURRENCY(sessionId), {
|
|
47
47
|
currency_id: newCurrencyId,
|
|
48
48
|
payment_method_id: method.id
|
|
49
49
|
});
|
|
@@ -55,7 +55,11 @@ export function usePaymentMethod(sessionData, sessionId, refreshSession) {
|
|
|
55
55
|
);
|
|
56
56
|
} catch {
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
if (sessionData && setSessionData) {
|
|
59
|
+
setSessionData({ ...sessionData, checkoutSession: data, quotes: void 0 });
|
|
60
|
+
} else {
|
|
61
|
+
await refreshSession(true);
|
|
62
|
+
}
|
|
59
63
|
} catch (err) {
|
|
60
64
|
console.error("Failed to switch currency:", getErrorMessage(err));
|
|
61
65
|
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;
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
isPromotionActive
|
|
12
12
|
} from "../core/promotion.js";
|
|
13
13
|
import { updateSlippage } from "../core/submit.js";
|
|
14
|
-
export function usePricing(sessionData, sessionId, currency, isStripe, refreshSession, paymentMethodType) {
|
|
14
|
+
export function usePricing(sessionData, sessionId, currency, isStripe, refreshSession, paymentMethodType, switching) {
|
|
15
15
|
const session = sessionData?.checkoutSession;
|
|
16
16
|
const items = session?.line_items || [];
|
|
17
17
|
const [exchangeRate, setExchangeRate] = useState(null);
|
|
@@ -30,7 +30,7 @@ export function usePricing(sessionData, sessionId, currency, isStripe, refreshSe
|
|
|
30
30
|
const mountedRef = useRef(true);
|
|
31
31
|
const hasDynamicPricing = useMemo(() => checkHasDynamicPricing(items), [items]);
|
|
32
32
|
const fetchRate = useMemoizedFn(async () => {
|
|
33
|
-
if (!sessionId || !hasDynamicPricing || isStripe) {
|
|
33
|
+
if (!sessionId || !hasDynamicPricing || isStripe || switching) {
|
|
34
34
|
setRateStatus(hasDynamicPricing ? "unavailable" : "available");
|
|
35
35
|
if (isStripe) {
|
|
36
36
|
setExchangeRate(null);
|
|
@@ -62,7 +62,7 @@ export function usePricing(sessionData, sessionId, currency, isStripe, refreshSe
|
|
|
62
62
|
});
|
|
63
63
|
useEffect(() => {
|
|
64
64
|
mountedRef.current = true;
|
|
65
|
-
if (!hasDynamicPricing || isStripe || !sessionId || session?.status === "complete") {
|
|
65
|
+
if (!hasDynamicPricing || isStripe || switching || !sessionId || session?.status === "complete") {
|
|
66
66
|
if (isStripe) {
|
|
67
67
|
setExchangeRate(null);
|
|
68
68
|
setRateProvider(null);
|
|
@@ -95,7 +95,7 @@ export function usePricing(sessionData, sessionId, currency, isStripe, refreshSe
|
|
|
95
95
|
if (pollingRef.current) clearTimeout(pollingRef.current);
|
|
96
96
|
document.removeEventListener("visibilitychange", handleVisibility);
|
|
97
97
|
};
|
|
98
|
-
}, [hasDynamicPricing, isStripe, sessionId, currency?.id, session?.status]);
|
|
98
|
+
}, [hasDynamicPricing, isStripe, switching, sessionId, currency?.id, session?.status]);
|
|
99
99
|
const amounts = useMemo(
|
|
100
100
|
() => calculateAmounts(items, currency, session, exchangeRate, hasDynamicPricing, paymentMethodType),
|
|
101
101
|
[items, currency, exchangeRate, hasDynamicPricing, session, paymentMethodType]
|
|
@@ -328,6 +328,11 @@ export function useSubmit(sessionData, sessionId, currencyId, isStripe, isCredit
|
|
|
328
328
|
});
|
|
329
329
|
return;
|
|
330
330
|
}
|
|
331
|
+
if (errorCode === "STOP_ACCEPTING_ORDERS") {
|
|
332
|
+
setStatus("service_suspended");
|
|
333
|
+
setContext({ type: "service_suspended" });
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
331
336
|
if (errorCode === "UNIFIED_APP_REQUIRED" || errorCode === "CUSTOMER_LIMITED") {
|
|
332
337
|
setStatus("failed");
|
|
333
338
|
setContext({
|
|
@@ -443,7 +448,7 @@ export function useSubmit(sessionData, sessionId, currencyId, isStripe, isCredit
|
|
|
443
448
|
}
|
|
444
449
|
});
|
|
445
450
|
const cancel = useMemoizedFn(() => {
|
|
446
|
-
if (status === "confirming_price" || status === "confirming_fast_pay" || status === "credit_insufficient") {
|
|
451
|
+
if (status === "confirming_price" || status === "confirming_fast_pay" || status === "credit_insufficient" || status === "service_suspended") {
|
|
447
452
|
setStatus("idle");
|
|
448
453
|
setContext(null);
|
|
449
454
|
unlock();
|
|
@@ -4,19 +4,19 @@ import { useSessionContext } from "../context/SessionContext.js";
|
|
|
4
4
|
import { usePaymentMethodContext } from "../context/PaymentMethodContext.js";
|
|
5
5
|
import { performUpsell, performDownsell } from "../core/lineItems.js";
|
|
6
6
|
export function useUpsell() {
|
|
7
|
-
const { session, effectiveSessionId, refresh } = useSessionContext();
|
|
7
|
+
const { session, effectiveSessionId, refresh, sessionData, setSessionData } = useSessionContext();
|
|
8
8
|
const { currency } = usePaymentMethodContext();
|
|
9
9
|
const currencyId = currency?.id || null;
|
|
10
10
|
const upsell = useMemoizedFn(async (fromId, toId) => {
|
|
11
11
|
try {
|
|
12
|
-
await performUpsell(effectiveSessionId, fromId, toId, session, currencyId, refresh);
|
|
12
|
+
await performUpsell(effectiveSessionId, fromId, toId, session, currencyId, refresh, sessionData, setSessionData);
|
|
13
13
|
} catch (err) {
|
|
14
14
|
console.error("Failed to upsell:", getErrorMessage(err));
|
|
15
15
|
}
|
|
16
16
|
});
|
|
17
17
|
const downsell = useMemoizedFn(async (priceId) => {
|
|
18
18
|
try {
|
|
19
|
-
await performDownsell(effectiveSessionId, priceId, session, currencyId, refresh);
|
|
19
|
+
await performDownsell(effectiveSessionId, priceId, session, currencyId, refresh, sessionData, setSessionData);
|
|
20
20
|
} catch (err) {
|
|
21
21
|
console.error("Failed to downsell:", getErrorMessage(err));
|
|
22
22
|
}
|
package/es/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: {
|
|
@@ -37,10 +37,11 @@ function CheckoutProvider({
|
|
|
37
37
|
} = (0, _useCheckoutSession.useCheckoutSession)(sessionId);
|
|
38
38
|
const session = sessionData?.checkoutSession;
|
|
39
39
|
const effectiveSessionId = resolvedSessionId || sessionId;
|
|
40
|
-
const items = session?.line_items || [];
|
|
40
|
+
const items = (0, _react.useMemo)(() => session?.line_items || [], [session?.line_items]);
|
|
41
41
|
const isDonation = session?.submit_type === "donate";
|
|
42
42
|
const sessionValue = (0, _react.useMemo)(() => ({
|
|
43
43
|
sessionData,
|
|
44
|
+
setSessionData,
|
|
44
45
|
sessionId,
|
|
45
46
|
effectiveSessionId,
|
|
46
47
|
isLoading,
|
|
@@ -54,15 +55,19 @@ function CheckoutProvider({
|
|
|
54
55
|
product,
|
|
55
56
|
subscription,
|
|
56
57
|
pageInfo
|
|
57
|
-
}), [sessionData, sessionId, effectiveSessionId, isLoading, error, errorCode, refresh, items, session, isDonation, vendorCount, product, subscription, pageInfo]);
|
|
58
|
-
const paymentMethodHook = (0, _usePaymentMethod.usePaymentMethod)(sessionData, effectiveSessionId, refresh);
|
|
58
|
+
}), [sessionData, setSessionData, sessionId, effectiveSessionId, isLoading, error, errorCode, refresh, items, session, isDonation, vendorCount, product, subscription, pageInfo]);
|
|
59
|
+
const paymentMethodHook = (0, _usePaymentMethod.usePaymentMethod)(sessionData, effectiveSessionId, refresh, setSessionData);
|
|
59
60
|
const prevCurrencyRef = (0, _react.useRef)(null);
|
|
60
61
|
(0, _react.useEffect)(() => {
|
|
61
62
|
const currId = paymentMethodHook.currency?.id || null;
|
|
62
63
|
if (!currId || !session || session.status === "complete") return;
|
|
63
64
|
if (prevCurrencyRef.current === null || currId !== prevCurrencyRef.current) {
|
|
64
65
|
prevCurrencyRef.current = currId;
|
|
65
|
-
(0, _lineItems.recalculatePromotionIfNeeded)(session, effectiveSessionId, currId).then(
|
|
66
|
+
(0, _lineItems.recalculatePromotionIfNeeded)(session, effectiveSessionId, currId).then(recalculated => {
|
|
67
|
+
if (recalculated) {
|
|
68
|
+
refresh(true);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
66
71
|
}
|
|
67
72
|
}, [paymentMethodHook.currency?.id, session?.id]);
|
|
68
73
|
const paymentMethodValue = (0, _react.useMemo)(() => ({
|
|
@@ -78,7 +83,9 @@ function CheckoutProvider({
|
|
|
78
83
|
types: paymentMethodHook.types,
|
|
79
84
|
setCurrency: paymentMethodHook.setCurrency,
|
|
80
85
|
stripe: paymentMethodHook.stripe
|
|
81
|
-
}),
|
|
86
|
+
}),
|
|
87
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
88
|
+
[paymentMethodHook.current, paymentMethodHook.currency, paymentMethodHook.available, paymentMethodHook.currencies, paymentMethodHook.isStripe, paymentMethodHook.isCrypto, paymentMethodHook.isCredit, paymentMethodHook.switching, paymentMethodHook.setType, paymentMethodHook.types, paymentMethodHook.setCurrency, paymentMethodHook.stripe]);
|
|
82
89
|
const [exchangeRate, setExchangeRate] = (0, _react.useState)(null);
|
|
83
90
|
const [rateProvider, setRateProvider] = (0, _react.useState)(null);
|
|
84
91
|
const [rateProviderDisplay, setRateProviderDisplay] = (0, _react.useState)(null);
|
|
@@ -89,7 +96,7 @@ function CheckoutProvider({
|
|
|
89
96
|
const mountedRef = (0, _react.useRef)(true);
|
|
90
97
|
const hasDynamicPricing = (0, _react.useMemo)(() => (0, _exchangeRate.checkHasDynamicPricing)(items), [items]);
|
|
91
98
|
const fetchRate = (0, _ahooks.useMemoizedFn)(async () => {
|
|
92
|
-
if (!effectiveSessionId || !hasDynamicPricing || paymentMethodHook.isStripe) {
|
|
99
|
+
if (!effectiveSessionId || !hasDynamicPricing || paymentMethodHook.isStripe || paymentMethodHook.switching) {
|
|
93
100
|
setRateStatus(hasDynamicPricing ? "unavailable" : "available");
|
|
94
101
|
if (paymentMethodHook.isStripe) {
|
|
95
102
|
setExchangeRate(null);
|
|
@@ -123,17 +130,19 @@ function CheckoutProvider({
|
|
|
123
130
|
});
|
|
124
131
|
(0, _react.useEffect)(() => {
|
|
125
132
|
mountedRef.current = true;
|
|
126
|
-
if (!hasDynamicPricing || paymentMethodHook.isStripe || !effectiveSessionId || session?.status === "complete") {
|
|
133
|
+
if (!hasDynamicPricing || paymentMethodHook.isStripe || paymentMethodHook.switching || !effectiveSessionId || session?.status === "complete") {
|
|
127
134
|
if (paymentMethodHook.isStripe) {
|
|
128
135
|
setExchangeRate(null);
|
|
129
136
|
setRateProvider(null);
|
|
130
137
|
setRateProviderDisplay(null);
|
|
131
138
|
setRateFetchedAt(null);
|
|
132
139
|
}
|
|
133
|
-
if (
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
140
|
+
if (!paymentMethodHook.switching) {
|
|
141
|
+
if (session?.status === "complete") {
|
|
142
|
+
setRateStatus("available");
|
|
143
|
+
} else {
|
|
144
|
+
setRateStatus(hasDynamicPricing ? "unavailable" : "available");
|
|
145
|
+
}
|
|
137
146
|
}
|
|
138
147
|
return void 0;
|
|
139
148
|
}
|
|
@@ -150,7 +159,7 @@ function CheckoutProvider({
|
|
|
150
159
|
if (intervalRef.current) clearInterval(intervalRef.current);
|
|
151
160
|
document.removeEventListener("visibilitychange", handleVisibility);
|
|
152
161
|
};
|
|
153
|
-
}, [hasDynamicPricing, paymentMethodHook.isStripe, effectiveSessionId, paymentMethodHook.currency?.id, session?.status]);
|
|
162
|
+
}, [hasDynamicPricing, paymentMethodHook.isStripe, paymentMethodHook.switching, effectiveSessionId, paymentMethodHook.currency?.id, session?.status]);
|
|
154
163
|
const exchangeRateValue = (0, _react.useMemo)(() => ({
|
|
155
164
|
rate: exchangeRate,
|
|
156
165
|
provider: rateProvider,
|
|
@@ -2,11 +2,13 @@ import type { TLineItemExpanded, TCheckoutSessionExpanded } from '@blocklet/paym
|
|
|
2
2
|
import type { SessionData } from '../hooks/useCheckoutSession';
|
|
3
3
|
export interface SessionContextValue {
|
|
4
4
|
sessionData: SessionData | null;
|
|
5
|
+
/** Directly replace session data (e.g. using PUT response to skip redundant GET) */
|
|
6
|
+
setSessionData: (data: SessionData) => void;
|
|
5
7
|
sessionId: string;
|
|
6
8
|
effectiveSessionId: string;
|
|
7
9
|
isLoading: boolean;
|
|
8
10
|
error: string | null;
|
|
9
|
-
errorCode: 'SESSION_EXPIRED' | 'EMPTY_LINE_ITEMS' | null;
|
|
11
|
+
errorCode: 'SESSION_EXPIRED' | 'EMPTY_LINE_ITEMS' | 'STOP_ACCEPTING_ORDERS' | null;
|
|
10
12
|
refresh: (forceRefresh?: boolean) => Promise<void>;
|
|
11
13
|
items: TLineItemExpanded[];
|
|
12
14
|
session: TCheckoutSessionExpanded | null | undefined;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TCheckoutSessionExpanded, TPrice } from '@blocklet/payment-types';
|
|
2
|
-
|
|
3
|
-
export declare function
|
|
2
|
+
import type { SessionData } from '../hooks/useCheckoutSession';
|
|
3
|
+
export declare function addCrossSellItem(sessionId: string, crossSellItemId: string, session: TCheckoutSessionExpanded | undefined | null, currencyId: string | null | undefined, refresh: (force?: boolean) => Promise<void>, sessionData?: SessionData | null, setSessionData?: (data: SessionData) => void): Promise<void>;
|
|
4
|
+
export declare function removeCrossSellItem(sessionId: string, session: TCheckoutSessionExpanded | undefined | null, currencyId: string | null | undefined, refresh: (force?: boolean) => Promise<void>, sessionData?: SessionData | null, setSessionData?: (data: SessionData) => void): Promise<void>;
|
|
4
5
|
export declare function fetchCrossSellItem(sessionId: string): Promise<TPrice | null>;
|
|
@@ -10,17 +10,45 @@ var _api = _interopRequireWildcard(require("../../shared/api"));
|
|
|
10
10
|
var _lineItems = require("./lineItems");
|
|
11
11
|
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); }
|
|
12
12
|
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; }
|
|
13
|
-
async function addCrossSellItem(sessionId, crossSellItemId, session, currencyId, refresh) {
|
|
14
|
-
|
|
13
|
+
async function addCrossSellItem(sessionId, crossSellItemId, session, currencyId, refresh, sessionData, setSessionData) {
|
|
14
|
+
const {
|
|
15
|
+
data
|
|
16
|
+
} = await _api.default.put(_api.API.CROSS_SELL(sessionId), {
|
|
15
17
|
to: crossSellItemId
|
|
16
18
|
});
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
let finalSession = data;
|
|
20
|
+
if (data.discounts?.length) {
|
|
21
|
+
const recalculated = await (0, _lineItems.recalculatePromotionIfNeeded)(session, sessionId, currencyId);
|
|
22
|
+
if (recalculated) finalSession = recalculated;
|
|
23
|
+
}
|
|
24
|
+
if (sessionData && setSessionData) {
|
|
25
|
+
setSessionData({
|
|
26
|
+
...sessionData,
|
|
27
|
+
checkoutSession: finalSession,
|
|
28
|
+
quotes: void 0
|
|
29
|
+
});
|
|
30
|
+
} else {
|
|
31
|
+
await refresh(true);
|
|
32
|
+
}
|
|
19
33
|
}
|
|
20
|
-
async function removeCrossSellItem(sessionId, session, currencyId, refresh) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
await
|
|
34
|
+
async function removeCrossSellItem(sessionId, session, currencyId, refresh, sessionData, setSessionData) {
|
|
35
|
+
const {
|
|
36
|
+
data
|
|
37
|
+
} = await _api.default.delete(_api.API.CROSS_SELL(sessionId));
|
|
38
|
+
let finalSession = data;
|
|
39
|
+
if (data.discounts?.length) {
|
|
40
|
+
const recalculated = await (0, _lineItems.recalculatePromotionIfNeeded)(session, sessionId, currencyId);
|
|
41
|
+
if (recalculated) finalSession = recalculated;
|
|
42
|
+
}
|
|
43
|
+
if (sessionData && setSessionData) {
|
|
44
|
+
setSessionData({
|
|
45
|
+
...sessionData,
|
|
46
|
+
checkoutSession: finalSession,
|
|
47
|
+
quotes: void 0
|
|
48
|
+
});
|
|
49
|
+
} else {
|
|
50
|
+
await refresh(true);
|
|
51
|
+
}
|
|
24
52
|
}
|
|
25
53
|
const pendingFetches = /* @__PURE__ */new Map();
|
|
26
54
|
function fetchCrossSellItem(sessionId) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { TCheckoutSessionExpanded, TLineItemExpanded, TPrice } from '@blocklet/payment-types';
|
|
2
|
-
|
|
3
|
-
export declare function
|
|
4
|
-
export declare function
|
|
5
|
-
export declare function
|
|
6
|
-
export declare function
|
|
2
|
+
import type { SessionData } from '../hooks/useCheckoutSession';
|
|
3
|
+
export declare function recalculatePromotionIfNeeded(session: TCheckoutSessionExpanded | undefined | null, sessionId: string, currencyId: string | null | undefined): Promise<TCheckoutSessionExpanded | null>;
|
|
4
|
+
export declare function adjustQuantity(sessionId: string, itemId: string, qty: number, currencyId: string | null | undefined, session: TCheckoutSessionExpanded | undefined | null, refresh: (force?: boolean) => Promise<void>, sessionData?: SessionData | null, setSessionData?: (data: SessionData) => void): Promise<void>;
|
|
5
|
+
export declare function performUpsell(sessionId: string, fromId: string, toId: string, session: TCheckoutSessionExpanded | undefined | null, currencyId: string | null | undefined, refresh: (force?: boolean) => Promise<void>, sessionData?: SessionData | null, setSessionData?: (data: SessionData) => void): Promise<void>;
|
|
6
|
+
export declare function performDownsell(sessionId: string, priceId: string, session: TCheckoutSessionExpanded | undefined | null, currencyId: string | null | undefined, refresh: (force?: boolean) => Promise<void>, sessionData?: SessionData | null, setSessionData?: (data: SessionData) => void): Promise<void>;
|
|
7
|
+
export declare function changeDonationAmount(sessionId: string, priceId: string, amount: string, session: TCheckoutSessionExpanded | undefined | null, currencyId: string | null | undefined, refresh: (force?: boolean) => Promise<void>, sessionData?: SessionData | null, setSessionData?: (data: SessionData) => void): Promise<void>;
|
|
7
8
|
export declare function getCrossSellItem(items: TLineItemExpanded[]): TPrice | null;
|
|
@@ -14,56 +14,110 @@ var _promotion = require("./promotion");
|
|
|
14
14
|
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); }
|
|
15
15
|
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; }
|
|
16
16
|
async function recalculatePromotionIfNeeded(session, sessionId, currencyId) {
|
|
17
|
-
if (!(0, _promotion.hasAppliedDiscounts)(session)) return;
|
|
17
|
+
if (!(0, _promotion.hasAppliedDiscounts)(session)) return null;
|
|
18
18
|
try {
|
|
19
|
-
await (0, _promotion.recalculatePromotion)(sessionId, currencyId);
|
|
20
|
-
} catch {
|
|
19
|
+
return await (0, _promotion.recalculatePromotion)(sessionId, currencyId);
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
21
23
|
}
|
|
22
|
-
async function adjustQuantity(sessionId, itemId, qty, currencyId, session, refresh) {
|
|
23
|
-
|
|
24
|
+
async function adjustQuantity(sessionId, itemId, qty, currencyId, session, refresh, sessionData, setSessionData) {
|
|
25
|
+
const {
|
|
26
|
+
data
|
|
27
|
+
} = await _api.default.put(_api.API.ADJUST_QUANTITY(sessionId), {
|
|
24
28
|
itemId,
|
|
25
29
|
quantity: qty,
|
|
26
30
|
currency_id: currencyId
|
|
27
31
|
});
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
let finalSession = data;
|
|
33
|
+
if (data.discounts?.length || (0, _promotion.hasAppliedDiscounts)(session)) {
|
|
34
|
+
const recalculated = await recalculatePromotionIfNeeded(session, sessionId, currencyId);
|
|
35
|
+
if (recalculated) finalSession = recalculated;
|
|
36
|
+
}
|
|
37
|
+
if (sessionData && setSessionData) {
|
|
38
|
+
setSessionData({
|
|
39
|
+
...sessionData,
|
|
40
|
+
checkoutSession: finalSession,
|
|
41
|
+
quotes: void 0
|
|
42
|
+
});
|
|
43
|
+
} else {
|
|
44
|
+
await refresh(true);
|
|
45
|
+
}
|
|
30
46
|
}
|
|
31
|
-
async function performUpsell(sessionId, fromId, toId, session, currencyId, refresh) {
|
|
47
|
+
async function performUpsell(sessionId, fromId, toId, session, currencyId, refresh, sessionData, setSessionData) {
|
|
32
48
|
if ((session?.line_items?.length || 0) > 1) {
|
|
33
49
|
try {
|
|
34
50
|
await _api.default.delete(_api.API.CROSS_SELL(sessionId));
|
|
35
51
|
} catch {}
|
|
36
52
|
}
|
|
37
|
-
|
|
53
|
+
const {
|
|
54
|
+
data
|
|
55
|
+
} = await _api.default.put(_api.API.UPSELL(sessionId), {
|
|
38
56
|
from: fromId,
|
|
39
57
|
to: toId
|
|
40
58
|
});
|
|
41
|
-
|
|
42
|
-
|
|
59
|
+
let finalSession = data;
|
|
60
|
+
if (data.discounts?.length || (0, _promotion.hasAppliedDiscounts)(session)) {
|
|
61
|
+
const recalculated = await recalculatePromotionIfNeeded(session, sessionId, currencyId);
|
|
62
|
+
if (recalculated) finalSession = recalculated;
|
|
63
|
+
}
|
|
64
|
+
if (sessionData && setSessionData) {
|
|
65
|
+
setSessionData({
|
|
66
|
+
...sessionData,
|
|
67
|
+
checkoutSession: finalSession,
|
|
68
|
+
quotes: void 0
|
|
69
|
+
});
|
|
70
|
+
} else {
|
|
71
|
+
await refresh(true);
|
|
72
|
+
}
|
|
43
73
|
}
|
|
44
|
-
async function performDownsell(sessionId, priceId, session, currencyId, refresh) {
|
|
74
|
+
async function performDownsell(sessionId, priceId, session, currencyId, refresh, sessionData, setSessionData) {
|
|
45
75
|
if ((session?.line_items?.length || 0) > 1) {
|
|
46
76
|
try {
|
|
47
77
|
await _api.default.delete(_api.API.CROSS_SELL(sessionId));
|
|
48
78
|
} catch {}
|
|
49
79
|
}
|
|
50
|
-
|
|
80
|
+
const {
|
|
81
|
+
data
|
|
82
|
+
} = await _api.default.put(_api.API.DOWNSELL(sessionId), {
|
|
51
83
|
from: priceId
|
|
52
84
|
});
|
|
53
|
-
|
|
54
|
-
|
|
85
|
+
let finalSession = data;
|
|
86
|
+
if (data.discounts?.length || (0, _promotion.hasAppliedDiscounts)(session)) {
|
|
87
|
+
const recalculated = await recalculatePromotionIfNeeded(session, sessionId, currencyId);
|
|
88
|
+
if (recalculated) finalSession = recalculated;
|
|
89
|
+
}
|
|
90
|
+
if (sessionData && setSessionData) {
|
|
91
|
+
setSessionData({
|
|
92
|
+
...sessionData,
|
|
93
|
+
checkoutSession: finalSession,
|
|
94
|
+
quotes: void 0
|
|
95
|
+
});
|
|
96
|
+
} else {
|
|
97
|
+
await refresh(true);
|
|
98
|
+
}
|
|
55
99
|
}
|
|
56
|
-
async function changeDonationAmount(sessionId, priceId, amount, session, currencyId, refresh) {
|
|
100
|
+
async function changeDonationAmount(sessionId, priceId, amount, session, currencyId, refresh, sessionData, setSessionData) {
|
|
57
101
|
const {
|
|
58
102
|
data
|
|
59
103
|
} = await _api.default.put(_api.API.CHANGE_AMOUNT(sessionId), {
|
|
60
104
|
priceId,
|
|
61
105
|
amount
|
|
62
106
|
});
|
|
107
|
+
let finalSession = data;
|
|
63
108
|
if (data?.discounts?.length) {
|
|
64
|
-
await recalculatePromotionIfNeeded(session, sessionId, currencyId);
|
|
109
|
+
const recalculated = await recalculatePromotionIfNeeded(session, sessionId, currencyId);
|
|
110
|
+
if (recalculated) finalSession = recalculated;
|
|
111
|
+
}
|
|
112
|
+
if (sessionData && setSessionData) {
|
|
113
|
+
setSessionData({
|
|
114
|
+
...sessionData,
|
|
115
|
+
checkoutSession: finalSession,
|
|
116
|
+
quotes: void 0
|
|
117
|
+
});
|
|
118
|
+
} else {
|
|
119
|
+
await refresh(true);
|
|
65
120
|
}
|
|
66
|
-
await refresh(true);
|
|
67
121
|
}
|
|
68
122
|
function getCrossSellItem(items) {
|
|
69
123
|
for (const item of items) {
|
|
@@ -36,33 +36,39 @@ function getCurrencyStorageKey(did) {
|
|
|
36
36
|
return did ? `${CURRENCY_PREFERENCE_KEY}:${did}` : CURRENCY_PREFERENCE_KEY;
|
|
37
37
|
}
|
|
38
38
|
function getInitialCurrencyId(session, methods) {
|
|
39
|
+
const availableCurrencyIds = new Set(methods.flatMap(m => (m.payment_currencies || []).map(c => c.id)).filter(Boolean));
|
|
40
|
+
const isAvailable = currencyId => !!currencyId && availableCurrencyIds.has(currencyId);
|
|
41
|
+
if (session?.discounts?.length && isAvailable(session.currency_id)) {
|
|
42
|
+
return session.currency_id;
|
|
43
|
+
}
|
|
39
44
|
if (typeof window !== "undefined") {
|
|
40
45
|
try {
|
|
41
46
|
const params = new URLSearchParams(window.location.search);
|
|
42
47
|
const urlCurrency = params.get("currencyId") || params.get("currency_id");
|
|
43
|
-
if (urlCurrency) return urlCurrency;
|
|
48
|
+
if (isAvailable(urlCurrency)) return urlCurrency;
|
|
44
49
|
} catch {}
|
|
45
50
|
const user = session?.user;
|
|
46
51
|
if (user && !hasDidWallet(user)) {
|
|
47
52
|
const stripeMethod = methods.find(m => m.type === "stripe");
|
|
48
53
|
const stripeCurrency = stripeMethod?.payment_currencies?.[0];
|
|
49
|
-
if (stripeCurrency) return stripeCurrency
|
|
54
|
+
if (isAvailable(stripeCurrency?.id)) return stripeCurrency?.id;
|
|
50
55
|
}
|
|
51
56
|
try {
|
|
52
57
|
const did = session?.user?.did;
|
|
53
58
|
const stored = localStorage.getItem(getCurrencyStorageKey(did));
|
|
54
|
-
if (stored) return stored;
|
|
59
|
+
if (isAvailable(stored)) return stored;
|
|
55
60
|
} catch {}
|
|
56
61
|
}
|
|
57
|
-
|
|
62
|
+
if (isAvailable(session?.currency_id)) {
|
|
63
|
+
return session?.currency_id;
|
|
64
|
+
}
|
|
65
|
+
return methods[0]?.payment_currencies?.[0]?.id || null;
|
|
58
66
|
}
|
|
59
67
|
function findMethodAndCurrency(methods, currencyId) {
|
|
60
68
|
if (!currencyId) {
|
|
61
|
-
const first2 = methods[0];
|
|
62
|
-
const firstCurrency = first2?.payment_currencies?.[0] || null;
|
|
63
69
|
return {
|
|
64
|
-
method:
|
|
65
|
-
currency:
|
|
70
|
+
method: null,
|
|
71
|
+
currency: null
|
|
66
72
|
};
|
|
67
73
|
}
|
|
68
74
|
for (const method of methods) {
|