@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,212 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getFieldValidation = getFieldValidation;
7
+ exports.postalCodePatterns = void 0;
8
+ exports.validateEmail = validateEmail;
9
+ exports.validateForm = validateForm;
10
+ exports.validatePhone = validatePhone;
11
+ exports.validatePostalCode = validatePostalCode;
12
+ var _isEmail = _interopRequireDefault(require("validator/lib/isEmail"));
13
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
14
+ const fourDigit = /^\d{4}$/;
15
+ const fiveDigit = /^\d{5}$/;
16
+ const sixDigit = /^\d{6}$/;
17
+ const postalCodePatterns = exports.postalCodePatterns = {
18
+ GB: /^GIR[ ]?0AA|((AB|AL|B|BA|BB|BD|BH|BL|BN|BR|BS|BT|CA|CB|CF|CH|CM|CO|CR|CT|CV|CW|DA|DD|DE|DG|DH|DL|DN|DT|DY|E|EC|EH|EN|EX|FK|FY|G|GL|GY|GU|HA|HD|HG|HP|HR|HS|HU|HX|IG|IM|IP|IV|JE|KA|KT|KW|KY|L|LA|LD|LE|LL|LN|LS|LU|M|ME|MK|ML|N|NE|NG|NN|NP|NR|NW|OL|OX|PA|PE|PH|PL|PO|PR|RG|RH|RM|S|SA|SE|SG|SK|SL|SM|SN|SO|SP|SR|SS|ST|SW|SY|TA|TD|TF|TN|TQ|TR|TS|TW|UB|W|WA|WC|WD|WF|WN|WR|WS|WV|YO|ZE)(\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}))|BFPO[ ]?\d{1,4}$/i,
19
+ JE: /^JE\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}$/i,
20
+ GG: /^GY\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}$/i,
21
+ IM: /^IM\d[\dA-Z]?[ ]?\d[ABD-HJLN-UW-Z]{2}$/i,
22
+ US: /^\d{5}([ -]\d{4})?$/,
23
+ CA: /^[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJ-NPRSTV-Z][ ]?\d[ABCEGHJ-NPRSTV-Z]\d$/i,
24
+ DE: fiveDigit,
25
+ JP: /^\d{3}-?\d{4}$/,
26
+ FR: /^\d{2}[ ]?\d{3}$/,
27
+ AU: fourDigit,
28
+ IT: fiveDigit,
29
+ CH: fourDigit,
30
+ AT: fourDigit,
31
+ ES: fiveDigit,
32
+ NL: /^\d{4}[ ]?[A-Z]{2}$/i,
33
+ BE: fourDigit,
34
+ DK: fourDigit,
35
+ SE: /^\d{3}[ ]?\d{2}$/,
36
+ NO: fourDigit,
37
+ BR: /^\d{5}-?\d{3}$/,
38
+ PT: /^\d{4}(-\d{3})?$/,
39
+ FI: fiveDigit,
40
+ KR: fiveDigit,
41
+ CN: sixDigit,
42
+ TW: /^\d{3}(\d{2})?$/,
43
+ SG: sixDigit,
44
+ IN: /^[1-9]\d{5}$/,
45
+ RU: sixDigit,
46
+ PL: /^\d{2}-\d{3}$/,
47
+ CZ: /^\d{3}[ ]?\d{2}$/,
48
+ HU: fourDigit,
49
+ RO: sixDigit,
50
+ BG: fourDigit,
51
+ HR: fiveDigit,
52
+ SK: /^\d{3}[ ]?\d{2}$/,
53
+ SI: fourDigit,
54
+ TR: fiveDigit,
55
+ IL: fiveDigit,
56
+ ZA: fourDigit,
57
+ MX: fiveDigit,
58
+ AR: /^([A-HJ-NP-Z])?\d{4}([A-Z]{3})?$/i,
59
+ CL: /^\d{7}$/,
60
+ CO: sixDigit,
61
+ NZ: fourDigit,
62
+ MY: fiveDigit,
63
+ TH: fiveDigit,
64
+ PH: fourDigit,
65
+ ID: fiveDigit,
66
+ VN: sixDigit
67
+ };
68
+ function validatePostalCode(postalCode, country) {
69
+ if (!postalCode) return true;
70
+ try {
71
+ const countryUpper = country?.toUpperCase();
72
+ if (!countryUpper) return false;
73
+ const pattern = postalCodePatterns[countryUpper];
74
+ return !pattern || pattern.test(postalCode);
75
+ } catch {
76
+ return false;
77
+ }
78
+ }
79
+ function getFieldValidation(fieldName, validations, locale = "en") {
80
+ if (!validations || !validations[fieldName]) return {};
81
+ const fieldValidation = validations[fieldName];
82
+ const rules = {};
83
+ if (fieldValidation.pattern) {
84
+ rules.pattern = {
85
+ value: new RegExp(fieldValidation.pattern),
86
+ message: fieldValidation.pattern_message?.[locale] || "Invalid format"
87
+ };
88
+ }
89
+ return rules;
90
+ }
91
+ let phoneUtil = null;
92
+ async function getPhoneUtil() {
93
+ if (!phoneUtil) {
94
+ const result = await Promise.resolve().then(() => require("google-libphonenumber"));
95
+ const PhoneNumberUtil = (result.default || result)?.PhoneNumberUtil;
96
+ if (!PhoneNumberUtil) {
97
+ throw new Error("PhoneNumberUtil not found");
98
+ }
99
+ phoneUtil = PhoneNumberUtil.getInstance();
100
+ }
101
+ return phoneUtil;
102
+ }
103
+ const PHONE_REGEX_FALLBACK = /^[+]?[(]?[0-9]{3}[)]?[-\s.]?[0-9]{3}[-\s.]?[0-9]{4,6}$/im;
104
+ async function validatePhone(phone) {
105
+ if (!phone || phone.trim() === "") return false;
106
+ try {
107
+ const util = await getPhoneUtil();
108
+ const parsed = util.parseAndKeepRawInput(phone);
109
+ return util.isValidNumber(parsed);
110
+ } catch {
111
+ return PHONE_REGEX_FALLBACK.test(phone);
112
+ }
113
+ }
114
+ function validateEmail(email) {
115
+ if (!email || email.trim() === "") return false;
116
+ return (0, _isEmail.default)(email);
117
+ }
118
+ async function validateForm(values, options) {
119
+ const errors = {};
120
+ const {
121
+ phoneEnabled,
122
+ addressMode,
123
+ fieldValidation,
124
+ locale = "en"
125
+ } = options;
126
+ if (!values) {
127
+ return {
128
+ valid: false,
129
+ errors: {
130
+ customer_name: "Required"
131
+ }
132
+ };
133
+ }
134
+ const customerName = values.customer_name;
135
+ if (!customerName || customerName.trim() === "") {
136
+ errors.customer_name = "Required";
137
+ } else {
138
+ const nameRule = getFieldValidation("customer_name", fieldValidation, locale);
139
+ if (nameRule.pattern && !nameRule.pattern.value.test(customerName)) {
140
+ errors.customer_name = nameRule.pattern.message;
141
+ }
142
+ }
143
+ const customerEmail = values.customer_email;
144
+ if (!customerEmail || !validateEmail(customerEmail)) {
145
+ errors.customer_email = !customerEmail ? "Required" : "Invalid email";
146
+ } else {
147
+ const emailRule = getFieldValidation("customer_email", fieldValidation, locale);
148
+ if (emailRule.pattern && !emailRule.pattern.value.test(customerEmail)) {
149
+ errors.customer_email = emailRule.pattern.message;
150
+ }
151
+ }
152
+ if (phoneEnabled) {
153
+ const customerPhone = values.customer_phone;
154
+ if (!customerPhone || customerPhone.trim() === "") {
155
+ errors.customer_phone = "Required";
156
+ } else if (!(await validatePhone(customerPhone))) {
157
+ errors.customer_phone = "Invalid phone number";
158
+ } else {
159
+ const phoneRule = getFieldValidation("customer_phone", fieldValidation, locale);
160
+ if (phoneRule.pattern && !phoneRule.pattern.value.test(customerPhone)) {
161
+ errors.customer_phone = phoneRule.pattern.message;
162
+ }
163
+ }
164
+ }
165
+ const billingAddress = values.billing_address;
166
+ const postalCode = billingAddress?.postal_code;
167
+ const country = billingAddress?.country;
168
+ const state = billingAddress?.state;
169
+ const line1 = billingAddress?.line1;
170
+ const city = billingAddress?.city;
171
+ if (!postalCode) {
172
+ errors["billing_address.postal_code"] = "Required";
173
+ } else if (!validatePostalCode(postalCode, country)) {
174
+ errors["billing_address.postal_code"] = "Invalid postal code";
175
+ } else {
176
+ const postalRule = getFieldValidation("billing_address.postal_code", fieldValidation, locale);
177
+ if (postalRule.pattern && !postalRule.pattern.value.test(postalCode)) {
178
+ errors["billing_address.postal_code"] = postalRule.pattern.message;
179
+ }
180
+ }
181
+ if (!state) {
182
+ errors["billing_address.state"] = "Required";
183
+ } else {
184
+ const stateRule = getFieldValidation("billing_address.state", fieldValidation, locale);
185
+ if (stateRule.pattern && !stateRule.pattern.value.test(state)) {
186
+ errors["billing_address.state"] = stateRule.pattern.message;
187
+ }
188
+ }
189
+ if (addressMode === "required") {
190
+ if (!country) errors["billing_address.country"] = "Required";
191
+ if (!line1) {
192
+ errors["billing_address.line1"] = "Required";
193
+ } else {
194
+ const line1Rule = getFieldValidation("billing_address.line1", fieldValidation, locale);
195
+ if (line1Rule.pattern && !line1Rule.pattern.value.test(line1)) {
196
+ errors["billing_address.line1"] = line1Rule.pattern.message;
197
+ }
198
+ }
199
+ if (!city) {
200
+ errors["billing_address.city"] = "Required";
201
+ } else {
202
+ const cityRule = getFieldValidation("billing_address.city", fieldValidation, locale);
203
+ if (cityRule.pattern && !cityRule.pattern.value.test(city)) {
204
+ errors["billing_address.city"] = cityRule.pattern.message;
205
+ }
206
+ }
207
+ }
208
+ return {
209
+ valid: Object.keys(errors).length === 0,
210
+ errors
211
+ };
212
+ }
@@ -0,0 +1,42 @@
1
+ import type { TCheckoutSessionExpanded, TPrice, TPriceExpanded } from '@blocklet/payment-types';
2
+ export interface CheckoutSessionUser {
3
+ did?: string;
4
+ sourceAppPid?: string;
5
+ connectedAccounts?: Array<{
6
+ provider: string;
7
+ }>;
8
+ extraConfigs?: {
9
+ connectedAccounts?: Array<{
10
+ provider: string;
11
+ }>;
12
+ };
13
+ }
14
+ export type CheckoutSessionRuntime = TCheckoutSessionExpanded & {
15
+ user?: CheckoutSessionUser;
16
+ quote_locked_at?: number;
17
+ };
18
+ export type PriceWithCrossSell = TPriceExpanded & {
19
+ cross_sell?: {
20
+ cross_sells_to?: TPrice;
21
+ cross_sells_to_id?: string;
22
+ };
23
+ };
24
+ export interface UpsellExpanded {
25
+ upsells_to_id?: string;
26
+ upsells_to?: TPrice;
27
+ }
28
+ /**
29
+ * Get human-readable message from unknown error.
30
+ * Works for standard Error, AxiosError, and string errors.
31
+ */
32
+ export declare function getErrorMessage(err: unknown): string;
33
+ /**
34
+ * Extract API error details from an unknown axios-like error.
35
+ * Returns message, error code, and raw response data.
36
+ */
37
+ export declare function getAxiosErrorDetails(err: unknown): {
38
+ message: string;
39
+ code: string | undefined;
40
+ data: Record<string, unknown> | undefined;
41
+ isCancelled: boolean;
42
+ };
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getAxiosErrorDetails = getAxiosErrorDetails;
7
+ exports.getErrorMessage = getErrorMessage;
8
+ function getErrorMessage(err) {
9
+ const axErr = err;
10
+ if (axErr?.response?.data?.error) return axErr.response.data.error;
11
+ if (err instanceof Error) return err.message;
12
+ if (typeof err === "string") return err;
13
+ return axErr?.message || "Unknown error";
14
+ }
15
+ function getAxiosErrorDetails(err) {
16
+ const axErr = err;
17
+ const data = axErr?.response?.data;
18
+ return {
19
+ message: data?.error || axErr?.message || "Unknown error",
20
+ code: data?.code,
21
+ data,
22
+ isCancelled: axErr?.name === "CanceledError" || axErr?.name === "AbortError"
23
+ };
24
+ }
@@ -0,0 +1,18 @@
1
+ declare module 'google-libphonenumber' {
2
+ export class PhoneNumberUtil {
3
+ static getInstance(): PhoneNumberUtil;
4
+ parseAndKeepRawInput(numberToParse: string, defaultRegion?: string): any;
5
+ isValidNumber(number: any): boolean;
6
+ }
7
+ }
8
+
9
+ declare module '@arcblock/ws' {
10
+ export class WsClient {
11
+ constructor(url: string, options?: Record<string, any>);
12
+ connect(): void;
13
+ disconnect(): void;
14
+ isConnected(): boolean;
15
+ subscribe(channel: string): any;
16
+ unsubscribe(channel: string): void;
17
+ }
18
+ }
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@blocklet/payment-react-headless",
3
+ "version": "1.26.0",
4
+ "description": "Headless React hooks for payment-kit checkout",
5
+ "keywords": [
6
+ "react",
7
+ "payment",
8
+ "headless",
9
+ "hooks"
10
+ ],
11
+ "author": "ArcBlock",
12
+ "homepage": "https://github.com/blocklet/payment-kit",
13
+ "license": "Apache-2.0",
14
+ "main": "lib/index.js",
15
+ "module": "es/index.js",
16
+ "types": "lib/index.d.ts",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/blocklet/payment-kit.git"
20
+ },
21
+ "scripts": {
22
+ "lint": "tsc --noEmit",
23
+ "clean": "rm -rf es lib",
24
+ "build": "pnpm run clean && tsc --noEmit && unbuild",
25
+ "test": "node tools/jest.js --passWithNoTests",
26
+ "prepublish": "pnpm run build"
27
+ },
28
+ "exports": {
29
+ ".": {
30
+ "types": "./lib/index.d.ts",
31
+ "import": "./es/index.js",
32
+ "require": "./lib/index.js"
33
+ }
34
+ },
35
+ "dependencies": {
36
+ "@arcblock/ws": "^1.28.5",
37
+ "@blocklet/js-sdk": "workspace:*",
38
+ "@blocklet/payment-types": "1.26.0",
39
+ "@ocap/util": "^1.28.5",
40
+ "ahooks": "^3.8.5",
41
+ "google-libphonenumber": "^3.2.42",
42
+ "lodash": "^4.17.21",
43
+ "p-wait-for": "^3.2.0",
44
+ "validator": "^13.15.15"
45
+ },
46
+ "peerDependencies": {
47
+ "react": ">=17.0.0"
48
+ },
49
+ "devDependencies": {
50
+ "@blocklet/payment-types": "workspace:*",
51
+ "@types/lodash": "^4.17.16",
52
+ "@types/react": "^18.3.23",
53
+ "@types/validator": "^13.12.2",
54
+ "jest": "^29.7.0",
55
+ "react": "^19.1.0",
56
+ "ts-jest": "^29.4.0",
57
+ "typescript": "5.5.4",
58
+ "unbuild": "^2.0.0"
59
+ },
60
+ "publishConfig": {
61
+ "access": "public"
62
+ },
63
+ "gitHead": "9585ec8bc077fc5f8a8c5946d05436b10576e145"
64
+ }
@@ -0,0 +1,269 @@
1
+ import React, { useMemo, useState, useRef, useEffect } from 'react';
2
+ import { useMemoizedFn } from 'ahooks';
3
+ import type { TLineItemExpanded } from '@blocklet/payment-types';
4
+
5
+ import { useCheckoutSession } from '../hooks/useCheckoutSession';
6
+ import { usePaymentMethod as usePaymentMethodHook } from '../hooks/usePaymentMethod';
7
+ import { useCustomerForm as useCustomerFormHook } from '../hooks/useCustomerForm';
8
+ import { useSubmit as useSubmitHook } from '../hooks/useSubmit';
9
+ import { checkHasDynamicPricing, fetchExchangeRate, BASE_INTERVAL } from '../core/exchangeRate';
10
+ import { recalculatePromotionIfNeeded } from '../core/lineItems';
11
+
12
+ import { SessionContext, type SessionContextValue } from './SessionContext';
13
+ import { PaymentMethodContext, type PaymentMethodContextValue } from './PaymentMethodContext';
14
+ import { ExchangeRateContext, type ExchangeRateContextValue } from './ExchangeRateContext';
15
+ import { CustomerFormContext } from './CustomerFormContext';
16
+ import { SubmitReactContext } from './SubmitContext';
17
+
18
+ export interface CheckoutProviderProps {
19
+ sessionId: string;
20
+ children: React.ReactNode;
21
+ }
22
+
23
+ export function CheckoutProvider({ sessionId, children }: CheckoutProviderProps) {
24
+ // 1. Session layer
25
+ const {
26
+ isLoading,
27
+ error,
28
+ errorCode,
29
+ refresh,
30
+ setSessionData,
31
+ sessionData,
32
+ resolvedSessionId,
33
+ vendorCount,
34
+ product,
35
+ subscription,
36
+ pageInfo,
37
+ } = useCheckoutSession(sessionId);
38
+
39
+ const session = sessionData?.checkoutSession;
40
+ const effectiveSessionId = resolvedSessionId || sessionId;
41
+ const items = (session?.line_items || []) as TLineItemExpanded[];
42
+ const isDonation = session?.submit_type === 'donate';
43
+
44
+ const sessionValue = useMemo<SessionContextValue>(
45
+ () => ({
46
+ sessionData,
47
+ sessionId,
48
+ effectiveSessionId,
49
+ isLoading,
50
+ error,
51
+ errorCode,
52
+ refresh,
53
+ items,
54
+ session: session || null,
55
+ isDonation,
56
+ vendorCount,
57
+ product,
58
+ subscription,
59
+ pageInfo,
60
+ }),
61
+ [
62
+ sessionData,
63
+ sessionId,
64
+ effectiveSessionId,
65
+ isLoading,
66
+ error,
67
+ errorCode,
68
+ refresh,
69
+ items,
70
+ session,
71
+ isDonation,
72
+ vendorCount,
73
+ product,
74
+ subscription,
75
+ pageInfo,
76
+ ]
77
+ );
78
+
79
+ // 2. Payment method layer
80
+ const paymentMethodHook = usePaymentMethodHook(sessionData, effectiveSessionId, refresh);
81
+
82
+ // Recalculate promotion when currency changes
83
+ const prevCurrencyRef = useRef<string | null>(null);
84
+ useEffect(() => {
85
+ const currId = paymentMethodHook.currency?.id || null;
86
+ if (!currId || !session || session.status === 'complete') return;
87
+ if (prevCurrencyRef.current === null || currId !== prevCurrencyRef.current) {
88
+ prevCurrencyRef.current = currId;
89
+ recalculatePromotionIfNeeded(session, effectiveSessionId, currId).then(() => refresh(true));
90
+ }
91
+ }, [paymentMethodHook.currency?.id, session?.id]); // eslint-disable-line react-hooks/exhaustive-deps
92
+
93
+ const paymentMethodValue = useMemo<PaymentMethodContextValue>(
94
+ () => ({
95
+ current: paymentMethodHook.current,
96
+ currency: paymentMethodHook.currency,
97
+ available: paymentMethodHook.available,
98
+ currencies: paymentMethodHook.currencies,
99
+ isStripe: paymentMethodHook.isStripe,
100
+ isCrypto: paymentMethodHook.isCrypto,
101
+ isCredit: paymentMethodHook.isCredit,
102
+ switching: paymentMethodHook.switching,
103
+ setType: paymentMethodHook.setType,
104
+ types: paymentMethodHook.types,
105
+ setCurrency: paymentMethodHook.setCurrency,
106
+ stripe: paymentMethodHook.stripe,
107
+ }),
108
+ [
109
+ paymentMethodHook.current,
110
+ paymentMethodHook.currency,
111
+ paymentMethodHook.available,
112
+ paymentMethodHook.currencies,
113
+ paymentMethodHook.isStripe,
114
+ paymentMethodHook.isCrypto,
115
+ paymentMethodHook.isCredit,
116
+ paymentMethodHook.switching,
117
+ paymentMethodHook.setType,
118
+ paymentMethodHook.types,
119
+ paymentMethodHook.setCurrency,
120
+ paymentMethodHook.stripe,
121
+ ]
122
+ );
123
+
124
+ // 3. Exchange rate layer
125
+ const [exchangeRate, setExchangeRate] = useState<string | null>(null);
126
+ const [rateProvider, setRateProvider] = useState<string | null>(null);
127
+ const [rateProviderDisplay, setRateProviderDisplay] = useState<string | null>(null);
128
+ const [rateFetchedAt, setRateFetchedAt] = useState<number | null>(null);
129
+ const [rateStatus, setRateStatus] = useState<'loading' | 'available' | 'unavailable'>('loading');
130
+ const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
131
+ const fetchingRef = useRef(false);
132
+ const mountedRef = useRef(true);
133
+
134
+ const hasDynamicPricing = useMemo(() => checkHasDynamicPricing(items), [items]);
135
+
136
+ const fetchRate = useMemoizedFn(async () => {
137
+ if (!effectiveSessionId || !hasDynamicPricing || paymentMethodHook.isStripe) {
138
+ setRateStatus(hasDynamicPricing ? 'unavailable' : 'available');
139
+ if (paymentMethodHook.isStripe) {
140
+ setExchangeRate(null);
141
+ setRateProvider(null);
142
+ setRateProviderDisplay(null);
143
+ setRateFetchedAt(null);
144
+ }
145
+ return;
146
+ }
147
+
148
+ // Guard against concurrent fetches (setInterval keeps firing regardless)
149
+ if (fetchingRef.current) return;
150
+ fetchingRef.current = true;
151
+
152
+ try {
153
+ setRateStatus('loading');
154
+ const result = await fetchExchangeRate(effectiveSessionId, paymentMethodHook.currency?.id);
155
+
156
+ if (!mountedRef.current) return;
157
+
158
+ if (result.rate) {
159
+ setExchangeRate(result.rate);
160
+ setRateProvider(result.provider);
161
+ setRateProviderDisplay(result.providerDisplay);
162
+ setRateFetchedAt(result.fetchedAt);
163
+ setRateStatus('available');
164
+ } else {
165
+ setRateStatus('unavailable');
166
+ }
167
+ } catch {
168
+ if (!mountedRef.current) return;
169
+ setRateStatus('unavailable');
170
+ } finally {
171
+ fetchingRef.current = false;
172
+ }
173
+ });
174
+
175
+ useEffect(() => {
176
+ mountedRef.current = true;
177
+
178
+ if (!hasDynamicPricing || paymentMethodHook.isStripe || !effectiveSessionId || session?.status === 'complete') {
179
+ // Clear stale rate when switching to Stripe
180
+ if (paymentMethodHook.isStripe) {
181
+ setExchangeRate(null);
182
+ setRateProvider(null);
183
+ setRateProviderDisplay(null);
184
+ setRateFetchedAt(null);
185
+ }
186
+ // When session is complete and we already have a rate, keep it as 'available'
187
+ // (the rate used at payment time is still valid for display).
188
+ // Otherwise set appropriate status:
189
+ // - non-dynamic → 'available' (no rate needed)
190
+ // - dynamic but Stripe or no session → 'unavailable'
191
+ if (session?.status === 'complete') {
192
+ // Completed session: rate is irrelevant, suppress unavailable warnings
193
+ setRateStatus('available');
194
+ } else {
195
+ setRateStatus(hasDynamicPricing ? 'unavailable' : 'available');
196
+ }
197
+ return undefined;
198
+ }
199
+
200
+ // Initial fetch
201
+ fetchRate();
202
+
203
+ // Use setInterval (like V1) — more robust than chained setTimeout.
204
+ // setInterval keeps firing regardless of whether the previous fetch completed;
205
+ // the fetchingRef guard prevents concurrent fetches.
206
+ intervalRef.current = setInterval(fetchRate, BASE_INTERVAL);
207
+
208
+ const handleVisibility = () => {
209
+ if (!document.hidden) {
210
+ // Fetch immediately when tab becomes visible
211
+ fetchRate();
212
+ }
213
+ };
214
+
215
+ document.addEventListener('visibilitychange', handleVisibility);
216
+
217
+ return () => {
218
+ mountedRef.current = false;
219
+ if (intervalRef.current) clearInterval(intervalRef.current);
220
+ document.removeEventListener('visibilitychange', handleVisibility);
221
+ };
222
+ }, [hasDynamicPricing, paymentMethodHook.isStripe, effectiveSessionId, paymentMethodHook.currency?.id, session?.status]); // eslint-disable-line react-hooks/exhaustive-deps
223
+
224
+ const exchangeRateValue = useMemo<ExchangeRateContextValue>(
225
+ () => ({
226
+ rate: exchangeRate,
227
+ provider: rateProvider,
228
+ providerDisplay: rateProviderDisplay,
229
+ fetchedAt: rateFetchedAt,
230
+ status: rateStatus,
231
+ hasDynamicPricing,
232
+ refresh: fetchRate,
233
+ }),
234
+ [exchangeRate, rateProvider, rateProviderDisplay, rateFetchedAt, rateStatus, hasDynamicPricing, fetchRate]
235
+ );
236
+
237
+ // 4. Customer form layer
238
+ const customerForm = useCustomerFormHook(
239
+ sessionData,
240
+ paymentMethodHook.currency?.id || null,
241
+ paymentMethodHook.current?.id || null
242
+ );
243
+
244
+ // 5. Submit layer (depends on customer form)
245
+ const submit = useSubmitHook(
246
+ sessionData,
247
+ effectiveSessionId,
248
+ paymentMethodHook.currency?.id || null,
249
+ paymentMethodHook.isStripe,
250
+ paymentMethodHook.isCredit,
251
+ isDonation,
252
+ customerForm.values,
253
+ customerForm.validate,
254
+ refresh,
255
+ setSessionData
256
+ );
257
+
258
+ return (
259
+ <SessionContext.Provider value={sessionValue}>
260
+ <PaymentMethodContext.Provider value={paymentMethodValue}>
261
+ <ExchangeRateContext.Provider value={exchangeRateValue}>
262
+ <CustomerFormContext.Provider value={customerForm}>
263
+ <SubmitReactContext.Provider value={submit}>{children}</SubmitReactContext.Provider>
264
+ </CustomerFormContext.Provider>
265
+ </ExchangeRateContext.Provider>
266
+ </PaymentMethodContext.Provider>
267
+ </SessionContext.Provider>
268
+ );
269
+ }
@@ -0,0 +1,14 @@
1
+ import { createContext, useContext } from 'react';
2
+ import type { UseCustomerFormReturn } from '../hooks/useCustomerForm';
3
+
4
+ export type CustomerFormContextValue = UseCustomerFormReturn;
5
+
6
+ export const CustomerFormContext = createContext<CustomerFormContextValue | null>(null);
7
+
8
+ export function useCustomerFormContext(): CustomerFormContextValue {
9
+ const ctx = useContext(CustomerFormContext);
10
+ if (!ctx) {
11
+ throw new Error('useCustomerFormContext must be used within <CheckoutProvider>');
12
+ }
13
+ return ctx;
14
+ }
@@ -0,0 +1,21 @@
1
+ import { createContext, useContext } from 'react';
2
+
3
+ export interface ExchangeRateContextValue {
4
+ rate: string | null;
5
+ provider: string | null;
6
+ providerDisplay: string | null;
7
+ fetchedAt: number | null;
8
+ status: 'loading' | 'available' | 'unavailable';
9
+ hasDynamicPricing: boolean;
10
+ refresh: () => Promise<void>;
11
+ }
12
+
13
+ export const ExchangeRateContext = createContext<ExchangeRateContextValue | null>(null);
14
+
15
+ export function useExchangeRateContext(): ExchangeRateContextValue {
16
+ const ctx = useContext(ExchangeRateContext);
17
+ if (!ctx) {
18
+ throw new Error('useExchangeRateContext must be used within <CheckoutProvider>');
19
+ }
20
+ return ctx;
21
+ }