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