@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,25 @@
1
+ import api, { API } from "../../shared/api.js";
2
+ export const BASE_INTERVAL = 3e4;
3
+ export const MAX_INTERVAL = 12e4;
4
+ export function checkHasDynamicPricing(items) {
5
+ return items.some((item) => (item.upsell_price || item.price)?.pricing_type === "dynamic");
6
+ }
7
+ export async function fetchExchangeRate(sessionId, currencyId) {
8
+ const { data } = await api.get(API.EXCHANGE_RATE(sessionId), {
9
+ params: currencyId ? { currency_id: currencyId } : void 0
10
+ });
11
+ const rateValue = data.rate || data.exchange_rate;
12
+ if (rateValue) {
13
+ const providerDisplay = data.provider_display || data.provider_name || data.provider_id || null;
14
+ return {
15
+ rate: rateValue,
16
+ provider: data.provider_name || data.rate_provider_name || null,
17
+ providerDisplay,
18
+ fetchedAt: data.fetched_at || data.timestamp_ms || null
19
+ };
20
+ }
21
+ return { rate: null, provider: null, providerDisplay: null, fetchedAt: null };
22
+ }
23
+ export function getBackoffInterval(failCount) {
24
+ return Math.min(BASE_INTERVAL * 2 ** failCount, MAX_INTERVAL);
25
+ }
@@ -0,0 +1,10 @@
1
+ export { parseProduct, parseSubscription, parsePageInfo } from './session';
2
+ export { flattenCurrencies, hasDidWallet, getInitialCurrencyId, findMethodAndCurrency, buildPaymentTypes, } from './paymentMethod';
3
+ export { BASE_INTERVAL, MAX_INTERVAL, checkHasDynamicPricing, fetchExchangeRate, getBackoffInterval, } from './exchangeRate';
4
+ export { calculateAmounts, calculateQuoteMeta, calculateTrial, type CalculatedAmounts, type QuoteMeta, } from './pricing';
5
+ export { extractPromotionCodeFromSession, applyPromotionCode, removePromotionCode, recalculatePromotion, isPromotionActive, hasAppliedDiscounts, } from './promotion';
6
+ export { recalculatePromotionIfNeeded, adjustQuantity, performUpsell, performDownsell, changeDonationAmount, getCrossSellItem, } from './lineItems';
7
+ export { addCrossSellItem, removeCrossSellItem, fetchCrossSellItem } from './crossSell';
8
+ export { parseBillingInterval, type BillingIntervalType, type BillingIntervalOption, type BillingIntervalInfo, } from './billingInterval';
9
+ export { buildFields, createInitialValues, setNestedValue } from './customerForm';
10
+ export { QUOTE_ERROR_CODES, RELAY_SOCKET_PREFIX, getAppId, getRelayChannel, getRelayProtocol, getSocketHost, buildSubmitPayload, isQuoteError, abortStripePayment, submitCheckout, confirmFastCheckout, updateSlippage, } from './submit';
@@ -0,0 +1,55 @@
1
+ export { parseProduct, parseSubscription, parsePageInfo } from "./session.js";
2
+ export {
3
+ flattenCurrencies,
4
+ hasDidWallet,
5
+ getInitialCurrencyId,
6
+ findMethodAndCurrency,
7
+ buildPaymentTypes
8
+ } from "./paymentMethod.js";
9
+ export {
10
+ BASE_INTERVAL,
11
+ MAX_INTERVAL,
12
+ checkHasDynamicPricing,
13
+ fetchExchangeRate,
14
+ getBackoffInterval
15
+ } from "./exchangeRate.js";
16
+ export {
17
+ calculateAmounts,
18
+ calculateQuoteMeta,
19
+ calculateTrial
20
+ } from "./pricing.js";
21
+ export {
22
+ extractPromotionCodeFromSession,
23
+ applyPromotionCode,
24
+ removePromotionCode,
25
+ recalculatePromotion,
26
+ isPromotionActive,
27
+ hasAppliedDiscounts
28
+ } from "./promotion.js";
29
+ export {
30
+ recalculatePromotionIfNeeded,
31
+ adjustQuantity,
32
+ performUpsell,
33
+ performDownsell,
34
+ changeDonationAmount,
35
+ getCrossSellItem
36
+ } from "./lineItems.js";
37
+ export { addCrossSellItem, removeCrossSellItem, fetchCrossSellItem } from "./crossSell.js";
38
+ export {
39
+ parseBillingInterval
40
+ } from "./billingInterval.js";
41
+ export { buildFields, createInitialValues, setNestedValue } from "./customerForm.js";
42
+ export {
43
+ QUOTE_ERROR_CODES,
44
+ RELAY_SOCKET_PREFIX,
45
+ getAppId,
46
+ getRelayChannel,
47
+ getRelayProtocol,
48
+ getSocketHost,
49
+ buildSubmitPayload,
50
+ isQuoteError,
51
+ abortStripePayment,
52
+ submitCheckout,
53
+ confirmFastCheckout,
54
+ updateSlippage
55
+ } from "./submit.js";
@@ -0,0 +1,7 @@
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>;
7
+ export declare function getCrossSellItem(items: TLineItemExpanded[]): TPrice | null;
@@ -0,0 +1,59 @@
1
+ import api, { API } from "../../shared/api.js";
2
+ import { recalculatePromotion, hasAppliedDiscounts } from "./promotion.js";
3
+ export async function recalculatePromotionIfNeeded(session, sessionId, currencyId) {
4
+ if (!hasAppliedDiscounts(session)) return;
5
+ try {
6
+ await recalculatePromotion(sessionId, currencyId);
7
+ } catch {
8
+ }
9
+ }
10
+ export async function adjustQuantity(sessionId, itemId, qty, currencyId, session, refresh) {
11
+ await api.put(API.ADJUST_QUANTITY(sessionId), {
12
+ itemId,
13
+ quantity: qty,
14
+ currency_id: currencyId
15
+ });
16
+ await recalculatePromotionIfNeeded(session, sessionId, currencyId);
17
+ await refresh(true);
18
+ }
19
+ export async function performUpsell(sessionId, fromId, toId, session, currencyId, refresh) {
20
+ if ((session?.line_items?.length || 0) > 1) {
21
+ try {
22
+ await api.delete(API.CROSS_SELL(sessionId));
23
+ } catch {
24
+ }
25
+ }
26
+ await api.put(API.UPSELL(sessionId), { from: fromId, to: toId });
27
+ await recalculatePromotionIfNeeded(session, sessionId, currencyId);
28
+ await refresh(true);
29
+ }
30
+ export async function performDownsell(sessionId, priceId, session, currencyId, refresh) {
31
+ if ((session?.line_items?.length || 0) > 1) {
32
+ try {
33
+ await api.delete(API.CROSS_SELL(sessionId));
34
+ } catch {
35
+ }
36
+ }
37
+ await api.put(API.DOWNSELL(sessionId), { from: priceId });
38
+ await recalculatePromotionIfNeeded(session, sessionId, currencyId);
39
+ await refresh(true);
40
+ }
41
+ export async function changeDonationAmount(sessionId, priceId, amount, session, currencyId, refresh) {
42
+ const { data } = await api.put(API.CHANGE_AMOUNT(sessionId), {
43
+ priceId,
44
+ amount
45
+ });
46
+ if (data?.discounts?.length) {
47
+ await recalculatePromotionIfNeeded(session, sessionId, currencyId);
48
+ }
49
+ await refresh(true);
50
+ }
51
+ export function getCrossSellItem(items) {
52
+ for (const item of items) {
53
+ const crossSell = item.price?.cross_sell;
54
+ if (crossSell?.cross_sells_to) {
55
+ return crossSell.cross_sells_to;
56
+ }
57
+ }
58
+ return null;
59
+ }
@@ -0,0 +1,23 @@
1
+ import type { TPaymentMethodExpanded, TPaymentCurrency, TCheckoutSessionExpanded } from '@blocklet/payment-types';
2
+ import type { CheckoutSessionUser } from '../../types/checkout-augmented';
3
+ export type CurrencyWithMethod = TPaymentCurrency & {
4
+ method?: {
5
+ id: string;
6
+ name: string;
7
+ type: string;
8
+ };
9
+ };
10
+ export declare function flattenCurrencies(methods: TPaymentMethodExpanded[]): CurrencyWithMethod[];
11
+ export declare function hasDidWallet(user: CheckoutSessionUser | undefined): boolean;
12
+ export declare function getCurrencyStorageKey(did?: string): string;
13
+ export declare function getInitialCurrencyId(session: TCheckoutSessionExpanded | undefined | null, methods: TPaymentMethodExpanded[]): string | null;
14
+ export declare function findMethodAndCurrency(methods: TPaymentMethodExpanded[], currencyId: string | null): {
15
+ method: TPaymentMethodExpanded | null;
16
+ currency: TPaymentCurrency | null;
17
+ };
18
+ export declare function buildPaymentTypes(methods: TPaymentMethodExpanded[], currentMethod: TPaymentMethodExpanded | null): {
19
+ type: "stripe" | "crypto";
20
+ label: string;
21
+ currencies: CurrencyWithMethod[];
22
+ active: boolean;
23
+ }[];
@@ -0,0 +1,85 @@
1
+ export function flattenCurrencies(methods) {
2
+ const out = [];
3
+ methods.forEach((method) => {
4
+ const currencies = method.payment_currencies || [];
5
+ currencies.forEach((currency) => {
6
+ const enriched = {
7
+ ...currency,
8
+ method: { id: method.id, name: method.name, type: method.type }
9
+ };
10
+ out.push(enriched);
11
+ });
12
+ });
13
+ return out;
14
+ }
15
+ export function hasDidWallet(user) {
16
+ const connected = user?.connectedAccounts || user?.extraConfigs?.connectedAccounts || [];
17
+ return connected.some((x) => x.provider === "wallet");
18
+ }
19
+ const CURRENCY_PREFERENCE_KEY = "payment-currency-preference";
20
+ export function getCurrencyStorageKey(did) {
21
+ return did ? `${CURRENCY_PREFERENCE_KEY}:${did}` : CURRENCY_PREFERENCE_KEY;
22
+ }
23
+ export function getInitialCurrencyId(session, methods) {
24
+ if (typeof window !== "undefined") {
25
+ try {
26
+ const params = new URLSearchParams(window.location.search);
27
+ const urlCurrency = params.get("currencyId") || params.get("currency_id");
28
+ if (urlCurrency) return urlCurrency;
29
+ } catch {
30
+ }
31
+ const user = session?.user;
32
+ if (user && !hasDidWallet(user)) {
33
+ const stripeMethod = methods.find((m) => m.type === "stripe");
34
+ const stripeCurrency = stripeMethod?.payment_currencies?.[0];
35
+ if (stripeCurrency) return stripeCurrency.id;
36
+ }
37
+ try {
38
+ const did = session?.user?.did;
39
+ const stored = localStorage.getItem(getCurrencyStorageKey(did));
40
+ if (stored) return stored;
41
+ } catch {
42
+ }
43
+ }
44
+ return session?.currency_id || null;
45
+ }
46
+ export function findMethodAndCurrency(methods, currencyId) {
47
+ if (!currencyId) {
48
+ const first2 = methods[0];
49
+ const firstCurrency = first2?.payment_currencies?.[0] || null;
50
+ return { method: first2 || null, currency: firstCurrency };
51
+ }
52
+ for (const method of methods) {
53
+ for (const currency of method.payment_currencies || []) {
54
+ if (currency.id === currencyId) {
55
+ return { method, currency };
56
+ }
57
+ }
58
+ }
59
+ const first = methods[0];
60
+ return { method: first || null, currency: first?.payment_currencies?.[0] || null };
61
+ }
62
+ export function buildPaymentTypes(methods, currentMethod) {
63
+ const typeMap = /* @__PURE__ */ new Map();
64
+ methods.forEach((method) => {
65
+ const type = method.type === "stripe" ? "stripe" : "crypto";
66
+ if (!typeMap.has(type)) {
67
+ typeMap.set(type, { currencies: [], active: false });
68
+ }
69
+ const entry = typeMap.get(type);
70
+ const enriched = (method.payment_currencies || []).map((cur) => ({
71
+ ...cur,
72
+ method: { id: method.id, name: method.name, type: method.type }
73
+ }));
74
+ entry.currencies.push(...enriched);
75
+ if (method === currentMethod) {
76
+ entry.active = true;
77
+ }
78
+ });
79
+ return Array.from(typeMap.entries()).map(([type, data]) => ({
80
+ type,
81
+ label: type === "stripe" ? "Card" : "Crypto",
82
+ currencies: data.currencies,
83
+ active: data.active
84
+ }));
85
+ }
@@ -0,0 +1,32 @@
1
+ import type { TCheckoutSessionExpanded, TPaymentCurrency, TLineItemExpanded } from '@blocklet/payment-types';
2
+ import type { SessionData } from '../hooks/useCheckoutSession';
3
+ export interface CalculatedAmounts {
4
+ subtotal: string;
5
+ paymentAmount: string;
6
+ total: string;
7
+ discount: string | null;
8
+ tax: {
9
+ amount: string;
10
+ rate: number;
11
+ inclusive: boolean;
12
+ } | null;
13
+ staking: string | null;
14
+ usdEquivalent: string | null;
15
+ subtotalUsdEquivalent?: string | null;
16
+ }
17
+ export declare function calculateAmounts(items: TLineItemExpanded[], currency: TPaymentCurrency | null, session: TCheckoutSessionExpanded | undefined | null, exchangeRate: string | null, hasDynamicPricing: boolean, paymentMethodType?: string | null): CalculatedAmounts;
18
+ export interface QuoteMeta {
19
+ locked: boolean;
20
+ lockedAt: number | null;
21
+ expiresAt: number | null;
22
+ expired: boolean;
23
+ baseCurrency: string | null;
24
+ slippagePercent: number | null;
25
+ }
26
+ export declare function calculateQuoteMeta(items: TLineItemExpanded[], hasDynamicPricing: boolean, sessionData: SessionData | null): QuoteMeta;
27
+ export declare function calculateTrial(session: TCheckoutSessionExpanded | undefined | null, currencyId: string | null | undefined, items: TLineItemExpanded[], currency: TPaymentCurrency | null, hasDynamicPricing: boolean, exchangeRate: string | null): {
28
+ active: boolean;
29
+ days: number;
30
+ afterTrialPrice: string | null;
31
+ afterTrialInterval: string | null;
32
+ };
@@ -0,0 +1,221 @@
1
+ import { BN, fromUnitToToken, fromTokenToUnit } from "@ocap/util";
2
+ import {
3
+ formatDynamicPrice,
4
+ formatUsdAmount,
5
+ getUsdAmountFromTokenUnits,
6
+ getCheckoutAmount,
7
+ getPriceUnitAmountByCurrency
8
+ } from "../../shared/format.js";
9
+ function getStakingSetup(items, currency, billingThreshold = 0) {
10
+ const staking = { licensed: new BN(0), metered: new BN(0) };
11
+ const recurringItems = items.map((x) => x.upsell_price || x.price).filter((x) => x?.type === "recurring" && x?.recurring);
12
+ if (recurringItems.length > 0) {
13
+ if (+billingThreshold) {
14
+ return fromTokenToUnit(billingThreshold, currency.decimal).toString();
15
+ }
16
+ items.forEach((x) => {
17
+ const price = x.upsell_price || x.price;
18
+ const unit = getPriceUnitAmountByCurrency(price, currency);
19
+ const amount = new BN(unit).mul(new BN(x.quantity));
20
+ if (price?.type === "recurring" && price?.recurring) {
21
+ if (price.recurring.usage_type === "licensed") {
22
+ staking.licensed = staking.licensed.add(amount);
23
+ }
24
+ if (price.recurring.usage_type === "metered") {
25
+ staking.metered = staking.metered.add(amount);
26
+ }
27
+ }
28
+ });
29
+ return staking.licensed.add(staking.metered).toString();
30
+ }
31
+ return "0";
32
+ }
33
+ function calculateCouponDiscount(items, currency, session, hasDynamicPricing, exchangeRate, trialing, recurringOnly) {
34
+ const discounts = session?.discounts || [];
35
+ if (discounts.length === 0) return new BN(0);
36
+ const coupon = discounts[0].coupon_details || {};
37
+ if (recurringOnly && coupon.duration === "once") return new BN(0);
38
+ const discountableItems = items.filter((item) => {
39
+ if (item.discountable === false) return false;
40
+ const price = item.upsell_price || item.price;
41
+ if (price?.recurring?.usage_type === "metered") return false;
42
+ return true;
43
+ });
44
+ const discountableResult = getCheckoutAmount(discountableItems, currency, trialing, true, {
45
+ exchangeRate: hasDynamicPricing ? exchangeRate : null
46
+ });
47
+ const discountableSubtotalBN = new BN(discountableResult.total);
48
+ if (discountableSubtotalBN.lte(new BN(0))) return new BN(0);
49
+ if (coupon.percent_off && coupon.percent_off > 0) {
50
+ return discountableSubtotalBN.mul(new BN(coupon.percent_off)).div(new BN(100));
51
+ }
52
+ if (coupon.amount_off && coupon.amount_off !== "0") {
53
+ const amountOff = coupon.currency_options?.[currency.id]?.amount_off || coupon.amount_off;
54
+ if (amountOff && amountOff !== "0") {
55
+ const amountOffBN = new BN(amountOff);
56
+ return amountOffBN.lt(discountableSubtotalBN) ? amountOffBN : discountableSubtotalBN;
57
+ }
58
+ }
59
+ return new BN(0);
60
+ }
61
+ export function calculateAmounts(items, currency, session, exchangeRate, hasDynamicPricing, paymentMethodType) {
62
+ if (!currency || items.length === 0) {
63
+ return {
64
+ subtotal: "0",
65
+ paymentAmount: "0",
66
+ total: "0",
67
+ discount: null,
68
+ tax: null,
69
+ staking: null,
70
+ usdEquivalent: null
71
+ };
72
+ }
73
+ const subData = session?.subscription_data;
74
+ const trialDays = Number(subData?.trial_period_days || 0);
75
+ const trialing = trialDays > 0;
76
+ const result = getCheckoutAmount(items, currency, trialing, true, {
77
+ exchangeRate: hasDynamicPricing ? exchangeRate : null
78
+ });
79
+ const subtotalBN = new BN(result.total);
80
+ const subtotalFormatted = formatDynamicPrice(fromUnitToToken(subtotalBN, currency.decimal), hasDynamicPricing);
81
+ const discountBN = calculateCouponDiscount(items, currency, session, hasDynamicPricing, exchangeRate, trialing, false);
82
+ const discount = discountBN.gt(new BN(0)) ? formatDynamicPrice(fromUnitToToken(discountBN.toString(), currency.decimal), hasDynamicPricing) : null;
83
+ const taxAmount = session?.total_details?.amount_tax;
84
+ const taxBN = new BN(taxAmount || "0");
85
+ const totalDetails = session?.total_details;
86
+ const tax = taxAmount && taxAmount !== "0" ? {
87
+ amount: fromUnitToToken(taxAmount, currency.decimal),
88
+ rate: totalDetails?.tax_rate || 0,
89
+ inclusive: totalDetails?.tax_inclusive || false
90
+ } : null;
91
+ const noStake = subData?.no_stake || false;
92
+ const billingThreshold = Math.max(
93
+ Number(subData?.billing_threshold_amount || 0),
94
+ Number(subData?.min_stake_amount || 0)
95
+ );
96
+ const shouldShowStaking = paymentMethodType === "arcblock" && currency.type !== "credit" && !noStake;
97
+ const stakingUnitStr = shouldShowStaking ? getStakingSetup(items, currency, billingThreshold) : "0";
98
+ const stakingBN = new BN(stakingUnitStr);
99
+ const subtotalWithStakingBN = subtotalBN.add(stakingBN);
100
+ let totalBN = subtotalWithStakingBN.sub(discountBN);
101
+ if (taxBN.gt(new BN(0)) && !tax?.inclusive) {
102
+ totalBN = totalBN.add(taxBN);
103
+ }
104
+ if (totalBN.isNeg()) {
105
+ totalBN = new BN(0);
106
+ }
107
+ const totalFormatted = formatDynamicPrice(fromUnitToToken(totalBN, currency.decimal), hasDynamicPricing);
108
+ const displaySubtotalBN = subtotalWithStakingBN;
109
+ const displaySubtotalFormatted = formatDynamicPrice(
110
+ fromUnitToToken(displaySubtotalBN, currency.decimal),
111
+ hasDynamicPricing
112
+ );
113
+ let usdEquivalent = null;
114
+ let subtotalUsdEquivalent = null;
115
+ if (hasDynamicPricing && exchangeRate && currency) {
116
+ const totalUsd = getUsdAmountFromTokenUnits(totalBN.toString(), currency.decimal, exchangeRate);
117
+ if (totalUsd) {
118
+ usdEquivalent = formatUsdAmount(totalUsd) || null;
119
+ }
120
+ const subtotalUsd = getUsdAmountFromTokenUnits(displaySubtotalBN.toString(), currency.decimal, exchangeRate);
121
+ if (subtotalUsd) {
122
+ subtotalUsdEquivalent = formatUsdAmount(subtotalUsd) || null;
123
+ }
124
+ }
125
+ const stakingFormatted = stakingBN.gt(new BN(0)) ? `${formatDynamicPrice(fromUnitToToken(stakingBN.toString(), currency.decimal), hasDynamicPricing)} ${currency.symbol}` : null;
126
+ return {
127
+ subtotal: `${displaySubtotalFormatted} ${currency.symbol}`,
128
+ paymentAmount: `${subtotalFormatted} ${currency.symbol}`,
129
+ total: `${totalFormatted} ${currency.symbol}`,
130
+ discount: discount ? `${discount} ${currency.symbol}` : null,
131
+ tax,
132
+ staking: stakingFormatted,
133
+ usdEquivalent: usdEquivalent ? `$${usdEquivalent}` : null,
134
+ subtotalUsdEquivalent: subtotalUsdEquivalent ? `$${subtotalUsdEquivalent}` : null
135
+ };
136
+ }
137
+ export function calculateQuoteMeta(items, hasDynamicPricing, sessionData) {
138
+ if (!items.length || !hasDynamicPricing) {
139
+ return {
140
+ locked: false,
141
+ lockedAt: null,
142
+ expiresAt: null,
143
+ expired: false,
144
+ baseCurrency: null,
145
+ slippagePercent: null
146
+ };
147
+ }
148
+ const session = sessionData?.checkoutSession;
149
+ let baseCurrency = null;
150
+ let expiresAt = null;
151
+ let slippagePercent = null;
152
+ items.forEach((item) => {
153
+ const price = item.upsell_price || item.price;
154
+ if (!baseCurrency && price?.base_currency) baseCurrency = price.base_currency;
155
+ const expires = item.expires_at;
156
+ if (expires) {
157
+ expiresAt = expiresAt === null ? expires : Math.min(expiresAt, expires);
158
+ }
159
+ const itemSlippage = item.slippage_percent;
160
+ if (slippagePercent === null && Number.isFinite(Number(itemSlippage))) {
161
+ slippagePercent = Number(itemSlippage);
162
+ }
163
+ });
164
+ const piCreated = sessionData?.paymentIntent?.created_at ? new Date(sessionData.paymentIntent.created_at).getTime() : null;
165
+ const sessionLock = session?.quote_locked_at;
166
+ const lockedAt = piCreated || sessionLock || null;
167
+ const locked = !!lockedAt;
168
+ const now = Date.now();
169
+ const expired = expiresAt ? now > expiresAt : false;
170
+ return { locked, lockedAt, expiresAt, expired, baseCurrency, slippagePercent };
171
+ }
172
+ export function calculateTrial(session, currencyId, items, currency, hasDynamicPricing, exchangeRate) {
173
+ const subData = session?.subscription_data;
174
+ let trialDays = Number(subData?.trial_period_days || 0);
175
+ if (trialDays > 0 && currencyId) {
176
+ const trialCurrencyIds = (subData?.trial_currency || "").split(",").map((s) => s.trim()).filter(Boolean);
177
+ if (trialCurrencyIds.length > 0 && !trialCurrencyIds.includes(currencyId)) {
178
+ trialDays = 0;
179
+ }
180
+ }
181
+ const trialActive = trialDays > 0;
182
+ let afterTrialPrice = null;
183
+ let afterTrialInterval = null;
184
+ if (trialActive && currency && items.length > 0) {
185
+ const noTrialResult = getCheckoutAmount(items, currency, false, true, {
186
+ exchangeRate: hasDynamicPricing ? exchangeRate : null
187
+ });
188
+ let afterTrialBN = new BN(noTrialResult.total);
189
+ const recurringDiscount = calculateCouponDiscount(
190
+ items,
191
+ currency,
192
+ session,
193
+ hasDynamicPricing,
194
+ exchangeRate,
195
+ false,
196
+ true
197
+ );
198
+ if (recurringDiscount.gt(new BN(0))) {
199
+ afterTrialBN = afterTrialBN.sub(recurringDiscount);
200
+ if (afterTrialBN.isNeg()) afterTrialBN = new BN(0);
201
+ }
202
+ afterTrialPrice = `${formatDynamicPrice(
203
+ fromUnitToToken(afterTrialBN, currency.decimal),
204
+ hasDynamicPricing
205
+ )} ${currency.symbol}`;
206
+ const recurringItem = items.find((item) => {
207
+ const price = item.upsell_price || item.price;
208
+ return price?.type === "recurring" && price?.recurring;
209
+ });
210
+ if (recurringItem) {
211
+ const price = recurringItem.upsell_price || recurringItem.price;
212
+ afterTrialInterval = price?.recurring?.interval || null;
213
+ }
214
+ }
215
+ return {
216
+ active: trialActive,
217
+ days: trialDays,
218
+ afterTrialPrice,
219
+ afterTrialInterval
220
+ };
221
+ }
@@ -0,0 +1,10 @@
1
+ import type { TCheckoutSessionExpanded } from '@blocklet/payment-types';
2
+ export declare function extractPromotionCodeFromSession(session: TCheckoutSessionExpanded | undefined | null): string | null;
3
+ export declare function applyPromotionCode(sessionId: string, code: string, currencyId: string | null | undefined): Promise<{
4
+ success: boolean;
5
+ error?: string;
6
+ }>;
7
+ export declare function removePromotionCode(sessionId: string): Promise<void>;
8
+ export declare function recalculatePromotion(sessionId: string, currencyId: string | null | undefined): Promise<void>;
9
+ export declare function isPromotionActive(session: TCheckoutSessionExpanded | undefined | null): boolean;
10
+ export declare function hasAppliedDiscounts(session: TCheckoutSessionExpanded | undefined | null): boolean;
@@ -0,0 +1,39 @@
1
+ import api, { API } from "../../shared/api.js";
2
+ import { getErrorMessage } from "../../types/checkout-augmented.js";
3
+ export function extractPromotionCodeFromSession(session) {
4
+ const discounts = session?.discounts || [];
5
+ if (discounts.length === 0) return null;
6
+ const firstDiscount = discounts[0];
7
+ const details = firstDiscount.promotion_code_details;
8
+ const verification = firstDiscount.verification_data;
9
+ return details?.code || verification?.code || firstDiscount.promotion_code || firstDiscount.coupon || null;
10
+ }
11
+ export async function applyPromotionCode(sessionId, code, currencyId) {
12
+ try {
13
+ const { data } = await api.post(API.APPLY_PROMOTION(sessionId), {
14
+ promotion_code: code,
15
+ currency_id: currencyId
16
+ });
17
+ if (data.error) {
18
+ return { success: false, error: data.error };
19
+ }
20
+ return { success: true };
21
+ } catch (err) {
22
+ return { success: false, error: getErrorMessage(err) || "Invalid promotion code" };
23
+ }
24
+ }
25
+ export async function removePromotionCode(sessionId) {
26
+ await api.delete(API.REMOVE_PROMOTION(sessionId));
27
+ }
28
+ export async function recalculatePromotion(sessionId, currencyId) {
29
+ await api.post(API.RECALCULATE_PROMOTION_SESSION(sessionId), {
30
+ currency_id: currencyId
31
+ });
32
+ }
33
+ export function isPromotionActive(session) {
34
+ return session?.allow_promotion_codes !== false;
35
+ }
36
+ export function hasAppliedDiscounts(session) {
37
+ const discounts = session?.discounts || [];
38
+ return discounts.length > 0;
39
+ }
@@ -0,0 +1,26 @@
1
+ import type { TCheckoutSessionExpanded } from '@blocklet/payment-types';
2
+ export declare function parseProduct(session: TCheckoutSessionExpanded): {
3
+ name: string;
4
+ description: string;
5
+ images: any[];
6
+ features: {
7
+ name: string;
8
+ icon: string | undefined;
9
+ }[];
10
+ billing: {
11
+ mode: "payment" | "subscription";
12
+ interval: "month" | "year" | "week" | "day" | null;
13
+ intervalCount: number;
14
+ displayInterval: string;
15
+ };
16
+ metadata: Record<string, any>;
17
+ } | null;
18
+ export declare function parseSubscription(session: TCheckoutSessionExpanded): {
19
+ mode: "payment" | "subscription" | "setup";
20
+ showStake: boolean;
21
+ confirmMessage: string;
22
+ } | null;
23
+ export declare function parsePageInfo(session: TCheckoutSessionExpanded): {
24
+ formPurposeDescription: any;
25
+ showProductFeatures: boolean;
26
+ };
@@ -0,0 +1,50 @@
1
+ export function parseProduct(session) {
2
+ const items = session.line_items || [];
3
+ if (items.length === 0) return null;
4
+ const firstItem = items[0];
5
+ const price = firstItem.upsell_price || firstItem.price;
6
+ const product = price?.product;
7
+ if (!product) return null;
8
+ const recurring = price?.recurring;
9
+ const mode = session.mode === "subscription" ? "subscription" : "payment";
10
+ const intervalMap = {
11
+ day: "daily",
12
+ week: "weekly",
13
+ month: "monthly",
14
+ year: "yearly"
15
+ };
16
+ return {
17
+ name: product.name || "",
18
+ description: product.description || "",
19
+ images: product.images || [],
20
+ features: (product.features || []).map((f) => ({
21
+ name: f.name || "",
22
+ icon: f.icon
23
+ })),
24
+ billing: {
25
+ mode,
26
+ interval: recurring?.interval || null,
27
+ intervalCount: recurring?.interval_count || 1,
28
+ displayInterval: recurring ? intervalMap[recurring.interval] || recurring.interval : ""
29
+ },
30
+ metadata: product.metadata || {}
31
+ };
32
+ }
33
+ export function parseSubscription(session) {
34
+ const subData = session.subscription_data;
35
+ if (!subData && session.mode === "payment") return null;
36
+ return {
37
+ mode: session.mode || "payment",
38
+ showStake: !subData?.no_stake,
39
+ confirmMessage: ""
40
+ };
41
+ }
42
+ export function parsePageInfo(session) {
43
+ const metadata = session.metadata;
44
+ const pageInfo = metadata?.page_info;
45
+ const showRaw = metadata?.show_product_features ?? pageInfo?.show_product_features;
46
+ return {
47
+ formPurposeDescription: pageInfo?.form_purpose_description,
48
+ showProductFeatures: `${showRaw}` === "true"
49
+ };
50
+ }