@blocklet/payment-react-headless 1.26.1 → 1.26.2
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 +16 -4
- package/es/checkout/context/SessionContext.d.ts +2 -0
- package/es/checkout/core/crossSell.d.ts +3 -2
- package/es/checkout/core/crossSell.js +24 -8
- package/es/checkout/core/lineItems.d.ts +6 -5
- package/es/checkout/core/lineItems.js +48 -17
- 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 +14 -9
- package/es/checkout/hooks/useCrossSell.js +11 -3
- package/es/checkout/hooks/useLineItems.js +36 -9
- package/es/checkout/hooks/usePaymentMethod.d.ts +1 -1
- package/es/checkout/hooks/usePaymentMethod.js +14 -4
- package/es/checkout/hooks/useUpsell.js +3 -3
- package/lib/checkout/context/CheckoutProvider.js +13 -4
- package/lib/checkout/context/SessionContext.d.ts +2 -0
- package/lib/checkout/core/crossSell.d.ts +3 -2
- package/lib/checkout/core/crossSell.js +36 -8
- package/lib/checkout/core/lineItems.d.ts +6 -5
- package/lib/checkout/core/lineItems.js +72 -18
- 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 +18 -9
- package/lib/checkout/hooks/useCrossSell.js +5 -3
- package/lib/checkout/hooks/useLineItems.js +10 -8
- package/lib/checkout/hooks/usePaymentMethod.d.ts +1 -1
- package/lib/checkout/hooks/usePaymentMethod.js +20 -4
- package/lib/checkout/hooks/useUpsell.js +5 -3
- package/package.json +3 -3
- package/src/checkout/context/CheckoutProvider.tsx +18 -5
- package/src/checkout/context/SessionContext.ts +2 -0
- package/src/checkout/core/crossSell.ts +29 -8
- package/src/checkout/core/lineItems.ts +62 -18
- package/src/checkout/core/promotion.ts +6 -2
- package/src/checkout/hooks/useBillingInterval.ts +8 -5
- package/src/checkout/hooks/useCheckout.ts +15 -10
- package/src/checkout/hooks/useCrossSell.ts +11 -3
- package/src/checkout/hooks/useLineItems.ts +42 -9
- package/src/checkout/hooks/usePaymentMethod.ts +17 -5
- package/src/checkout/hooks/useUpsell.ts +3 -3
|
@@ -28,11 +28,12 @@ export function CheckoutProvider({ sessionId, children }) {
|
|
|
28
28
|
} = useCheckoutSession(sessionId);
|
|
29
29
|
const session = sessionData?.checkoutSession;
|
|
30
30
|
const effectiveSessionId = resolvedSessionId || sessionId;
|
|
31
|
-
const items = session?.line_items || [];
|
|
31
|
+
const items = useMemo(() => session?.line_items || [], [session?.line_items]);
|
|
32
32
|
const isDonation = session?.submit_type === "donate";
|
|
33
33
|
const sessionValue = useMemo(
|
|
34
34
|
() => ({
|
|
35
35
|
sessionData,
|
|
36
|
+
setSessionData,
|
|
36
37
|
sessionId,
|
|
37
38
|
effectiveSessionId,
|
|
38
39
|
isLoading,
|
|
@@ -49,6 +50,7 @@ export function CheckoutProvider({ sessionId, children }) {
|
|
|
49
50
|
}),
|
|
50
51
|
[
|
|
51
52
|
sessionData,
|
|
53
|
+
setSessionData,
|
|
52
54
|
sessionId,
|
|
53
55
|
effectiveSessionId,
|
|
54
56
|
isLoading,
|
|
@@ -64,14 +66,18 @@ export function CheckoutProvider({ sessionId, children }) {
|
|
|
64
66
|
pageInfo
|
|
65
67
|
]
|
|
66
68
|
);
|
|
67
|
-
const paymentMethodHook = usePaymentMethodHook(sessionData, effectiveSessionId, refresh);
|
|
69
|
+
const paymentMethodHook = usePaymentMethodHook(sessionData, effectiveSessionId, refresh, setSessionData);
|
|
68
70
|
const prevCurrencyRef = useRef(null);
|
|
69
71
|
useEffect(() => {
|
|
70
72
|
const currId = paymentMethodHook.currency?.id || null;
|
|
71
73
|
if (!currId || !session || session.status === "complete") return;
|
|
72
74
|
if (prevCurrencyRef.current === null || currId !== prevCurrencyRef.current) {
|
|
73
75
|
prevCurrencyRef.current = currId;
|
|
74
|
-
recalculatePromotionIfNeeded(session, effectiveSessionId, currId).then(() =>
|
|
76
|
+
recalculatePromotionIfNeeded(session, effectiveSessionId, currId).then((recalculated) => {
|
|
77
|
+
if (recalculated && sessionData) {
|
|
78
|
+
setSessionData({ ...sessionData, checkoutSession: recalculated, quotes: void 0 });
|
|
79
|
+
}
|
|
80
|
+
});
|
|
75
81
|
}
|
|
76
82
|
}, [paymentMethodHook.currency?.id, session?.id]);
|
|
77
83
|
const paymentMethodValue = useMemo(
|
|
@@ -175,7 +181,13 @@ export function CheckoutProvider({ sessionId, children }) {
|
|
|
175
181
|
if (intervalRef.current) clearInterval(intervalRef.current);
|
|
176
182
|
document.removeEventListener("visibilitychange", handleVisibility);
|
|
177
183
|
};
|
|
178
|
-
}, [
|
|
184
|
+
}, [
|
|
185
|
+
hasDynamicPricing,
|
|
186
|
+
paymentMethodHook.isStripe,
|
|
187
|
+
effectiveSessionId,
|
|
188
|
+
paymentMethodHook.currency?.id,
|
|
189
|
+
session?.status
|
|
190
|
+
]);
|
|
179
191
|
const exchangeRateValue = useMemo(
|
|
180
192
|
() => ({
|
|
181
193
|
rate: exchangeRate,
|
|
@@ -2,6 +2,8 @@ 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;
|
|
@@ -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>;
|
|
@@ -1,14 +1,30 @@
|
|
|
1
1
|
import api, { API } from "../../shared/api.js";
|
|
2
2
|
import { recalculatePromotionIfNeeded } from "./lineItems.js";
|
|
3
|
-
export async function addCrossSellItem(sessionId, crossSellItemId, session, currencyId, refresh) {
|
|
4
|
-
await api.put(API.CROSS_SELL(sessionId), { to: crossSellItemId });
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
export async function addCrossSellItem(sessionId, crossSellItemId, session, currencyId, refresh, sessionData, setSessionData) {
|
|
4
|
+
const { data } = await api.put(API.CROSS_SELL(sessionId), { to: crossSellItemId });
|
|
5
|
+
let finalSession = data;
|
|
6
|
+
if (data.discounts?.length) {
|
|
7
|
+
const recalculated = await recalculatePromotionIfNeeded(session, sessionId, currencyId);
|
|
8
|
+
if (recalculated) finalSession = recalculated;
|
|
9
|
+
}
|
|
10
|
+
if (sessionData && setSessionData) {
|
|
11
|
+
setSessionData({ ...sessionData, checkoutSession: finalSession, quotes: void 0 });
|
|
12
|
+
} else {
|
|
13
|
+
await refresh(true);
|
|
14
|
+
}
|
|
7
15
|
}
|
|
8
|
-
export async function removeCrossSellItem(sessionId, session, currencyId, refresh) {
|
|
9
|
-
await api.delete(API.CROSS_SELL(sessionId));
|
|
10
|
-
|
|
11
|
-
|
|
16
|
+
export async function removeCrossSellItem(sessionId, session, currencyId, refresh, sessionData, setSessionData) {
|
|
17
|
+
const { data } = await api.delete(API.CROSS_SELL(sessionId));
|
|
18
|
+
let finalSession = data;
|
|
19
|
+
if (data.discounts?.length) {
|
|
20
|
+
const recalculated = await recalculatePromotionIfNeeded(session, sessionId, currencyId);
|
|
21
|
+
if (recalculated) finalSession = recalculated;
|
|
22
|
+
}
|
|
23
|
+
if (sessionData && setSessionData) {
|
|
24
|
+
setSessionData({ ...sessionData, checkoutSession: finalSession, quotes: void 0 });
|
|
25
|
+
} else {
|
|
26
|
+
await refresh(true);
|
|
27
|
+
}
|
|
12
28
|
}
|
|
13
29
|
const pendingFetches = /* @__PURE__ */ new Map();
|
|
14
30
|
export 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;
|
|
@@ -1,52 +1,83 @@
|
|
|
1
1
|
import api, { API } from "../../shared/api.js";
|
|
2
2
|
import { recalculatePromotion, hasAppliedDiscounts } from "./promotion.js";
|
|
3
3
|
export async function recalculatePromotionIfNeeded(session, sessionId, currencyId) {
|
|
4
|
-
if (!hasAppliedDiscounts(session)) return;
|
|
4
|
+
if (!hasAppliedDiscounts(session)) return null;
|
|
5
5
|
try {
|
|
6
|
-
await recalculatePromotion(sessionId, currencyId);
|
|
6
|
+
return await recalculatePromotion(sessionId, currencyId);
|
|
7
7
|
} catch {
|
|
8
|
+
return null;
|
|
8
9
|
}
|
|
9
10
|
}
|
|
10
|
-
export async function adjustQuantity(sessionId, itemId, qty, currencyId, session, refresh) {
|
|
11
|
-
await api.put(API.ADJUST_QUANTITY(sessionId), {
|
|
11
|
+
export async function adjustQuantity(sessionId, itemId, qty, currencyId, session, refresh, sessionData, setSessionData) {
|
|
12
|
+
const { data } = await api.put(API.ADJUST_QUANTITY(sessionId), {
|
|
12
13
|
itemId,
|
|
13
14
|
quantity: qty,
|
|
14
15
|
currency_id: currencyId
|
|
15
16
|
});
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
let finalSession = data;
|
|
18
|
+
if (data.discounts?.length || hasAppliedDiscounts(session)) {
|
|
19
|
+
const recalculated = await recalculatePromotionIfNeeded(session, sessionId, currencyId);
|
|
20
|
+
if (recalculated) finalSession = recalculated;
|
|
21
|
+
}
|
|
22
|
+
if (sessionData && setSessionData) {
|
|
23
|
+
setSessionData({ ...sessionData, checkoutSession: finalSession, quotes: void 0 });
|
|
24
|
+
} else {
|
|
25
|
+
await refresh(true);
|
|
26
|
+
}
|
|
18
27
|
}
|
|
19
|
-
export async function performUpsell(sessionId, fromId, toId, session, currencyId, refresh) {
|
|
28
|
+
export async function performUpsell(sessionId, fromId, toId, session, currencyId, refresh, sessionData, setSessionData) {
|
|
20
29
|
if ((session?.line_items?.length || 0) > 1) {
|
|
21
30
|
try {
|
|
22
31
|
await api.delete(API.CROSS_SELL(sessionId));
|
|
23
32
|
} catch {
|
|
24
33
|
}
|
|
25
34
|
}
|
|
26
|
-
await api.put(API.UPSELL(sessionId), { from: fromId, to: toId });
|
|
27
|
-
|
|
28
|
-
|
|
35
|
+
const { data } = await api.put(API.UPSELL(sessionId), { from: fromId, to: toId });
|
|
36
|
+
let finalSession = data;
|
|
37
|
+
if (data.discounts?.length || hasAppliedDiscounts(session)) {
|
|
38
|
+
const recalculated = await recalculatePromotionIfNeeded(session, sessionId, currencyId);
|
|
39
|
+
if (recalculated) finalSession = recalculated;
|
|
40
|
+
}
|
|
41
|
+
if (sessionData && setSessionData) {
|
|
42
|
+
setSessionData({ ...sessionData, checkoutSession: finalSession, quotes: void 0 });
|
|
43
|
+
} else {
|
|
44
|
+
await refresh(true);
|
|
45
|
+
}
|
|
29
46
|
}
|
|
30
|
-
export async function performDownsell(sessionId, priceId, session, currencyId, refresh) {
|
|
47
|
+
export async function performDownsell(sessionId, priceId, session, currencyId, refresh, sessionData, setSessionData) {
|
|
31
48
|
if ((session?.line_items?.length || 0) > 1) {
|
|
32
49
|
try {
|
|
33
50
|
await api.delete(API.CROSS_SELL(sessionId));
|
|
34
51
|
} catch {
|
|
35
52
|
}
|
|
36
53
|
}
|
|
37
|
-
await api.put(API.DOWNSELL(sessionId), { from: priceId });
|
|
38
|
-
|
|
39
|
-
|
|
54
|
+
const { data } = await api.put(API.DOWNSELL(sessionId), { from: priceId });
|
|
55
|
+
let finalSession = data;
|
|
56
|
+
if (data.discounts?.length || hasAppliedDiscounts(session)) {
|
|
57
|
+
const recalculated = await recalculatePromotionIfNeeded(session, sessionId, currencyId);
|
|
58
|
+
if (recalculated) finalSession = recalculated;
|
|
59
|
+
}
|
|
60
|
+
if (sessionData && setSessionData) {
|
|
61
|
+
setSessionData({ ...sessionData, checkoutSession: finalSession, quotes: void 0 });
|
|
62
|
+
} else {
|
|
63
|
+
await refresh(true);
|
|
64
|
+
}
|
|
40
65
|
}
|
|
41
|
-
export async function changeDonationAmount(sessionId, priceId, amount, session, currencyId, refresh) {
|
|
66
|
+
export async function changeDonationAmount(sessionId, priceId, amount, session, currencyId, refresh, sessionData, setSessionData) {
|
|
42
67
|
const { data } = await api.put(API.CHANGE_AMOUNT(sessionId), {
|
|
43
68
|
priceId,
|
|
44
69
|
amount
|
|
45
70
|
});
|
|
71
|
+
let finalSession = data;
|
|
46
72
|
if (data?.discounts?.length) {
|
|
47
|
-
await recalculatePromotionIfNeeded(session, sessionId, currencyId);
|
|
73
|
+
const recalculated = await recalculatePromotionIfNeeded(session, sessionId, currencyId);
|
|
74
|
+
if (recalculated) finalSession = recalculated;
|
|
75
|
+
}
|
|
76
|
+
if (sessionData && setSessionData) {
|
|
77
|
+
setSessionData({ ...sessionData, checkoutSession: finalSession, quotes: void 0 });
|
|
78
|
+
} else {
|
|
79
|
+
await refresh(true);
|
|
48
80
|
}
|
|
49
|
-
await refresh(true);
|
|
50
81
|
}
|
|
51
82
|
export function getCrossSellItem(items) {
|
|
52
83
|
for (const item of items) {
|
|
@@ -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;
|
|
@@ -26,9 +26,10 @@ export async function removePromotionCode(sessionId) {
|
|
|
26
26
|
await api.delete(API.REMOVE_PROMOTION(sessionId));
|
|
27
27
|
}
|
|
28
28
|
export async function recalculatePromotion(sessionId, currencyId) {
|
|
29
|
-
await api.post(API.RECALCULATE_PROMOTION_SESSION(sessionId), {
|
|
29
|
+
const { data } = await api.post(API.RECALCULATE_PROMOTION_SESSION(sessionId), {
|
|
30
30
|
currency_id: currencyId
|
|
31
31
|
});
|
|
32
|
+
return data;
|
|
32
33
|
}
|
|
33
34
|
export function isPromotionActive(session) {
|
|
34
35
|
return session?.allow_promotion_codes !== false;
|
|
@@ -6,20 +6,21 @@ import { usePaymentMethodContext } from "../context/PaymentMethodContext.js";
|
|
|
6
6
|
import { parseBillingInterval } from "../core/billingInterval.js";
|
|
7
7
|
import { performUpsell, performDownsell } from "../core/lineItems.js";
|
|
8
8
|
export function useBillingInterval() {
|
|
9
|
-
const { items, session, effectiveSessionId, refresh } = useSessionContext();
|
|
9
|
+
const { items, session, effectiveSessionId, refresh, sessionData, setSessionData } = useSessionContext();
|
|
10
10
|
const { currency } = usePaymentMethodContext();
|
|
11
11
|
const currencyId = currency?.id || null;
|
|
12
12
|
const [switching, setSwitching] = useState(false);
|
|
13
|
+
const [pendingInterval, setPendingInterval] = useState(null);
|
|
13
14
|
const upsell = useMemoizedFn(async (fromId, toId) => {
|
|
14
15
|
try {
|
|
15
|
-
await performUpsell(effectiveSessionId, fromId, toId, session, currencyId, refresh);
|
|
16
|
+
await performUpsell(effectiveSessionId, fromId, toId, session, currencyId, refresh, sessionData, setSessionData);
|
|
16
17
|
} catch (err) {
|
|
17
18
|
console.error("Failed to upsell:", getErrorMessage(err));
|
|
18
19
|
}
|
|
19
20
|
});
|
|
20
21
|
const downsell = useMemoizedFn(async (priceId) => {
|
|
21
22
|
try {
|
|
22
|
-
await performDownsell(effectiveSessionId, priceId, session, currencyId, refresh);
|
|
23
|
+
await performDownsell(effectiveSessionId, priceId, session, currencyId, refresh, sessionData, setSessionData);
|
|
23
24
|
} catch (err) {
|
|
24
25
|
console.error("Failed to downsell:", getErrorMessage(err));
|
|
25
26
|
}
|
|
@@ -28,12 +29,13 @@ export function useBillingInterval() {
|
|
|
28
29
|
const parsed = parseBillingInterval(items);
|
|
29
30
|
if (!parsed) return null;
|
|
30
31
|
return {
|
|
31
|
-
current: parsed.current,
|
|
32
|
+
current: pendingInterval || parsed.current,
|
|
32
33
|
available: parsed.available,
|
|
33
34
|
switching,
|
|
34
35
|
switch: async (interval) => {
|
|
35
36
|
const target = parsed.available.find((a) => a.interval === interval);
|
|
36
37
|
if (!target || switching) return;
|
|
38
|
+
setPendingInterval(interval);
|
|
37
39
|
setSwitching(true);
|
|
38
40
|
try {
|
|
39
41
|
if (!parsed.firstItem.upsell_price_id && target.priceId) {
|
|
@@ -43,8 +45,9 @@ export function useBillingInterval() {
|
|
|
43
45
|
}
|
|
44
46
|
} finally {
|
|
45
47
|
setSwitching(false);
|
|
48
|
+
setPendingInterval(null);
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
51
|
};
|
|
49
|
-
}, [items, effectiveSessionId, switching]);
|
|
52
|
+
}, [items, effectiveSessionId, switching, pendingInterval]);
|
|
50
53
|
}
|
|
@@ -22,6 +22,7 @@ export function useCheckout(sessionId) {
|
|
|
22
22
|
error,
|
|
23
23
|
errorCode,
|
|
24
24
|
refresh,
|
|
25
|
+
setSessionData,
|
|
25
26
|
sessionData,
|
|
26
27
|
resolvedSessionId,
|
|
27
28
|
vendorCount,
|
|
@@ -31,7 +32,7 @@ export function useCheckout(sessionId) {
|
|
|
31
32
|
} = useCheckoutSession(sessionId);
|
|
32
33
|
const session = sessionData?.checkoutSession;
|
|
33
34
|
const effectiveSessionId = resolvedSessionId || sessionId;
|
|
34
|
-
const paymentMethodHook = usePaymentMethod(sessionData, effectiveSessionId, refresh);
|
|
35
|
+
const paymentMethodHook = usePaymentMethod(sessionData, effectiveSessionId, refresh, setSessionData);
|
|
35
36
|
const pricingHook = usePricing(
|
|
36
37
|
sessionData,
|
|
37
38
|
effectiveSessionId,
|
|
@@ -57,7 +58,7 @@ export function useCheckout(sessionId) {
|
|
|
57
58
|
formHook.validate,
|
|
58
59
|
refresh
|
|
59
60
|
);
|
|
60
|
-
const items = session?.line_items || [];
|
|
61
|
+
const items = useMemo(() => session?.line_items || [], [session?.line_items]);
|
|
61
62
|
const currencyId = paymentMethodHook.currency?.id || null;
|
|
62
63
|
const prevCurrencyRef = useRef(null);
|
|
63
64
|
useEffect(() => {
|
|
@@ -65,26 +66,30 @@ export function useCheckout(sessionId) {
|
|
|
65
66
|
if (!currId || !session) return;
|
|
66
67
|
if (prevCurrencyRef.current === null || currId !== prevCurrencyRef.current) {
|
|
67
68
|
prevCurrencyRef.current = currId;
|
|
68
|
-
recalculatePromotionIfNeeded(session, effectiveSessionId, currId).then(() =>
|
|
69
|
+
recalculatePromotionIfNeeded(session, effectiveSessionId, currId).then((recalculated) => {
|
|
70
|
+
if (recalculated && sessionData) {
|
|
71
|
+
setSessionData({ ...sessionData, checkoutSession: recalculated, quotes: void 0 });
|
|
72
|
+
}
|
|
73
|
+
});
|
|
69
74
|
}
|
|
70
75
|
}, [paymentMethodHook.currency?.id, session?.id]);
|
|
71
76
|
const updateQuantity = useMemoizedFn(async (itemId, qty) => {
|
|
72
77
|
try {
|
|
73
|
-
await adjustQuantity(effectiveSessionId, itemId, qty, currencyId, session, refresh);
|
|
78
|
+
await adjustQuantity(effectiveSessionId, itemId, qty, currencyId, session, refresh, sessionData, setSessionData);
|
|
74
79
|
} catch (err) {
|
|
75
80
|
console.error("Failed to update quantity:", getErrorMessage(err));
|
|
76
81
|
}
|
|
77
82
|
});
|
|
78
83
|
const upsell = useMemoizedFn(async (fromId, toId) => {
|
|
79
84
|
try {
|
|
80
|
-
await performUpsell(effectiveSessionId, fromId, toId, session, currencyId, refresh);
|
|
85
|
+
await performUpsell(effectiveSessionId, fromId, toId, session, currencyId, refresh, sessionData, setSessionData);
|
|
81
86
|
} catch (err) {
|
|
82
87
|
console.error("Failed to upsell:", getErrorMessage(err));
|
|
83
88
|
}
|
|
84
89
|
});
|
|
85
90
|
const downsell = useMemoizedFn(async (priceId) => {
|
|
86
91
|
try {
|
|
87
|
-
await performDownsell(effectiveSessionId, priceId, session, currencyId, refresh);
|
|
92
|
+
await performDownsell(effectiveSessionId, priceId, session, currencyId, refresh, sessionData, setSessionData);
|
|
88
93
|
} catch (err) {
|
|
89
94
|
console.error("Failed to downsell:", getErrorMessage(err));
|
|
90
95
|
}
|
|
@@ -109,14 +114,14 @@ export function useCheckout(sessionId) {
|
|
|
109
114
|
const addCrossSell = useMemoizedFn(async () => {
|
|
110
115
|
if (!crossSellItem) return;
|
|
111
116
|
try {
|
|
112
|
-
await addCrossSellItem(effectiveSessionId, crossSellItem.id, session, currencyId, refresh);
|
|
117
|
+
await addCrossSellItem(effectiveSessionId, crossSellItem.id, session, currencyId, refresh, sessionData, setSessionData);
|
|
113
118
|
} catch (err) {
|
|
114
119
|
console.error("Failed to add cross-sell:", getErrorMessage(err));
|
|
115
120
|
}
|
|
116
121
|
});
|
|
117
122
|
const removeCrossSell = useMemoizedFn(async () => {
|
|
118
123
|
try {
|
|
119
|
-
await removeCrossSellItem(effectiveSessionId, session, currencyId, refresh);
|
|
124
|
+
await removeCrossSellItem(effectiveSessionId, session, currencyId, refresh, sessionData, setSessionData);
|
|
120
125
|
} catch (err) {
|
|
121
126
|
console.error("Failed to remove cross-sell:", getErrorMessage(err));
|
|
122
127
|
}
|
|
@@ -141,7 +146,7 @@ export function useCheckout(sessionId) {
|
|
|
141
146
|
const setDonationAmount = useMemoizedFn(async (priceId, amount) => {
|
|
142
147
|
if (!isDonation) return;
|
|
143
148
|
try {
|
|
144
|
-
await changeDonationAmount(effectiveSessionId, priceId, amount, session, currencyId, refresh);
|
|
149
|
+
await changeDonationAmount(effectiveSessionId, priceId, amount, session, currencyId, refresh, sessionData, setSessionData);
|
|
145
150
|
} catch (err) {
|
|
146
151
|
console.error("Failed to change amount:", getErrorMessage(err));
|
|
147
152
|
}
|
|
@@ -6,7 +6,7 @@ import { usePaymentMethodContext } from "../context/PaymentMethodContext.js";
|
|
|
6
6
|
import { getCrossSellItem } from "../core/lineItems.js";
|
|
7
7
|
import { addCrossSellItem, removeCrossSellItem, fetchCrossSellItem } from "../core/crossSell.js";
|
|
8
8
|
export function useCrossSell() {
|
|
9
|
-
const { items, session, effectiveSessionId, refresh } = useSessionContext();
|
|
9
|
+
const { items, session, effectiveSessionId, refresh, sessionData, setSessionData } = useSessionContext();
|
|
10
10
|
const { currency } = usePaymentMethodContext();
|
|
11
11
|
const currencyId = currency?.id || null;
|
|
12
12
|
const embeddedItem = useMemo(() => getCrossSellItem(items), [items]);
|
|
@@ -40,7 +40,15 @@ export function useCrossSell() {
|
|
|
40
40
|
const crossSellItemPrice = getCrossSellItem(items);
|
|
41
41
|
if (!crossSellItemPrice) return;
|
|
42
42
|
try {
|
|
43
|
-
await addCrossSellItem(
|
|
43
|
+
await addCrossSellItem(
|
|
44
|
+
effectiveSessionId,
|
|
45
|
+
crossSellItemPrice.id,
|
|
46
|
+
session,
|
|
47
|
+
currencyId,
|
|
48
|
+
refresh,
|
|
49
|
+
sessionData,
|
|
50
|
+
setSessionData
|
|
51
|
+
);
|
|
44
52
|
} catch (err) {
|
|
45
53
|
console.error("Failed to add cross-sell:", getErrorMessage(err));
|
|
46
54
|
}
|
|
@@ -48,7 +56,7 @@ export function useCrossSell() {
|
|
|
48
56
|
const remove = useMemoizedFn(async () => {
|
|
49
57
|
if (session?.status === "complete") return;
|
|
50
58
|
try {
|
|
51
|
-
await removeCrossSellItem(effectiveSessionId, session, currencyId, refresh);
|
|
59
|
+
await removeCrossSellItem(effectiveSessionId, session, currencyId, refresh, sessionData, setSessionData);
|
|
52
60
|
} catch (err) {
|
|
53
61
|
console.error("Failed to remove cross-sell:", getErrorMessage(err));
|
|
54
62
|
}
|
|
@@ -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,9 @@ import {
|
|
|
9
9
|
findMethodAndCurrency,
|
|
10
10
|
buildPaymentTypes
|
|
11
11
|
} from "../core/paymentMethod.js";
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
import { recalculatePromotionIfNeeded } from "../core/lineItems.js";
|
|
13
|
+
export function usePaymentMethod(sessionData, sessionId, refreshSession, setSessionData) {
|
|
14
|
+
const methods = useMemo(() => sessionData?.paymentMethods || [], [sessionData?.paymentMethods]);
|
|
14
15
|
const session = sessionData?.checkoutSession;
|
|
15
16
|
const [currencyId, setCurrencyId] = useState(() => getInitialCurrencyId(session, methods));
|
|
16
17
|
const [switching, setSwitching] = useState(false);
|
|
@@ -43,7 +44,7 @@ export function usePaymentMethod(sessionData, sessionId, refreshSession) {
|
|
|
43
44
|
if (!method || !sessionId) return;
|
|
44
45
|
setSwitching(true);
|
|
45
46
|
try {
|
|
46
|
-
await api.put(API.SWITCH_CURRENCY(sessionId), {
|
|
47
|
+
const { data } = await api.put(API.SWITCH_CURRENCY(sessionId), {
|
|
47
48
|
currency_id: newCurrencyId,
|
|
48
49
|
payment_method_id: method.id
|
|
49
50
|
});
|
|
@@ -55,7 +56,16 @@ export function usePaymentMethod(sessionData, sessionId, refreshSession) {
|
|
|
55
56
|
);
|
|
56
57
|
} catch {
|
|
57
58
|
}
|
|
58
|
-
|
|
59
|
+
let finalSession = data;
|
|
60
|
+
if (data.discounts?.length) {
|
|
61
|
+
const recalculated = await recalculatePromotionIfNeeded(session, sessionId, newCurrencyId);
|
|
62
|
+
if (recalculated) finalSession = recalculated;
|
|
63
|
+
}
|
|
64
|
+
if (sessionData && setSessionData) {
|
|
65
|
+
setSessionData({ ...sessionData, checkoutSession: finalSession, quotes: void 0 });
|
|
66
|
+
} else {
|
|
67
|
+
await refreshSession(true);
|
|
68
|
+
}
|
|
59
69
|
} catch (err) {
|
|
60
70
|
console.error("Failed to switch currency:", getErrorMessage(err));
|
|
61
71
|
if (session?.currency_id) {
|
|
@@ -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
|
}
|
|
@@ -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,23 @@ 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 && sessionData) {
|
|
68
|
+
setSessionData({
|
|
69
|
+
...sessionData,
|
|
70
|
+
checkoutSession: recalculated,
|
|
71
|
+
quotes: void 0
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
});
|
|
66
75
|
}
|
|
67
76
|
}, [paymentMethodHook.currency?.id, session?.id]);
|
|
68
77
|
const paymentMethodValue = (0, _react.useMemo)(() => ({
|
|
@@ -2,6 +2,8 @@ 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;
|