@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.
Files changed (75) hide show
  1. package/es/checkout/context/CheckoutProvider.js +26 -10
  2. package/es/checkout/context/SessionContext.d.ts +3 -1
  3. package/es/checkout/core/crossSell.d.ts +3 -2
  4. package/es/checkout/core/crossSell.js +24 -8
  5. package/es/checkout/core/customerForm.js +0 -6
  6. package/es/checkout/core/lineItems.d.ts +6 -5
  7. package/es/checkout/core/lineItems.js +48 -17
  8. package/es/checkout/core/paymentMethod.js +15 -7
  9. package/es/checkout/core/pricing.js +1 -2
  10. package/es/checkout/core/promotion.d.ts +1 -1
  11. package/es/checkout/core/promotion.js +2 -1
  12. package/es/checkout/hooks/useBillingInterval.js +8 -5
  13. package/es/checkout/hooks/useCheckout.js +17 -11
  14. package/es/checkout/hooks/useCheckoutSession.d.ts +2 -2
  15. package/es/checkout/hooks/useCheckoutSession.js +7 -0
  16. package/es/checkout/hooks/useCheckoutStatus.d.ts +1 -1
  17. package/es/checkout/hooks/useCrossSell.js +11 -3
  18. package/es/checkout/hooks/useCustomerForm.d.ts +4 -0
  19. package/es/checkout/hooks/useCustomerForm.js +6 -0
  20. package/es/checkout/hooks/useLineItems.js +36 -9
  21. package/es/checkout/hooks/usePaymentMethod.d.ts +1 -1
  22. package/es/checkout/hooks/usePaymentMethod.js +8 -4
  23. package/es/checkout/hooks/usePricing.d.ts +1 -1
  24. package/es/checkout/hooks/usePricing.js +4 -4
  25. package/es/checkout/hooks/useSubmit.js +6 -1
  26. package/es/checkout/hooks/useUpsell.js +3 -3
  27. package/es/checkout/types.d.ts +5 -3
  28. package/lib/checkout/context/CheckoutProvider.js +21 -12
  29. package/lib/checkout/context/SessionContext.d.ts +3 -1
  30. package/lib/checkout/core/crossSell.d.ts +3 -2
  31. package/lib/checkout/core/crossSell.js +36 -8
  32. package/lib/checkout/core/customerForm.js +0 -5
  33. package/lib/checkout/core/lineItems.d.ts +6 -5
  34. package/lib/checkout/core/lineItems.js +72 -18
  35. package/lib/checkout/core/paymentMethod.js +14 -8
  36. package/lib/checkout/core/pricing.js +1 -2
  37. package/lib/checkout/core/promotion.d.ts +1 -1
  38. package/lib/checkout/core/promotion.js +4 -1
  39. package/lib/checkout/hooks/useBillingInterval.js +10 -5
  40. package/lib/checkout/hooks/useCheckout.js +16 -11
  41. package/lib/checkout/hooks/useCheckoutSession.d.ts +2 -2
  42. package/lib/checkout/hooks/useCheckoutSession.js +7 -0
  43. package/lib/checkout/hooks/useCheckoutStatus.d.ts +1 -1
  44. package/lib/checkout/hooks/useCrossSell.js +5 -3
  45. package/lib/checkout/hooks/useCustomerForm.d.ts +4 -0
  46. package/lib/checkout/hooks/useCustomerForm.js +6 -0
  47. package/lib/checkout/hooks/useLineItems.js +10 -8
  48. package/lib/checkout/hooks/usePaymentMethod.d.ts +1 -1
  49. package/lib/checkout/hooks/usePaymentMethod.js +14 -4
  50. package/lib/checkout/hooks/usePricing.d.ts +1 -1
  51. package/lib/checkout/hooks/usePricing.js +4 -4
  52. package/lib/checkout/hooks/useSubmit.js +8 -1
  53. package/lib/checkout/hooks/useUpsell.js +5 -3
  54. package/lib/checkout/types.d.ts +5 -3
  55. package/package.json +3 -3
  56. package/src/checkout/context/CheckoutProvider.tsx +38 -17
  57. package/src/checkout/context/SessionContext.ts +3 -1
  58. package/src/checkout/core/crossSell.ts +29 -8
  59. package/src/checkout/core/customerForm.ts +0 -6
  60. package/src/checkout/core/lineItems.ts +62 -18
  61. package/src/checkout/core/paymentMethod.ts +24 -7
  62. package/src/checkout/core/pricing.ts +1 -2
  63. package/src/checkout/core/promotion.ts +6 -2
  64. package/src/checkout/hooks/useBillingInterval.ts +8 -5
  65. package/src/checkout/hooks/useCheckout.ts +20 -12
  66. package/src/checkout/hooks/useCheckoutSession.ts +12 -3
  67. package/src/checkout/hooks/useCheckoutStatus.ts +1 -1
  68. package/src/checkout/hooks/useCrossSell.ts +11 -3
  69. package/src/checkout/hooks/useCustomerForm.ts +12 -0
  70. package/src/checkout/hooks/useLineItems.ts +42 -9
  71. package/src/checkout/hooks/usePaymentMethod.ts +13 -5
  72. package/src/checkout/hooks/usePricing.ts +5 -4
  73. package/src/checkout/hooks/useSubmit.ts +13 -1
  74. package/src/checkout/hooks/useUpsell.ts +3 -3
  75. 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") return;
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(effectiveSessionId, item.price_id, qty, currencyId, session, refresh);
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(effectiveSessionId, crossSellItem.id, session, currencyId, refresh);
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(effectiveSessionId, priceId, amount, session, currencyId, refresh);
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>): UsePaymentMethodReturn;
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
- await refreshSession(true);
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
  }
@@ -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: 'SESSION_EXPIRED' | 'EMPTY_LINE_ITEMS' | null */
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(() => refresh(true));
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
- }), [paymentMethodHook.current, paymentMethodHook.currency, paymentMethodHook.available, paymentMethodHook.currencies, paymentMethodHook.isStripe, paymentMethodHook.isCrypto, paymentMethodHook.isCredit, paymentMethodHook.switching, paymentMethodHook.setType, paymentMethodHook.types, paymentMethodHook.setCurrency, paymentMethodHook.stripe]);
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 (session?.status === "complete") {
134
- setRateStatus("available");
135
- } else {
136
- setRateStatus(hasDynamicPricing ? "unavailable" : "available");
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
- export declare function addCrossSellItem(sessionId: string, crossSellItemId: string, session: TCheckoutSessionExpanded | undefined | null, currencyId: string | null | undefined, refresh: (force?: boolean) => Promise<void>): Promise<void>;
3
- export declare function removeCrossSellItem(sessionId: string, session: TCheckoutSessionExpanded | undefined | null, currencyId: string | null | undefined, refresh: (force?: boolean) => Promise<void>): Promise<void>;
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
- await _api.default.put(_api.API.CROSS_SELL(sessionId), {
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
- await (0, _lineItems.recalculatePromotionIfNeeded)(session, sessionId, currencyId);
18
- await refresh(true);
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
- await _api.default.delete(_api.API.CROSS_SELL(sessionId));
22
- await (0, _lineItems.recalculatePromotionIfNeeded)(session, sessionId, currencyId);
23
- await refresh(true);
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) {
@@ -37,11 +37,6 @@ function buildFields(session) {
37
37
  type: "text",
38
38
  required: true,
39
39
  group: "address"
40
- }, {
41
- name: "billing_address.line2",
42
- type: "text",
43
- required: false,
44
- group: "address"
45
40
  }, {
46
41
  name: "billing_address.city",
47
42
  type: "text",
@@ -1,7 +1,8 @@
1
1
  import type { TCheckoutSessionExpanded, TLineItemExpanded, TPrice } from '@blocklet/payment-types';
2
- export declare function recalculatePromotionIfNeeded(session: TCheckoutSessionExpanded | undefined | null, sessionId: string, currencyId: string | null | undefined): Promise<void>;
3
- export declare function adjustQuantity(sessionId: string, itemId: string, qty: number, currencyId: string | null | undefined, session: TCheckoutSessionExpanded | undefined | null, refresh: (force?: boolean) => Promise<void>): Promise<void>;
4
- export declare function performUpsell(sessionId: string, fromId: string, toId: string, session: TCheckoutSessionExpanded | undefined | null, currencyId: string | null | undefined, refresh: (force?: boolean) => Promise<void>): Promise<void>;
5
- export declare function performDownsell(sessionId: string, priceId: string, session: TCheckoutSessionExpanded | undefined | null, currencyId: string | null | undefined, refresh: (force?: boolean) => Promise<void>): Promise<void>;
6
- export declare function changeDonationAmount(sessionId: string, priceId: string, amount: string, session: TCheckoutSessionExpanded | undefined | null, currencyId: string | null | undefined, refresh: (force?: boolean) => Promise<void>): Promise<void>;
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
- await _api.default.put(_api.API.ADJUST_QUANTITY(sessionId), {
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
- await recalculatePromotionIfNeeded(session, sessionId, currencyId);
29
- await refresh(true);
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
- await _api.default.put(_api.API.UPSELL(sessionId), {
53
+ const {
54
+ data
55
+ } = await _api.default.put(_api.API.UPSELL(sessionId), {
38
56
  from: fromId,
39
57
  to: toId
40
58
  });
41
- await recalculatePromotionIfNeeded(session, sessionId, currencyId);
42
- await refresh(true);
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
- await _api.default.put(_api.API.DOWNSELL(sessionId), {
80
+ const {
81
+ data
82
+ } = await _api.default.put(_api.API.DOWNSELL(sessionId), {
51
83
  from: priceId
52
84
  });
53
- await recalculatePromotionIfNeeded(session, sessionId, currencyId);
54
- await refresh(true);
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.id;
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
- return session?.currency_id || null;
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: first2 || null,
65
- currency: firstCurrency
70
+ method: null,
71
+ currency: null
66
72
  };
67
73
  }
68
74
  for (const method of methods) {