@blocklet/payment-react-headless 1.26.0

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 (250) hide show
  1. package/.eslintrc.js +18 -0
  2. package/build.config.ts +30 -0
  3. package/es/checkout/context/CheckoutProvider.d.ts +6 -0
  4. package/es/checkout/context/CheckoutProvider.js +209 -0
  5. package/es/checkout/context/CustomerFormContext.d.ts +4 -0
  6. package/es/checkout/context/CustomerFormContext.js +9 -0
  7. package/es/checkout/context/ExchangeRateContext.d.ts +11 -0
  8. package/es/checkout/context/ExchangeRateContext.js +9 -0
  9. package/es/checkout/context/PaymentMethodContext.d.ts +26 -0
  10. package/es/checkout/context/PaymentMethodContext.js +9 -0
  11. package/es/checkout/context/SessionContext.d.ts +45 -0
  12. package/es/checkout/context/SessionContext.js +9 -0
  13. package/es/checkout/context/SubmitContext.d.ts +4 -0
  14. package/es/checkout/context/SubmitContext.js +9 -0
  15. package/es/checkout/context/index.d.ts +6 -0
  16. package/es/checkout/context/index.js +6 -0
  17. package/es/checkout/core/billingInterval.d.ts +15 -0
  18. package/es/checkout/core/billingInterval.js +36 -0
  19. package/es/checkout/core/crossSell.d.ts +4 -0
  20. package/es/checkout/core/crossSell.js +30 -0
  21. package/es/checkout/core/customerForm.d.ts +5 -0
  22. package/es/checkout/core/customerForm.js +105 -0
  23. package/es/checkout/core/exchangeRate.d.ts +11 -0
  24. package/es/checkout/core/exchangeRate.js +25 -0
  25. package/es/checkout/core/index.d.ts +10 -0
  26. package/es/checkout/core/index.js +55 -0
  27. package/es/checkout/core/lineItems.d.ts +7 -0
  28. package/es/checkout/core/lineItems.js +59 -0
  29. package/es/checkout/core/paymentMethod.d.ts +23 -0
  30. package/es/checkout/core/paymentMethod.js +85 -0
  31. package/es/checkout/core/pricing.d.ts +32 -0
  32. package/es/checkout/core/pricing.js +221 -0
  33. package/es/checkout/core/promotion.d.ts +10 -0
  34. package/es/checkout/core/promotion.js +39 -0
  35. package/es/checkout/core/session.d.ts +26 -0
  36. package/es/checkout/core/session.js +50 -0
  37. package/es/checkout/core/submit.d.ts +40 -0
  38. package/es/checkout/core/submit.js +66 -0
  39. package/es/checkout/hooks/index.d.ts +34 -0
  40. package/es/checkout/hooks/index.js +19 -0
  41. package/es/checkout/hooks/useBillingInterval.d.ts +14 -0
  42. package/es/checkout/hooks/useBillingInterval.js +50 -0
  43. package/es/checkout/hooks/useCheckout.d.ts +2 -0
  44. package/es/checkout/hooks/useCheckout.js +212 -0
  45. package/es/checkout/hooks/useCheckoutSession.d.ts +58 -0
  46. package/es/checkout/hooks/useCheckoutSession.js +107 -0
  47. package/es/checkout/hooks/useCheckoutStatus.d.ts +10 -0
  48. package/es/checkout/hooks/useCheckoutStatus.js +16 -0
  49. package/es/checkout/hooks/useCrossSell.d.ts +8 -0
  50. package/es/checkout/hooks/useCrossSell.js +57 -0
  51. package/es/checkout/hooks/useCustomerForm.d.ts +14 -0
  52. package/es/checkout/hooks/useCustomerForm.js +116 -0
  53. package/es/checkout/hooks/useCustomerFormFeature.d.ts +2 -0
  54. package/es/checkout/hooks/useCustomerFormFeature.js +4 -0
  55. package/es/checkout/hooks/useExchangeRate.d.ts +11 -0
  56. package/es/checkout/hooks/useExchangeRate.js +15 -0
  57. package/es/checkout/hooks/useLineItems.d.ts +22 -0
  58. package/es/checkout/hooks/useLineItems.js +139 -0
  59. package/es/checkout/hooks/usePaymentMethod.d.ts +26 -0
  60. package/es/checkout/hooks/usePaymentMethod.js +101 -0
  61. package/es/checkout/hooks/usePaymentMethodFeature.d.ts +2 -0
  62. package/es/checkout/hooks/usePaymentMethodFeature.js +4 -0
  63. package/es/checkout/hooks/usePricing.d.ts +57 -0
  64. package/es/checkout/hooks/usePricing.js +174 -0
  65. package/es/checkout/hooks/usePricingFeature.d.ts +28 -0
  66. package/es/checkout/hooks/usePricingFeature.js +36 -0
  67. package/es/checkout/hooks/useProduct.d.ts +32 -0
  68. package/es/checkout/hooks/useProduct.js +5 -0
  69. package/es/checkout/hooks/usePromotion.d.ts +12 -0
  70. package/es/checkout/hooks/usePromotion.js +48 -0
  71. package/es/checkout/hooks/useSlippage.d.ts +8 -0
  72. package/es/checkout/hooks/useSlippage.js +29 -0
  73. package/es/checkout/hooks/useSubmit.d.ts +38 -0
  74. package/es/checkout/hooks/useSubmit.js +493 -0
  75. package/es/checkout/hooks/useSubmitFeature.d.ts +2 -0
  76. package/es/checkout/hooks/useSubmitFeature.js +4 -0
  77. package/es/checkout/hooks/useUpsell.d.ts +5 -0
  78. package/es/checkout/hooks/useUpsell.js +25 -0
  79. package/es/checkout/index.d.ts +37 -0
  80. package/es/checkout/index.js +28 -0
  81. package/es/checkout/types.d.ts +262 -0
  82. package/es/checkout/types.js +0 -0
  83. package/es/index.d.ts +1 -0
  84. package/es/index.js +28 -0
  85. package/es/shared/api.d.ts +41 -0
  86. package/es/shared/api.js +81 -0
  87. package/es/shared/format.d.ts +38 -0
  88. package/es/shared/format.js +229 -0
  89. package/es/shared/polling.d.ts +15 -0
  90. package/es/shared/polling.js +20 -0
  91. package/es/shared/types.d.ts +10 -0
  92. package/es/shared/types.js +0 -0
  93. package/es/shared/validation.d.ts +38 -0
  94. package/es/shared/validation.js +190 -0
  95. package/es/types/checkout-augmented.d.ts +42 -0
  96. package/es/types/checkout-augmented.js +17 -0
  97. package/es/types/external.d.ts +18 -0
  98. package/examples/01-basic-checkout.tsx +159 -0
  99. package/examples/01-credit-recharge.tsx +19 -0
  100. package/examples/02-subscription.tsx +40 -0
  101. package/examples/03-upsell.tsx +60 -0
  102. package/examples/04-cross-sell.tsx +54 -0
  103. package/examples/05-full-checkout.tsx +126 -0
  104. package/jest.config.js +15 -0
  105. package/lib/checkout/context/CheckoutProvider.d.ts +6 -0
  106. package/lib/checkout/context/CheckoutProvider.js +181 -0
  107. package/lib/checkout/context/CustomerFormContext.d.ts +4 -0
  108. package/lib/checkout/context/CustomerFormContext.js +16 -0
  109. package/lib/checkout/context/ExchangeRateContext.d.ts +11 -0
  110. package/lib/checkout/context/ExchangeRateContext.js +16 -0
  111. package/lib/checkout/context/PaymentMethodContext.d.ts +26 -0
  112. package/lib/checkout/context/PaymentMethodContext.js +16 -0
  113. package/lib/checkout/context/SessionContext.d.ts +45 -0
  114. package/lib/checkout/context/SessionContext.js +16 -0
  115. package/lib/checkout/context/SubmitContext.d.ts +4 -0
  116. package/lib/checkout/context/SubmitContext.js +16 -0
  117. package/lib/checkout/context/index.d.ts +6 -0
  118. package/lib/checkout/context/index.js +77 -0
  119. package/lib/checkout/core/billingInterval.d.ts +15 -0
  120. package/lib/checkout/core/billingInterval.js +42 -0
  121. package/lib/checkout/core/crossSell.d.ts +4 -0
  122. package/lib/checkout/core/crossSell.js +43 -0
  123. package/lib/checkout/core/customerForm.d.ts +5 -0
  124. package/lib/checkout/core/customerForm.js +106 -0
  125. package/lib/checkout/core/exchangeRate.d.ts +11 -0
  126. package/lib/checkout/core/exchangeRate.js +45 -0
  127. package/lib/checkout/core/index.d.ts +10 -0
  128. package/lib/checkout/core/index.js +297 -0
  129. package/lib/checkout/core/lineItems.d.ts +7 -0
  130. package/lib/checkout/core/lineItems.js +76 -0
  131. package/lib/checkout/core/paymentMethod.d.ts +23 -0
  132. package/lib/checkout/core/paymentMethod.js +114 -0
  133. package/lib/checkout/core/pricing.d.ts +32 -0
  134. package/lib/checkout/core/pricing.js +216 -0
  135. package/lib/checkout/core/promotion.d.ts +10 -0
  136. package/lib/checkout/core/promotion.js +62 -0
  137. package/lib/checkout/core/session.d.ts +26 -0
  138. package/lib/checkout/core/session.js +58 -0
  139. package/lib/checkout/core/submit.d.ts +40 -0
  140. package/lib/checkout/core/submit.js +84 -0
  141. package/lib/checkout/hooks/index.d.ts +34 -0
  142. package/lib/checkout/hooks/index.js +138 -0
  143. package/lib/checkout/hooks/useBillingInterval.d.ts +14 -0
  144. package/lib/checkout/hooks/useBillingInterval.js +63 -0
  145. package/lib/checkout/hooks/useCheckout.d.ts +2 -0
  146. package/lib/checkout/hooks/useCheckout.js +190 -0
  147. package/lib/checkout/hooks/useCheckoutSession.d.ts +58 -0
  148. package/lib/checkout/hooks/useCheckoutSession.js +119 -0
  149. package/lib/checkout/hooks/useCheckoutStatus.d.ts +10 -0
  150. package/lib/checkout/hooks/useCheckoutStatus.js +28 -0
  151. package/lib/checkout/hooks/useCrossSell.d.ts +8 -0
  152. package/lib/checkout/hooks/useCrossSell.js +75 -0
  153. package/lib/checkout/hooks/useCustomerForm.d.ts +14 -0
  154. package/lib/checkout/hooks/useCustomerForm.js +135 -0
  155. package/lib/checkout/hooks/useCustomerFormFeature.d.ts +2 -0
  156. package/lib/checkout/hooks/useCustomerFormFeature.js +10 -0
  157. package/lib/checkout/hooks/useExchangeRate.d.ts +11 -0
  158. package/lib/checkout/hooks/useExchangeRate.js +29 -0
  159. package/lib/checkout/hooks/useLineItems.d.ts +22 -0
  160. package/lib/checkout/hooks/useLineItems.js +142 -0
  161. package/lib/checkout/hooks/usePaymentMethod.d.ts +26 -0
  162. package/lib/checkout/hooks/usePaymentMethod.js +101 -0
  163. package/lib/checkout/hooks/usePaymentMethodFeature.d.ts +2 -0
  164. package/lib/checkout/hooks/usePaymentMethodFeature.js +10 -0
  165. package/lib/checkout/hooks/usePricing.d.ts +57 -0
  166. package/lib/checkout/hooks/usePricing.js +168 -0
  167. package/lib/checkout/hooks/usePricingFeature.d.ts +28 -0
  168. package/lib/checkout/hooks/usePricingFeature.js +48 -0
  169. package/lib/checkout/hooks/useProduct.d.ts +32 -0
  170. package/lib/checkout/hooks/useProduct.js +21 -0
  171. package/lib/checkout/hooks/usePromotion.d.ts +12 -0
  172. package/lib/checkout/hooks/usePromotion.js +57 -0
  173. package/lib/checkout/hooks/useSlippage.d.ts +8 -0
  174. package/lib/checkout/hooks/useSlippage.js +39 -0
  175. package/lib/checkout/hooks/useSubmit.d.ts +38 -0
  176. package/lib/checkout/hooks/useSubmit.js +504 -0
  177. package/lib/checkout/hooks/useSubmitFeature.d.ts +2 -0
  178. package/lib/checkout/hooks/useSubmitFeature.js +10 -0
  179. package/lib/checkout/hooks/useUpsell.d.ts +5 -0
  180. package/lib/checkout/hooks/useUpsell.js +40 -0
  181. package/lib/checkout/index.d.ts +37 -0
  182. package/lib/checkout/index.js +182 -0
  183. package/lib/checkout/types.d.ts +262 -0
  184. package/lib/checkout/types.js +1 -0
  185. package/lib/index.d.ts +1 -0
  186. package/lib/index.js +162 -0
  187. package/lib/shared/api.d.ts +41 -0
  188. package/lib/shared/api.js +88 -0
  189. package/lib/shared/format.d.ts +38 -0
  190. package/lib/shared/format.js +262 -0
  191. package/lib/shared/polling.d.ts +15 -0
  192. package/lib/shared/polling.js +32 -0
  193. package/lib/shared/types.d.ts +10 -0
  194. package/lib/shared/types.js +1 -0
  195. package/lib/shared/validation.d.ts +38 -0
  196. package/lib/shared/validation.js +212 -0
  197. package/lib/types/checkout-augmented.d.ts +42 -0
  198. package/lib/types/checkout-augmented.js +24 -0
  199. package/lib/types/external.d.ts +18 -0
  200. package/package.json +64 -0
  201. package/src/checkout/context/CheckoutProvider.tsx +269 -0
  202. package/src/checkout/context/CustomerFormContext.ts +14 -0
  203. package/src/checkout/context/ExchangeRateContext.ts +21 -0
  204. package/src/checkout/context/PaymentMethodContext.ts +36 -0
  205. package/src/checkout/context/SessionContext.ts +49 -0
  206. package/src/checkout/context/SubmitContext.ts +14 -0
  207. package/src/checkout/context/index.ts +6 -0
  208. package/src/checkout/core/billingInterval.ts +62 -0
  209. package/src/checkout/core/crossSell.ts +52 -0
  210. package/src/checkout/core/customerForm.ts +122 -0
  211. package/src/checkout/core/exchangeRate.ts +38 -0
  212. package/src/checkout/core/index.ts +60 -0
  213. package/src/checkout/core/lineItems.ts +106 -0
  214. package/src/checkout/core/paymentMethod.ts +113 -0
  215. package/src/checkout/core/pricing.ts +347 -0
  216. package/src/checkout/core/promotion.ts +59 -0
  217. package/src/checkout/core/session.ts +62 -0
  218. package/src/checkout/core/submit.ts +109 -0
  219. package/src/checkout/hooks/index.ts +41 -0
  220. package/src/checkout/hooks/useBillingInterval.ts +71 -0
  221. package/src/checkout/hooks/useCheckout.ts +267 -0
  222. package/src/checkout/hooks/useCheckoutSession.ts +217 -0
  223. package/src/checkout/hooks/useCheckoutStatus.ts +31 -0
  224. package/src/checkout/hooks/useCrossSell.ts +80 -0
  225. package/src/checkout/hooks/useCustomerForm.ts +156 -0
  226. package/src/checkout/hooks/useCustomerFormFeature.ts +7 -0
  227. package/src/checkout/hooks/useExchangeRate.ts +28 -0
  228. package/src/checkout/hooks/useLineItems.ts +191 -0
  229. package/src/checkout/hooks/usePaymentMethod.ts +165 -0
  230. package/src/checkout/hooks/usePaymentMethodFeature.ts +8 -0
  231. package/src/checkout/hooks/usePricing.ts +274 -0
  232. package/src/checkout/hooks/usePricingFeature.ts +73 -0
  233. package/src/checkout/hooks/useProduct.ts +32 -0
  234. package/src/checkout/hooks/usePromotion.ts +67 -0
  235. package/src/checkout/hooks/useSlippage.ts +39 -0
  236. package/src/checkout/hooks/useSubmit.ts +684 -0
  237. package/src/checkout/hooks/useSubmitFeature.ts +7 -0
  238. package/src/checkout/hooks/useUpsell.ts +35 -0
  239. package/src/checkout/index.ts +65 -0
  240. package/src/checkout/types.ts +292 -0
  241. package/src/index.ts +64 -0
  242. package/src/shared/api.ts +118 -0
  243. package/src/shared/format.ts +318 -0
  244. package/src/shared/polling.ts +49 -0
  245. package/src/shared/types.ts +13 -0
  246. package/src/shared/validation.ts +254 -0
  247. package/src/types/checkout-augmented.ts +77 -0
  248. package/src/types/external.d.ts +18 -0
  249. package/tools/jest.js +1 -0
  250. package/tsconfig.json +18 -0
@@ -0,0 +1,40 @@
1
+ import type { TCheckoutSessionExpanded } from '@blocklet/payment-types';
2
+ import type { CheckoutFormData } from '../types';
3
+ export declare const QUOTE_ERROR_CODES: readonly ["QUOTE_LOCK_EXPIRED", "QUOTE_AMOUNT_MISMATCH", "QUOTE_EXPIRED_OR_USED", "QUOTE_NOT_FOUND", "QUOTE_REQUIRED", "QUOTE_MAX_PAYABLE_EXCEEDED", "quote_validation_failed"];
4
+ export declare const RELAY_SOCKET_PREFIX = "/.well-known/service/relay";
5
+ export declare function getAppId(): string;
6
+ export declare function getRelayChannel(token: string): string;
7
+ export declare function getRelayProtocol(): string;
8
+ export declare function getSocketHost(): string;
9
+ /**
10
+ * Compute a fingerprint of the payment context for idempotency key stability.
11
+ * Key changes only when session state actually changes (quantity, upsell, currency).
12
+ * Same fingerprint → reuse Quote (intent: "retry with same Quote").
13
+ */
14
+ export declare function getSessionFingerprint(session: TCheckoutSessionExpanded | undefined | null, currencyId: string | null): string;
15
+ export declare function buildSubmitPayload(sessionId: string, currencyId: string | null, formValues: CheckoutFormData, session: TCheckoutSessionExpanded | undefined | null, priceConfirmed?: boolean, idempotencyKey?: string): {
16
+ price_confirmed?: boolean | undefined;
17
+ idempotency_key: string;
18
+ preview_rate: string | undefined;
19
+ customer_name: string;
20
+ customer_email?: string;
21
+ customer_phone?: string;
22
+ payment_method: string;
23
+ payment_currency: string;
24
+ billing_address?: {
25
+ country: string;
26
+ state?: string;
27
+ city?: string;
28
+ line1?: string;
29
+ line2?: string;
30
+ postal_code: string;
31
+ };
32
+ };
33
+ export declare function isQuoteError(errorCode: string): boolean;
34
+ export declare function abortStripePayment(sessionId: string): Promise<void>;
35
+ export declare function submitCheckout(sessionId: string, isDonation: boolean, payload: Record<string, unknown>): Promise<Record<string, unknown>>;
36
+ export declare function confirmFastCheckout(sessionId: string): Promise<Record<string, unknown>>;
37
+ export declare function updateSlippage(sessionId: string, config: {
38
+ mode: string;
39
+ percent: number;
40
+ }): Promise<Record<string, unknown>>;
@@ -0,0 +1,66 @@
1
+ import api, { API } from "../../shared/api.js";
2
+ import { generateIdempotencyKey } from "../../shared/polling.js";
3
+ export const QUOTE_ERROR_CODES = [
4
+ "QUOTE_LOCK_EXPIRED",
5
+ "QUOTE_AMOUNT_MISMATCH",
6
+ "QUOTE_EXPIRED_OR_USED",
7
+ "QUOTE_NOT_FOUND",
8
+ "QUOTE_REQUIRED",
9
+ "QUOTE_MAX_PAYABLE_EXCEEDED",
10
+ "quote_validation_failed"
11
+ ];
12
+ export const RELAY_SOCKET_PREFIX = "/.well-known/service/relay";
13
+ export function getAppId() {
14
+ const blocklet = window.blocklet;
15
+ return blocklet?.appPid || blocklet?.appId || "";
16
+ }
17
+ export function getRelayChannel(token) {
18
+ return `relay:${getAppId()}:${token}`;
19
+ }
20
+ export function getRelayProtocol() {
21
+ return window.location.protocol === "https:" ? "wss:" : "ws:";
22
+ }
23
+ export function getSocketHost() {
24
+ return new URL(window.location.href).host;
25
+ }
26
+ export function getSessionFingerprint(session, currencyId) {
27
+ if (!session) return "";
28
+ const items = session.line_items || [];
29
+ const sig = items.map((i) => `${i.upsell_price_id || i.price_id}:${i.quantity}`).join("|");
30
+ return `${session.id}-${currencyId}-${sig}`;
31
+ }
32
+ export function buildSubmitPayload(sessionId, currencyId, formValues, session, priceConfirmed = false, idempotencyKey) {
33
+ const lineItems = session?.line_items || [];
34
+ const matchedItem = lineItems.find((item) => item.exchange_rate);
35
+ const previewRate = matchedItem?.exchange_rate;
36
+ return {
37
+ ...formValues,
38
+ idempotency_key: idempotencyKey || generateIdempotencyKey(sessionId, currencyId || ""),
39
+ preview_rate: previewRate || void 0,
40
+ ...priceConfirmed && { price_confirmed: true }
41
+ };
42
+ }
43
+ export function isQuoteError(errorCode) {
44
+ return QUOTE_ERROR_CODES.includes(errorCode) || errorCode === "QUOTE_UPDATED";
45
+ }
46
+ export async function abortStripePayment(sessionId) {
47
+ try {
48
+ await api.post(API.ABORT_STRIPE(sessionId));
49
+ } catch {
50
+ }
51
+ }
52
+ export async function submitCheckout(sessionId, isDonation, payload) {
53
+ const url = isDonation ? API.DONATE_SUBMIT(sessionId) : API.SUBMIT(sessionId);
54
+ const { data } = await api.put(url, payload);
55
+ return data;
56
+ }
57
+ export async function confirmFastCheckout(sessionId) {
58
+ const { data } = await api.post(API.FAST_CHECKOUT_CONFIRM(sessionId), {});
59
+ return data;
60
+ }
61
+ export async function updateSlippage(sessionId, config) {
62
+ const { data } = await api.put(API.SLIPPAGE(sessionId), {
63
+ slippage_config: config
64
+ });
65
+ return data;
66
+ }
@@ -0,0 +1,34 @@
1
+ export { useCheckout } from './useCheckout';
2
+ export { useCheckoutSession } from './useCheckoutSession';
3
+ export type { UseCheckoutSessionReturn, SessionData } from './useCheckoutSession';
4
+ export { usePaymentMethod } from './usePaymentMethod';
5
+ export type { UsePaymentMethodReturn } from './usePaymentMethod';
6
+ export { usePricing } from './usePricing';
7
+ export type { UsePricingReturn } from './usePricing';
8
+ export { useCustomerForm } from './useCustomerForm';
9
+ export type { UseCustomerFormReturn } from './useCustomerForm';
10
+ export { useSubmit } from './useSubmit';
11
+ export type { UseSubmitReturn, VendorStatus, VendorOrderStatus } from './useSubmit';
12
+ export { useProduct } from './useProduct';
13
+ export type { UseProductReturn } from './useProduct';
14
+ export { useLineItems } from './useLineItems';
15
+ export type { UseLineItemsReturn } from './useLineItems';
16
+ export { useBillingInterval } from './useBillingInterval';
17
+ export type { UseBillingIntervalReturn, BillingIntervalData } from './useBillingInterval';
18
+ export { useUpsell } from './useUpsell';
19
+ export type { UseUpsellReturn } from './useUpsell';
20
+ export { useCrossSell } from './useCrossSell';
21
+ export type { UseCrossSellReturn } from './useCrossSell';
22
+ export { usePromotion } from './usePromotion';
23
+ export type { UsePromotionReturn } from './usePromotion';
24
+ export { useExchangeRate } from './useExchangeRate';
25
+ export type { UseExchangeRateReturn } from './useExchangeRate';
26
+ export { useSlippage } from './useSlippage';
27
+ export type { UseSlippageReturn } from './useSlippage';
28
+ export { useCheckoutStatus } from './useCheckoutStatus';
29
+ export type { UseCheckoutStatusReturn } from './useCheckoutStatus';
30
+ export { usePricingFeature } from './usePricingFeature';
31
+ export type { UsePricingFeatureReturn } from './usePricingFeature';
32
+ export { usePaymentMethodFeature } from './usePaymentMethodFeature';
33
+ export { useCustomerFormFeature } from './useCustomerFormFeature';
34
+ export { useSubmitFeature } from './useSubmitFeature';
@@ -0,0 +1,19 @@
1
+ export { useCheckout } from "./useCheckout.js";
2
+ export { useCheckoutSession } from "./useCheckoutSession.js";
3
+ export { usePaymentMethod } from "./usePaymentMethod.js";
4
+ export { usePricing } from "./usePricing.js";
5
+ export { useCustomerForm } from "./useCustomerForm.js";
6
+ export { useSubmit } from "./useSubmit.js";
7
+ export { useProduct } from "./useProduct.js";
8
+ export { useLineItems } from "./useLineItems.js";
9
+ export { useBillingInterval } from "./useBillingInterval.js";
10
+ export { useUpsell } from "./useUpsell.js";
11
+ export { useCrossSell } from "./useCrossSell.js";
12
+ export { usePromotion } from "./usePromotion.js";
13
+ export { useExchangeRate } from "./useExchangeRate.js";
14
+ export { useSlippage } from "./useSlippage.js";
15
+ export { useCheckoutStatus } from "./useCheckoutStatus.js";
16
+ export { usePricingFeature } from "./usePricingFeature.js";
17
+ export { usePaymentMethodFeature } from "./usePaymentMethodFeature.js";
18
+ export { useCustomerFormFeature } from "./useCustomerFormFeature.js";
19
+ export { useSubmitFeature } from "./useSubmitFeature.js";
@@ -0,0 +1,14 @@
1
+ import { type BillingIntervalType } from '../core/billingInterval';
2
+ export interface BillingIntervalData {
3
+ current: BillingIntervalType | null;
4
+ available: Array<{
5
+ interval: BillingIntervalType;
6
+ priceId: string;
7
+ amount: string;
8
+ savings: string | null;
9
+ }>;
10
+ switch: (interval: BillingIntervalType) => Promise<void>;
11
+ switching: boolean;
12
+ }
13
+ export type UseBillingIntervalReturn = BillingIntervalData | null;
14
+ export declare function useBillingInterval(): UseBillingIntervalReturn;
@@ -0,0 +1,50 @@
1
+ import { useState, useMemo } from "react";
2
+ import { useMemoizedFn } from "ahooks";
3
+ import { getErrorMessage } from "../../types/checkout-augmented.js";
4
+ import { useSessionContext } from "../context/SessionContext.js";
5
+ import { usePaymentMethodContext } from "../context/PaymentMethodContext.js";
6
+ import { parseBillingInterval } from "../core/billingInterval.js";
7
+ import { performUpsell, performDownsell } from "../core/lineItems.js";
8
+ export function useBillingInterval() {
9
+ const { items, session, effectiveSessionId, refresh } = useSessionContext();
10
+ const { currency } = usePaymentMethodContext();
11
+ const currencyId = currency?.id || null;
12
+ const [switching, setSwitching] = useState(false);
13
+ const upsell = useMemoizedFn(async (fromId, toId) => {
14
+ try {
15
+ await performUpsell(effectiveSessionId, fromId, toId, session, currencyId, refresh);
16
+ } catch (err) {
17
+ console.error("Failed to upsell:", getErrorMessage(err));
18
+ }
19
+ });
20
+ const downsell = useMemoizedFn(async (priceId) => {
21
+ try {
22
+ await performDownsell(effectiveSessionId, priceId, session, currencyId, refresh);
23
+ } catch (err) {
24
+ console.error("Failed to downsell:", getErrorMessage(err));
25
+ }
26
+ });
27
+ return useMemo(() => {
28
+ const parsed = parseBillingInterval(items);
29
+ if (!parsed) return null;
30
+ return {
31
+ current: parsed.current,
32
+ available: parsed.available,
33
+ switching,
34
+ switch: async (interval) => {
35
+ const target = parsed.available.find((a) => a.interval === interval);
36
+ if (!target || switching) return;
37
+ setSwitching(true);
38
+ try {
39
+ if (!parsed.firstItem.upsell_price_id && target.priceId) {
40
+ await upsell(parsed.firstItem.price_id, target.priceId);
41
+ } else if (parsed.firstItem.upsell_price_id) {
42
+ await downsell(parsed.firstItem.upsell_price_id);
43
+ }
44
+ } finally {
45
+ setSwitching(false);
46
+ }
47
+ }
48
+ };
49
+ }, [items, effectiveSessionId, switching]);
50
+ }
@@ -0,0 +1,2 @@
1
+ import type { UseCheckoutReturn } from '../types';
2
+ export declare function useCheckout(sessionId: string): UseCheckoutReturn;
@@ -0,0 +1,212 @@
1
+ import { useMemo, useEffect, useRef, useState } from "react";
2
+ import { useMemoizedFn } from "ahooks";
3
+ import { getErrorMessage } from "../../types/checkout-augmented.js";
4
+ import { useCheckoutSession } from "./useCheckoutSession.js";
5
+ import { usePaymentMethod } from "./usePaymentMethod.js";
6
+ import { usePricing } from "./usePricing.js";
7
+ import { useCustomerForm } from "./useCustomerForm.js";
8
+ import { useSubmit } from "./useSubmit.js";
9
+ import {
10
+ recalculatePromotionIfNeeded,
11
+ adjustQuantity,
12
+ performUpsell,
13
+ performDownsell,
14
+ changeDonationAmount,
15
+ getCrossSellItem
16
+ } from "../core/lineItems.js";
17
+ import { addCrossSellItem, removeCrossSellItem, fetchCrossSellItem } from "../core/crossSell.js";
18
+ import { parseBillingInterval } from "../core/billingInterval.js";
19
+ export function useCheckout(sessionId) {
20
+ const {
21
+ isLoading,
22
+ error,
23
+ errorCode,
24
+ refresh,
25
+ sessionData,
26
+ resolvedSessionId,
27
+ vendorCount,
28
+ product,
29
+ subscription,
30
+ pageInfo
31
+ } = useCheckoutSession(sessionId);
32
+ const session = sessionData?.checkoutSession;
33
+ const effectiveSessionId = resolvedSessionId || sessionId;
34
+ const paymentMethodHook = usePaymentMethod(sessionData, effectiveSessionId, refresh);
35
+ const pricingHook = usePricing(
36
+ sessionData,
37
+ effectiveSessionId,
38
+ paymentMethodHook.currency,
39
+ paymentMethodHook.isStripe,
40
+ refresh,
41
+ paymentMethodHook.current?.type || null
42
+ );
43
+ const formHook = useCustomerForm(
44
+ sessionData,
45
+ paymentMethodHook.currency?.id || null,
46
+ paymentMethodHook.current?.id || null
47
+ );
48
+ const isDonation = session?.submit_type === "donate";
49
+ const submitHook = useSubmit(
50
+ sessionData,
51
+ effectiveSessionId,
52
+ paymentMethodHook.currency?.id || null,
53
+ paymentMethodHook.isStripe,
54
+ paymentMethodHook.isCredit,
55
+ isDonation,
56
+ formHook.values,
57
+ formHook.validate,
58
+ refresh
59
+ );
60
+ const items = session?.line_items || [];
61
+ const currencyId = paymentMethodHook.currency?.id || null;
62
+ const prevCurrencyRef = useRef(null);
63
+ useEffect(() => {
64
+ const currId = paymentMethodHook.currency?.id || null;
65
+ if (!currId || !session) return;
66
+ if (prevCurrencyRef.current === null || currId !== prevCurrencyRef.current) {
67
+ prevCurrencyRef.current = currId;
68
+ recalculatePromotionIfNeeded(session, effectiveSessionId, currId).then(() => refresh(true));
69
+ }
70
+ }, [paymentMethodHook.currency?.id, session?.id]);
71
+ const updateQuantity = useMemoizedFn(async (itemId, qty) => {
72
+ try {
73
+ await adjustQuantity(effectiveSessionId, itemId, qty, currencyId, session, refresh);
74
+ } catch (err) {
75
+ console.error("Failed to update quantity:", getErrorMessage(err));
76
+ }
77
+ });
78
+ const upsell = useMemoizedFn(async (fromId, toId) => {
79
+ try {
80
+ await performUpsell(effectiveSessionId, fromId, toId, session, currencyId, refresh);
81
+ } catch (err) {
82
+ console.error("Failed to upsell:", getErrorMessage(err));
83
+ }
84
+ });
85
+ const downsell = useMemoizedFn(async (priceId) => {
86
+ try {
87
+ await performDownsell(effectiveSessionId, priceId, session, currencyId, refresh);
88
+ } catch (err) {
89
+ console.error("Failed to downsell:", getErrorMessage(err));
90
+ }
91
+ });
92
+ const embeddedCrossSellItem = useMemo(() => getCrossSellItem(items), [items]);
93
+ const [fetchedCrossSellItem, setFetchedCrossSellItem] = useState(null);
94
+ useEffect(() => {
95
+ if (!effectiveSessionId || !session) return void 0;
96
+ if (embeddedCrossSellItem) return void 0;
97
+ let cancelled = false;
98
+ fetchCrossSellItem(effectiveSessionId).then((item) => {
99
+ if (!cancelled && item) {
100
+ setFetchedCrossSellItem(item);
101
+ }
102
+ });
103
+ return () => {
104
+ cancelled = true;
105
+ };
106
+ }, [effectiveSessionId, session?.id, embeddedCrossSellItem]);
107
+ const crossSellItem = embeddedCrossSellItem || fetchedCrossSellItem;
108
+ const crossSellRequired = useMemo(() => items.some((item) => item.cross_sell_required), [items]);
109
+ const addCrossSell = useMemoizedFn(async () => {
110
+ if (!crossSellItem) return;
111
+ try {
112
+ await addCrossSellItem(effectiveSessionId, crossSellItem.id, session, currencyId, refresh);
113
+ } catch (err) {
114
+ console.error("Failed to add cross-sell:", getErrorMessage(err));
115
+ }
116
+ });
117
+ const removeCrossSell = useMemoizedFn(async () => {
118
+ try {
119
+ await removeCrossSellItem(effectiveSessionId, session, currencyId, refresh);
120
+ } catch (err) {
121
+ console.error("Failed to remove cross-sell:", getErrorMessage(err));
122
+ }
123
+ });
124
+ const billingInterval = useMemo(() => {
125
+ const parsed = parseBillingInterval(items);
126
+ if (!parsed) return null;
127
+ return {
128
+ current: parsed.current,
129
+ available: parsed.available,
130
+ switch: async (interval) => {
131
+ const target = parsed.available.find((a) => a.interval === interval);
132
+ if (!target) return;
133
+ if (!parsed.firstItem.upsell_price_id && target.priceId) {
134
+ await upsell(parsed.firstItem.price_id, target.priceId);
135
+ } else if (parsed.firstItem.upsell_price_id) {
136
+ await downsell(parsed.firstItem.upsell_price_id);
137
+ }
138
+ }
139
+ };
140
+ }, [items, effectiveSessionId]);
141
+ const setDonationAmount = useMemoizedFn(async (priceId, amount) => {
142
+ if (!isDonation) return;
143
+ try {
144
+ await changeDonationAmount(effectiveSessionId, priceId, amount, session, currencyId, refresh);
145
+ } catch (err) {
146
+ console.error("Failed to change amount:", getErrorMessage(err));
147
+ }
148
+ });
149
+ const canSubmit = submitHook.status === "idle" && !isLoading && !error && session?.status === "open";
150
+ const isCompleted = submitHook.status === "completed";
151
+ return {
152
+ isLoading,
153
+ error,
154
+ errorCode,
155
+ refresh: async () => {
156
+ await refresh();
157
+ },
158
+ product,
159
+ vendorCount,
160
+ lineItems: {
161
+ items: items.map((item) => ({
162
+ ...item,
163
+ adjustable_quantity: item.adjustable_quantity
164
+ })),
165
+ updateQuantity,
166
+ upsell,
167
+ downsell,
168
+ billingInterval,
169
+ crossSellItem,
170
+ crossSellRequired,
171
+ addCrossSell,
172
+ removeCrossSell
173
+ },
174
+ paymentMethod: {
175
+ current: paymentMethodHook.current,
176
+ currency: paymentMethodHook.currency,
177
+ available: paymentMethodHook.available,
178
+ currencies: paymentMethodHook.currencies,
179
+ isStripe: paymentMethodHook.isStripe,
180
+ isCrypto: paymentMethodHook.isCrypto,
181
+ isCredit: paymentMethodHook.isCredit,
182
+ setType: paymentMethodHook.setType,
183
+ types: paymentMethodHook.types,
184
+ setCurrency: paymentMethodHook.setCurrency
185
+ },
186
+ stripe: paymentMethodHook.stripe,
187
+ pricing: pricingHook,
188
+ form: formHook,
189
+ submit: submitHook,
190
+ subscription,
191
+ pageInfo,
192
+ canSubmit,
193
+ isCompleted,
194
+ isDonation,
195
+ setDonationAmount,
196
+ customer: sessionData?.customer ? {
197
+ name: sessionData.customer.name || "",
198
+ email: sessionData.customer.email || "",
199
+ phone: sessionData.customer.phone || "",
200
+ address: {
201
+ country: sessionData.customer.address?.country || "",
202
+ state: sessionData.customer.address?.state || "",
203
+ city: sessionData.customer.address?.city || "",
204
+ line1: sessionData.customer.address?.line1 || "",
205
+ line2: sessionData.customer.address?.line2 || "",
206
+ postal_code: sessionData.customer.address?.postal_code || ""
207
+ }
208
+ } : null,
209
+ livemode: !!session?.livemode,
210
+ session: session || null
211
+ };
212
+ }
@@ -0,0 +1,58 @@
1
+ import type { TCheckoutSessionExpanded, TPaymentMethodExpanded, TPaymentIntent, TCustomer } from '@blocklet/payment-types';
2
+ export interface SessionData {
3
+ checkoutSession: TCheckoutSessionExpanded;
4
+ paymentMethods: TPaymentMethodExpanded[];
5
+ paymentIntent?: TPaymentIntent;
6
+ customer?: TCustomer;
7
+ quotes?: Record<string, {
8
+ quote_id: string;
9
+ expires_at: number;
10
+ quoted_amount: string;
11
+ exchange_rate?: string;
12
+ rate_provider_name?: string;
13
+ rate_provider_id?: string;
14
+ }>;
15
+ rateUnavailable?: boolean;
16
+ rateError?: string;
17
+ }
18
+ export interface UseCheckoutSessionReturn {
19
+ isLoading: boolean;
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;
23
+ refresh: (forceRefresh?: boolean) => Promise<void>;
24
+ /** Directly update session data (e.g. after completion polling returns fresh data) */
25
+ setSessionData: (data: SessionData) => void;
26
+ sessionData: SessionData | null;
27
+ resolvedSessionId: string;
28
+ vendorCount: number;
29
+ product: {
30
+ name: string;
31
+ description: string;
32
+ images: string[];
33
+ features: Array<{
34
+ name: string;
35
+ icon?: string;
36
+ }>;
37
+ billing: {
38
+ mode: 'payment' | 'subscription';
39
+ interval: 'month' | 'year' | 'week' | 'day' | null;
40
+ intervalCount: number;
41
+ displayInterval: string;
42
+ };
43
+ metadata: Record<string, string>;
44
+ } | null;
45
+ subscription: {
46
+ mode: 'payment' | 'subscription' | 'setup';
47
+ showStake: boolean;
48
+ confirmMessage: string;
49
+ } | null;
50
+ pageInfo: {
51
+ formPurposeDescription?: {
52
+ en: string;
53
+ zh: string;
54
+ };
55
+ showProductFeatures: boolean;
56
+ } | null;
57
+ }
58
+ export declare function useCheckoutSession(sessionId: string): UseCheckoutSessionReturn;
@@ -0,0 +1,107 @@
1
+ import { useState, useCallback, useRef, useEffect } from "react";
2
+ import { useMemoizedFn } from "ahooks";
3
+ import api, { API } from "../../shared/api.js";
4
+ import { getAxiosErrorDetails } from "../../types/checkout-augmented.js";
5
+ import { parseProduct, parseSubscription, parsePageInfo } from "../core/session.js";
6
+ export function useCheckoutSession(sessionId) {
7
+ const [isLoading, setIsLoading] = useState(true);
8
+ const [error, setError] = useState(null);
9
+ const [sessionData, setSessionData] = useState(null);
10
+ const abortRef = useRef(null);
11
+ const mountedRef = useRef(true);
12
+ const resolvedIdRef = useRef(null);
13
+ const isPaymentLink = sessionId?.startsWith("plink_");
14
+ const resolveSessionId = useMemoizedFn(async () => {
15
+ if (!isPaymentLink) return sessionId;
16
+ if (resolvedIdRef.current) return resolvedIdRef.current;
17
+ const { data } = await api.post(API.START_PAYMENT_LINK(sessionId));
18
+ const resolved = data?.checkoutSession?.id;
19
+ if (!resolved) throw new Error("Failed to start payment link session");
20
+ resolvedIdRef.current = resolved;
21
+ return resolved;
22
+ });
23
+ const fetchSession = useMemoizedFn(async (forceRefresh = false) => {
24
+ if (abortRef.current) {
25
+ abortRef.current.abort();
26
+ }
27
+ abortRef.current = new AbortController();
28
+ if (!sessionId) {
29
+ setIsLoading(false);
30
+ setError("Session ID is required");
31
+ return;
32
+ }
33
+ try {
34
+ if (!forceRefresh) {
35
+ setIsLoading(true);
36
+ }
37
+ setError(null);
38
+ const realId = await resolveSessionId();
39
+ const url = forceRefresh ? `${API.RETRIEVE_SESSION(realId)}?forceRefresh=1` : API.RETRIEVE_SESSION(realId);
40
+ const { data } = await api.get(url, {
41
+ signal: abortRef.current.signal
42
+ });
43
+ if (!mountedRef.current) return;
44
+ const cs = data?.checkoutSession;
45
+ if (cs) {
46
+ if (cs.status !== "complete" && cs.expires_at && cs.expires_at <= Math.round(Date.now() / 1e3)) {
47
+ setError("SESSION_EXPIRED");
48
+ setIsLoading(false);
49
+ return;
50
+ }
51
+ if (!cs.line_items?.length) {
52
+ setError("EMPTY_LINE_ITEMS");
53
+ setIsLoading(false);
54
+ return;
55
+ }
56
+ }
57
+ setSessionData(data);
58
+ setIsLoading(false);
59
+ } catch (err) {
60
+ if (!mountedRef.current) return;
61
+ const { isCancelled, message } = getAxiosErrorDetails(err);
62
+ if (isCancelled) return;
63
+ setError(message || "Failed to load checkout session");
64
+ setIsLoading(false);
65
+ }
66
+ });
67
+ const refresh = useCallback(
68
+ async (forceRefresh = false) => {
69
+ await fetchSession(forceRefresh);
70
+ },
71
+ [fetchSession]
72
+ );
73
+ useEffect(() => {
74
+ mountedRef.current = true;
75
+ fetchSession();
76
+ return () => {
77
+ mountedRef.current = false;
78
+ if (abortRef.current) {
79
+ abortRef.current.abort();
80
+ }
81
+ };
82
+ }, [sessionId]);
83
+ const session = sessionData?.checkoutSession;
84
+ const resolvedSessionId = resolvedIdRef.current || (isPaymentLink ? "" : sessionId);
85
+ let errorCode = null;
86
+ if (error === "SESSION_EXPIRED") {
87
+ errorCode = "SESSION_EXPIRED";
88
+ } else if (error === "EMPTY_LINE_ITEMS") {
89
+ errorCode = "EMPTY_LINE_ITEMS";
90
+ }
91
+ const vendorCount = session?.line_items?.reduce((count, item) => {
92
+ return count + (item?.price?.product?.vendor_config?.length || 0);
93
+ }, 0) || 0;
94
+ return {
95
+ isLoading,
96
+ error,
97
+ errorCode,
98
+ refresh,
99
+ setSessionData,
100
+ sessionData,
101
+ resolvedSessionId,
102
+ vendorCount,
103
+ product: session ? parseProduct(session) : null,
104
+ subscription: session ? parseSubscription(session) : null,
105
+ pageInfo: session ? parsePageInfo(session) : null
106
+ };
107
+ }
@@ -0,0 +1,10 @@
1
+ export interface UseCheckoutStatusReturn {
2
+ isLoading: boolean;
3
+ error: string | null;
4
+ errorCode: 'SESSION_EXPIRED' | 'EMPTY_LINE_ITEMS' | null;
5
+ canSubmit: boolean;
6
+ isCompleted: boolean;
7
+ isDonation: boolean;
8
+ livemode: boolean;
9
+ }
10
+ export declare function useCheckoutStatus(): UseCheckoutStatusReturn;
@@ -0,0 +1,16 @@
1
+ import { useSessionContext } from "../context/SessionContext.js";
2
+ export function useCheckoutStatus() {
3
+ const { isLoading, error, errorCode, session, isDonation } = useSessionContext();
4
+ const canSubmit = !isLoading && !error && session?.status === "open";
5
+ const isCompleted = session?.status === "complete";
6
+ const livemode = !!session?.livemode;
7
+ return {
8
+ isLoading,
9
+ error,
10
+ errorCode,
11
+ canSubmit,
12
+ isCompleted,
13
+ isDonation,
14
+ livemode
15
+ };
16
+ }
@@ -0,0 +1,8 @@
1
+ import type { TPrice } from '@blocklet/payment-types';
2
+ export interface UseCrossSellReturn {
3
+ item: TPrice | null;
4
+ required: boolean;
5
+ add: () => Promise<void>;
6
+ remove: () => Promise<void>;
7
+ }
8
+ export declare function useCrossSell(): UseCrossSellReturn;