@whop/elements-react-native 0.1.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 (339) hide show
  1. package/README.md +179 -0
  2. package/android/build.gradle +64 -0
  3. package/android/consumer-rules.pro +3 -0
  4. package/android/gradle.properties +5 -0
  5. package/android/src/main/java/com/whop/elements/WhopAddressSuggestionsModule.kt +102 -0
  6. package/android/src/main/java/com/whop/elements/WhopAuthSessionModule.kt +126 -0
  7. package/android/src/main/java/com/whop/elements/WhopElementsPackage.kt +39 -0
  8. package/android/src/main/java/com/whop/elements/WhopPayButtonManager.kt +74 -0
  9. package/android/src/main/java/com/whop/elements/WhopPayButtonPressEvent.kt +17 -0
  10. package/android/src/main/java/com/whop/elements/WhopPayButtonView.kt +178 -0
  11. package/android/src/main/java/com/whop/elements/WhopPlatformPayModule.kt +212 -0
  12. package/assets/whop-wordmark-dark.png +0 -0
  13. package/assets/whop-wordmark-dark@2x.png +0 -0
  14. package/assets/whop-wordmark-dark@3x.png +0 -0
  15. package/assets/whop-wordmark-light.png +0 -0
  16. package/assets/whop-wordmark-light@2x.png +0 -0
  17. package/assets/whop-wordmark-light@3x.png +0 -0
  18. package/ios/Package.swift +59 -0
  19. package/ios/Sources/ElementsReactNative/WhopAddressSuggestionsImpl.swift +189 -0
  20. package/ios/Sources/ElementsReactNative/WhopAuthSessionImpl.swift +106 -0
  21. package/ios/Sources/ElementsReactNative/WhopPayButtonImpl.swift +89 -0
  22. package/ios/Sources/ElementsReactNative/WhopPlatformPayImpl.swift +181 -0
  23. package/ios/generated/WhopAddressSuggestionsModule.h +11 -0
  24. package/ios/generated/WhopAddressSuggestionsModule.mm +46 -0
  25. package/ios/generated/WhopAuthSessionModule.h +11 -0
  26. package/ios/generated/WhopAuthSessionModule.mm +45 -0
  27. package/ios/generated/WhopElementsShims.h +35 -0
  28. package/ios/generated/WhopPayButtonComponentView.h +12 -0
  29. package/ios/generated/WhopPayButtonComponentView.mm +82 -0
  30. package/ios/generated/WhopPlatformPayModule.h +11 -0
  31. package/ios/generated/WhopPlatformPayModule.mm +51 -0
  32. package/lib/module/api/client.js +139 -0
  33. package/lib/module/api/client.js.map +1 -0
  34. package/lib/module/api/client.test.js +137 -0
  35. package/lib/module/api/client.test.js.map +1 -0
  36. package/lib/module/api/types.js +2 -0
  37. package/lib/module/api/types.js.map +1 -0
  38. package/lib/module/controller/actions.js +148 -0
  39. package/lib/module/controller/actions.js.map +1 -0
  40. package/lib/module/controller/actions.test.js +102 -0
  41. package/lib/module/controller/actions.test.js.map +1 -0
  42. package/lib/module/controller/store.js +158 -0
  43. package/lib/module/controller/store.js.map +1 -0
  44. package/lib/module/core/address/country-data.js +1370 -0
  45. package/lib/module/core/address/country-data.js.map +1 -0
  46. package/lib/module/core/address/country-name.js +38 -0
  47. package/lib/module/core/address/country-name.js.map +1 -0
  48. package/lib/module/core/address/country-names.generated.js +249 -0
  49. package/lib/module/core/address/country-names.generated.js.map +1 -0
  50. package/lib/module/core/address/engine.js +245 -0
  51. package/lib/module/core/address/engine.js.map +1 -0
  52. package/lib/module/core/address/engine.test.js +216 -0
  53. package/lib/module/core/address/engine.test.js.map +1 -0
  54. package/lib/module/core/address/form-state.js +409 -0
  55. package/lib/module/core/address/form-state.js.map +1 -0
  56. package/lib/module/core/address/form-state.test.js +516 -0
  57. package/lib/module/core/address/form-state.test.js.map +1 -0
  58. package/lib/module/core/address/places.js +36 -0
  59. package/lib/module/core/address/places.js.map +1 -0
  60. package/lib/module/core/address/required-coverage.test.js +57 -0
  61. package/lib/module/core/address/required-coverage.test.js.map +1 -0
  62. package/lib/module/core/address/suggest.js +102 -0
  63. package/lib/module/core/address/suggest.js.map +1 -0
  64. package/lib/module/core/address/suggest.test.js +156 -0
  65. package/lib/module/core/address/suggest.test.js.map +1 -0
  66. package/lib/module/core/address/use-suggestions.js +33 -0
  67. package/lib/module/core/address/use-suggestions.js.map +1 -0
  68. package/lib/module/core/payments/address-change.js +91 -0
  69. package/lib/module/core/payments/address-change.js.map +1 -0
  70. package/lib/module/core/payments/billing-merge.js +62 -0
  71. package/lib/module/core/payments/billing-merge.js.map +1 -0
  72. package/lib/module/core/payments/browser-info.js +42 -0
  73. package/lib/module/core/payments/browser-info.js.map +1 -0
  74. package/lib/module/core/payments/bt.js +151 -0
  75. package/lib/module/core/payments/bt.js.map +1 -0
  76. package/lib/module/core/payments/bt.test.js +23 -0
  77. package/lib/module/core/payments/bt.test.js.map +1 -0
  78. package/lib/module/core/payments/currency.js +37 -0
  79. package/lib/module/core/payments/currency.js.map +1 -0
  80. package/lib/module/core/payments/detail.js +159 -0
  81. package/lib/module/core/payments/detail.js.map +1 -0
  82. package/lib/module/core/payments/detail.test.js +225 -0
  83. package/lib/module/core/payments/detail.test.js.map +1 -0
  84. package/lib/module/core/payments/errors.js +115 -0
  85. package/lib/module/core/payments/errors.js.map +1 -0
  86. package/lib/module/core/payments/mint.js +89 -0
  87. package/lib/module/core/payments/mint.js.map +1 -0
  88. package/lib/module/core/payments/plan.js +131 -0
  89. package/lib/module/core/payments/plan.js.map +1 -0
  90. package/lib/module/core/payments/refusals.js +17 -0
  91. package/lib/module/core/payments/refusals.js.map +1 -0
  92. package/lib/module/core/payments/secure-fields.js +87 -0
  93. package/lib/module/core/payments/secure-fields.js.map +1 -0
  94. package/lib/module/core/payments/tax-id-catalog.js +616 -0
  95. package/lib/module/core/payments/tax-id-catalog.js.map +1 -0
  96. package/lib/module/core/payments/wallet-contact.js +42 -0
  97. package/lib/module/core/payments/wallet-contact.js.map +1 -0
  98. package/lib/module/core/payments/wallet-contact.test.js +77 -0
  99. package/lib/module/core/payments/wallet-contact.test.js.map +1 -0
  100. package/lib/module/elements/address.js +467 -0
  101. package/lib/module/elements/address.js.map +1 -0
  102. package/lib/module/elements/branding.js +114 -0
  103. package/lib/module/elements/branding.js.map +1 -0
  104. package/lib/module/elements/card-fields/context.js +254 -0
  105. package/lib/module/elements/card-fields/context.js.map +1 -0
  106. package/lib/module/elements/card-fields/fields.js +309 -0
  107. package/lib/module/elements/card-fields/fields.js.map +1 -0
  108. package/lib/module/elements/card-fields/index.js +30 -0
  109. package/lib/module/elements/card-fields/index.js.map +1 -0
  110. package/lib/module/elements/card.js +107 -0
  111. package/lib/module/elements/card.js.map +1 -0
  112. package/lib/module/elements/email.js +84 -0
  113. package/lib/module/elements/email.js.map +1 -0
  114. package/lib/module/elements/payment/declared-fields.js +186 -0
  115. package/lib/module/elements/payment/declared-fields.js.map +1 -0
  116. package/lib/module/elements/payment/index.js +448 -0
  117. package/lib/module/elements/payment/index.js.map +1 -0
  118. package/lib/module/elements/payment/wallet.js +132 -0
  119. package/lib/module/elements/payment/wallet.js.map +1 -0
  120. package/lib/module/elements/tax-id.js +100 -0
  121. package/lib/module/elements/tax-id.js.map +1 -0
  122. package/lib/module/i18n/catalogs.js +103 -0
  123. package/lib/module/i18n/catalogs.js.map +1 -0
  124. package/lib/module/i18n/index.js +45 -0
  125. package/lib/module/i18n/index.js.map +1 -0
  126. package/lib/module/index.js +27 -0
  127. package/lib/module/index.js.map +1 -0
  128. package/lib/module/namespace.js +174 -0
  129. package/lib/module/namespace.js.map +1 -0
  130. package/lib/module/native/index.js +128 -0
  131. package/lib/module/native/index.js.map +1 -0
  132. package/lib/module/package.json +1 -0
  133. package/lib/module/provider.js +56 -0
  134. package/lib/module/provider.js.map +1 -0
  135. package/lib/module/specs/NativeWhopAddressSuggestions.js +6 -0
  136. package/lib/module/specs/NativeWhopAddressSuggestions.js.map +1 -0
  137. package/lib/module/specs/NativeWhopAuthSession.js +10 -0
  138. package/lib/module/specs/NativeWhopAuthSession.js.map +1 -0
  139. package/lib/module/specs/NativeWhopPlatformPay.js +10 -0
  140. package/lib/module/specs/NativeWhopPlatformPay.js.map +1 -0
  141. package/lib/module/specs/WhopPayButtonNativeComponent.ts +22 -0
  142. package/lib/module/theme/appearance.js +85 -0
  143. package/lib/module/theme/appearance.js.map +1 -0
  144. package/lib/module/theme/appearance.test.js +124 -0
  145. package/lib/module/theme/appearance.test.js.map +1 -0
  146. package/lib/module/theme/provider.js +25 -0
  147. package/lib/module/theme/provider.js.map +1 -0
  148. package/lib/module/theme/resolve.js +69 -0
  149. package/lib/module/theme/resolve.js.map +1 -0
  150. package/lib/module/theme/scales.generated.js +419 -0
  151. package/lib/module/theme/scales.generated.js.map +1 -0
  152. package/lib/module/ui/element-frame.js +83 -0
  153. package/lib/module/ui/element-frame.js.map +1 -0
  154. package/lib/module/ui/payment-method-icon.js +69 -0
  155. package/lib/module/ui/payment-method-icon.js.map +1 -0
  156. package/lib/module/ui/primitives.js +266 -0
  157. package/lib/module/ui/primitives.js.map +1 -0
  158. package/lib/module/ui/select.js +281 -0
  159. package/lib/module/ui/select.js.map +1 -0
  160. package/lib/module/ui/suggestions.js +71 -0
  161. package/lib/module/ui/suggestions.js.map +1 -0
  162. package/lib/module/ui/whop-logo.js +42 -0
  163. package/lib/module/ui/whop-logo.js.map +1 -0
  164. package/lib/typescript/package.json +1 -0
  165. package/lib/typescript/src/api/client.d.ts +87 -0
  166. package/lib/typescript/src/api/client.d.ts.map +1 -0
  167. package/lib/typescript/src/api/types.d.ts +175 -0
  168. package/lib/typescript/src/api/types.d.ts.map +1 -0
  169. package/lib/typescript/src/controller/actions.d.ts +42 -0
  170. package/lib/typescript/src/controller/actions.d.ts.map +1 -0
  171. package/lib/typescript/src/controller/store.d.ts +90 -0
  172. package/lib/typescript/src/controller/store.d.ts.map +1 -0
  173. package/lib/typescript/src/core/address/country-data.d.ts +4 -0
  174. package/lib/typescript/src/core/address/country-data.d.ts.map +1 -0
  175. package/lib/typescript/src/core/address/country-name.d.ts +2 -0
  176. package/lib/typescript/src/core/address/country-name.d.ts.map +1 -0
  177. package/lib/typescript/src/core/address/country-names.generated.d.ts +2 -0
  178. package/lib/typescript/src/core/address/country-names.generated.d.ts.map +1 -0
  179. package/lib/typescript/src/core/address/engine.d.ts +97 -0
  180. package/lib/typescript/src/core/address/engine.d.ts.map +1 -0
  181. package/lib/typescript/src/core/address/form-state.d.ts +144 -0
  182. package/lib/typescript/src/core/address/form-state.d.ts.map +1 -0
  183. package/lib/typescript/src/core/address/places.d.ts +23 -0
  184. package/lib/typescript/src/core/address/places.d.ts.map +1 -0
  185. package/lib/typescript/src/core/address/suggest.d.ts +47 -0
  186. package/lib/typescript/src/core/address/suggest.d.ts.map +1 -0
  187. package/lib/typescript/src/core/address/use-suggestions.d.ts +3 -0
  188. package/lib/typescript/src/core/address/use-suggestions.d.ts.map +1 -0
  189. package/lib/typescript/src/core/payments/address-change.d.ts +52 -0
  190. package/lib/typescript/src/core/payments/address-change.d.ts.map +1 -0
  191. package/lib/typescript/src/core/payments/billing-merge.d.ts +39 -0
  192. package/lib/typescript/src/core/payments/billing-merge.d.ts.map +1 -0
  193. package/lib/typescript/src/core/payments/browser-info.d.ts +12 -0
  194. package/lib/typescript/src/core/payments/browser-info.d.ts.map +1 -0
  195. package/lib/typescript/src/core/payments/bt.d.ts +55 -0
  196. package/lib/typescript/src/core/payments/bt.d.ts.map +1 -0
  197. package/lib/typescript/src/core/payments/currency.d.ts +7 -0
  198. package/lib/typescript/src/core/payments/currency.d.ts.map +1 -0
  199. package/lib/typescript/src/core/payments/detail.d.ts +53 -0
  200. package/lib/typescript/src/core/payments/detail.d.ts.map +1 -0
  201. package/lib/typescript/src/core/payments/errors.d.ts +62 -0
  202. package/lib/typescript/src/core/payments/errors.d.ts.map +1 -0
  203. package/lib/typescript/src/core/payments/mint.d.ts +74 -0
  204. package/lib/typescript/src/core/payments/mint.d.ts.map +1 -0
  205. package/lib/typescript/src/core/payments/plan.d.ts +69 -0
  206. package/lib/typescript/src/core/payments/plan.d.ts.map +1 -0
  207. package/lib/typescript/src/core/payments/refusals.d.ts +12 -0
  208. package/lib/typescript/src/core/payments/refusals.d.ts.map +1 -0
  209. package/lib/typescript/src/core/payments/secure-fields.d.ts +33 -0
  210. package/lib/typescript/src/core/payments/secure-fields.d.ts.map +1 -0
  211. package/lib/typescript/src/core/payments/tax-id-catalog.d.ts +481 -0
  212. package/lib/typescript/src/core/payments/tax-id-catalog.d.ts.map +1 -0
  213. package/lib/typescript/src/core/payments/wallet-contact.d.ts +40 -0
  214. package/lib/typescript/src/core/payments/wallet-contact.d.ts.map +1 -0
  215. package/lib/typescript/src/elements/address.d.ts +55 -0
  216. package/lib/typescript/src/elements/address.d.ts.map +1 -0
  217. package/lib/typescript/src/elements/branding.d.ts +18 -0
  218. package/lib/typescript/src/elements/branding.d.ts.map +1 -0
  219. package/lib/typescript/src/elements/card-fields/context.d.ts +67 -0
  220. package/lib/typescript/src/elements/card-fields/context.d.ts.map +1 -0
  221. package/lib/typescript/src/elements/card-fields/fields.d.ts +15 -0
  222. package/lib/typescript/src/elements/card-fields/fields.d.ts.map +1 -0
  223. package/lib/typescript/src/elements/card-fields/index.d.ts +21 -0
  224. package/lib/typescript/src/elements/card-fields/index.d.ts.map +1 -0
  225. package/lib/typescript/src/elements/card.d.ts +21 -0
  226. package/lib/typescript/src/elements/card.d.ts.map +1 -0
  227. package/lib/typescript/src/elements/email.d.ts +31 -0
  228. package/lib/typescript/src/elements/email.d.ts.map +1 -0
  229. package/lib/typescript/src/elements/payment/declared-fields.d.ts +43 -0
  230. package/lib/typescript/src/elements/payment/declared-fields.d.ts.map +1 -0
  231. package/lib/typescript/src/elements/payment/index.d.ts +37 -0
  232. package/lib/typescript/src/elements/payment/index.d.ts.map +1 -0
  233. package/lib/typescript/src/elements/payment/wallet.d.ts +39 -0
  234. package/lib/typescript/src/elements/payment/wallet.d.ts.map +1 -0
  235. package/lib/typescript/src/elements/tax-id.d.ts +41 -0
  236. package/lib/typescript/src/elements/tax-id.d.ts.map +1 -0
  237. package/lib/typescript/src/i18n/catalogs.d.ts +7 -0
  238. package/lib/typescript/src/i18n/catalogs.d.ts.map +1 -0
  239. package/lib/typescript/src/i18n/index.d.ts +17 -0
  240. package/lib/typescript/src/i18n/index.d.ts.map +1 -0
  241. package/lib/typescript/src/index.d.ts +42 -0
  242. package/lib/typescript/src/index.d.ts.map +1 -0
  243. package/lib/typescript/src/namespace.d.ts +36 -0
  244. package/lib/typescript/src/namespace.d.ts.map +1 -0
  245. package/lib/typescript/src/native/index.d.ts +62 -0
  246. package/lib/typescript/src/native/index.d.ts.map +1 -0
  247. package/lib/typescript/src/provider.d.ts +37 -0
  248. package/lib/typescript/src/provider.d.ts.map +1 -0
  249. package/lib/typescript/src/specs/NativeWhopAddressSuggestions.d.ts +13 -0
  250. package/lib/typescript/src/specs/NativeWhopAddressSuggestions.d.ts.map +1 -0
  251. package/lib/typescript/src/specs/NativeWhopAuthSession.d.ts +20 -0
  252. package/lib/typescript/src/specs/NativeWhopAuthSession.d.ts.map +1 -0
  253. package/lib/typescript/src/specs/NativeWhopPlatformPay.d.ts +24 -0
  254. package/lib/typescript/src/specs/NativeWhopPlatformPay.d.ts.map +1 -0
  255. package/lib/typescript/src/specs/WhopPayButtonNativeComponent.d.ts +21 -0
  256. package/lib/typescript/src/specs/WhopPayButtonNativeComponent.d.ts.map +1 -0
  257. package/lib/typescript/src/theme/appearance.d.ts +71 -0
  258. package/lib/typescript/src/theme/appearance.d.ts.map +1 -0
  259. package/lib/typescript/src/theme/provider.d.ts +11 -0
  260. package/lib/typescript/src/theme/provider.d.ts.map +1 -0
  261. package/lib/typescript/src/theme/resolve.d.ts +19 -0
  262. package/lib/typescript/src/theme/resolve.d.ts.map +1 -0
  263. package/lib/typescript/src/theme/scales.generated.d.ts +18 -0
  264. package/lib/typescript/src/theme/scales.generated.d.ts.map +1 -0
  265. package/lib/typescript/src/ui/element-frame.d.ts +32 -0
  266. package/lib/typescript/src/ui/element-frame.d.ts.map +1 -0
  267. package/lib/typescript/src/ui/payment-method-icon.d.ts +21 -0
  268. package/lib/typescript/src/ui/payment-method-icon.d.ts.map +1 -0
  269. package/lib/typescript/src/ui/primitives.d.ts +94 -0
  270. package/lib/typescript/src/ui/primitives.d.ts.map +1 -0
  271. package/lib/typescript/src/ui/select.d.ts +25 -0
  272. package/lib/typescript/src/ui/select.d.ts.map +1 -0
  273. package/lib/typescript/src/ui/suggestions.d.ts +15 -0
  274. package/lib/typescript/src/ui/suggestions.d.ts.map +1 -0
  275. package/lib/typescript/src/ui/whop-logo.d.ts +4 -0
  276. package/lib/typescript/src/ui/whop-logo.d.ts.map +1 -0
  277. package/package.json +121 -0
  278. package/scripts/check-swift.sh +27 -0
  279. package/scripts/codegen-shims.mjs +409 -0
  280. package/scripts/gen-country-names.mjs +58 -0
  281. package/scripts/gen-theme.mjs +124 -0
  282. package/scripts/gen-wordmark.mjs +68 -0
  283. package/scripts/mirror.mjs +112 -0
  284. package/src/api/client.ts +202 -0
  285. package/src/api/types.ts +166 -0
  286. package/src/controller/actions.ts +197 -0
  287. package/src/controller/store.ts +201 -0
  288. package/src/core/address/country-data.ts +927 -0
  289. package/src/core/address/country-name.ts +36 -0
  290. package/src/core/address/country-names.generated.ts +246 -0
  291. package/src/core/address/engine.ts +349 -0
  292. package/src/core/address/form-state.ts +479 -0
  293. package/src/core/address/places.ts +39 -0
  294. package/src/core/address/suggest.ts +153 -0
  295. package/src/core/address/use-suggestions.ts +37 -0
  296. package/src/core/payments/address-change.ts +121 -0
  297. package/src/core/payments/billing-merge.ts +91 -0
  298. package/src/core/payments/browser-info.ts +43 -0
  299. package/src/core/payments/bt.ts +176 -0
  300. package/src/core/payments/currency.ts +54 -0
  301. package/src/core/payments/detail.ts +204 -0
  302. package/src/core/payments/errors.ts +171 -0
  303. package/src/core/payments/mint.ts +130 -0
  304. package/src/core/payments/plan.ts +198 -0
  305. package/src/core/payments/refusals.ts +24 -0
  306. package/src/core/payments/secure-fields.ts +85 -0
  307. package/src/core/payments/tax-id-catalog.ts +320 -0
  308. package/src/core/payments/wallet-contact.ts +56 -0
  309. package/src/elements/address.tsx +570 -0
  310. package/src/elements/branding.tsx +87 -0
  311. package/src/elements/card-fields/context.tsx +310 -0
  312. package/src/elements/card-fields/fields.tsx +281 -0
  313. package/src/elements/card-fields/index.tsx +37 -0
  314. package/src/elements/card.tsx +101 -0
  315. package/src/elements/email.tsx +100 -0
  316. package/src/elements/payment/declared-fields.tsx +238 -0
  317. package/src/elements/payment/index.tsx +497 -0
  318. package/src/elements/payment/wallet.tsx +155 -0
  319. package/src/elements/tax-id.tsx +143 -0
  320. package/src/i18n/catalogs.ts +100 -0
  321. package/src/i18n/index.tsx +46 -0
  322. package/src/index.ts +68 -0
  323. package/src/namespace.tsx +231 -0
  324. package/src/native/index.ts +173 -0
  325. package/src/provider.tsx +76 -0
  326. package/src/specs/NativeWhopAddressSuggestions.ts +14 -0
  327. package/src/specs/NativeWhopAuthSession.ts +21 -0
  328. package/src/specs/NativeWhopPlatformPay.ts +25 -0
  329. package/src/specs/WhopPayButtonNativeComponent.ts +22 -0
  330. package/src/theme/appearance.ts +150 -0
  331. package/src/theme/provider.tsx +28 -0
  332. package/src/theme/resolve.ts +68 -0
  333. package/src/theme/scales.generated.ts +427 -0
  334. package/src/ui/element-frame.tsx +94 -0
  335. package/src/ui/payment-method-icon.tsx +55 -0
  336. package/src/ui/primitives.tsx +269 -0
  337. package/src/ui/select.tsx +255 -0
  338. package/src/ui/suggestions.tsx +69 -0
  339. package/src/ui/whop-logo.tsx +37 -0
@@ -0,0 +1,37 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+
3
+ import { WhopAddressSuggestions } from '../../native';
4
+ import type { AddressSuggestion } from './suggest';
5
+
6
+ /** Long enough that a geocoder has something to work with, short enough to feel live. */
7
+ const MIN_QUERY_LENGTH = 3;
8
+ const DEBOUNCE_MS = 250;
9
+
10
+ export function useAddressSuggestions(enabled: boolean, query: string): AddressSuggestion[] {
11
+ const [rows, setRows] = useState<AddressSuggestion[]>([]);
12
+ const generation = useRef(0);
13
+
14
+ useEffect(() => {
15
+ // Advance BEFORE the early return: clearing the list does not cancel a lookup already in
16
+ // flight, so a generation that only moved when a new lookup was scheduled left the old one
17
+ // still matching, and deleting back to two characters repopulated the field.
18
+ const current = generation.current + 1;
19
+ generation.current = current;
20
+
21
+ const trimmed = query.trim();
22
+ if (!enabled || trimmed.length < MIN_QUERY_LENGTH) {
23
+ setRows([]);
24
+ return;
25
+ }
26
+ const timer = setTimeout(() => {
27
+ void WhopAddressSuggestions.suggest(trimmed).then((found) => {
28
+ // Geocoder latency is not ordered. Without this a slow lookup for "12 Ma" can land after
29
+ // "12 Main St" and put stale rows under a newer query.
30
+ if (generation.current === current) setRows(found);
31
+ });
32
+ }, DEBOUNCE_MS);
33
+ return () => clearTimeout(timer);
34
+ }, [enabled, query]);
35
+
36
+ return rows;
37
+ }
@@ -0,0 +1,121 @@
1
+ // ported verbatim from frontend/apps/web/src/elements/payments/lib/address-change.ts — keep in sync when the web element changes
2
+
3
+ import type { EmittedAddress } from '../address/engine';
4
+
5
+ import { createContext, useContext, useEffect, useMemo, useRef } from 'react';
6
+
7
+ /**
8
+ * The postal address the payment element's `addressChange` event carries: every place-identifying
9
+ * field the billing block collects, with the optionality the form itself has. Only `country` is
10
+ * guaranteed — each country's format collects its own field set (no state in the Netherlands, no
11
+ * postal code in Ireland, no line2 unless the buyer fills one), so a key is absent whenever the
12
+ * form did not collect a value for it. Name, organization, and email are not part of an address
13
+ * and stay off this event.
14
+ */
15
+ export interface PostalAddress {
16
+ line1?: string;
17
+ line2?: string;
18
+ city?: string;
19
+ state?: string;
20
+ postal_code?: string;
21
+ country: string;
22
+ }
23
+
24
+ export interface AddressChange {
25
+ complete: boolean;
26
+ address: PostalAddress;
27
+ }
28
+
29
+ /**
30
+ * A buyer typing a postal code produces one form snapshot per keystroke, and the consumer turns
31
+ * every emit into a tax quote or a shipping-rate lookup — one network call per character is the
32
+ * failure mode this guards. 300ms is the settle window: longer than the gap between keystrokes at
33
+ * any real typing speed (so a whole postal code collapses into ONE emit), short enough that the
34
+ * quote is back before the buyer reaches the pay button. TRAILING edge only — a leading emit would
35
+ * fire on the first character, which is the one value guaranteed to be useless.
36
+ */
37
+ export const ADDRESS_CHANGE_DEBOUNCE_MS = 300;
38
+
39
+ const OPTIONAL_ADDRESS_KEYS = ['line1', 'line2', 'city', 'state', 'postal_code'] as const;
40
+
41
+ export function postalAddressOf(address: EmittedAddress): PostalAddress {
42
+ const out: PostalAddress = { country: address.country };
43
+ for (const key of OPTIONAL_ADDRESS_KEYS) {
44
+ const value = address[key];
45
+ if (value) out[key] = value;
46
+ }
47
+ return out;
48
+ }
49
+
50
+ function sameAddressChange(a: AddressChange, b: AddressChange): boolean {
51
+ return (
52
+ a.complete === b.complete &&
53
+ a.address.country === b.address.country &&
54
+ OPTIONAL_ADDRESS_KEYS.every((key) => a.address[key] === b.address[key])
55
+ );
56
+ }
57
+
58
+ export interface AddressChangePublisher {
59
+ publish: (owner: string, change: AddressChange) => void;
60
+ /**
61
+ * The owner's block stopped being the address in force before its queued change settled — drop
62
+ * it. Scoped to the OWNER, not to the change object: a deselected block can still deliver one
63
+ * last snapshot in the very commit that deselects it (the form dispatches from a child effect,
64
+ * which flushes before this block's own), so a queue keyed on the change would miss the one
65
+ * actually in flight. A no-op once another block has queued its own — whichever order the two
66
+ * blocks' effects run in during a switch.
67
+ */
68
+ abandon: (owner: string) => void;
69
+ }
70
+
71
+ const AddressChangePublishContext = createContext<AddressChangePublisher>({
72
+ publish: () => {},
73
+ abandon: () => {},
74
+ });
75
+
76
+ export const AddressChangePublishProvider = AddressChangePublishContext.Provider;
77
+
78
+ export function usePublishAddressChange(): AddressChangePublisher {
79
+ return useContext(AddressChangePublishContext);
80
+ }
81
+
82
+ /**
83
+ * ELEMENT-SCOPED on purpose: every method tile mounts its own billing block, so the debounce
84
+ * timer and the last-emitted memory have to be shared across them — a per-block memory would
85
+ * suppress the republish that a tile switch back to an earlier address depends on.
86
+ */
87
+ export function useAddressChangePublisher(emit: (change: AddressChange) => void): AddressChangePublisher {
88
+ const emitRef = useRef(emit);
89
+ const pendingRef = useRef<{ owner: string; change: AddressChange } | undefined>(undefined);
90
+ const emittedRef = useRef<AddressChange | undefined>(undefined);
91
+ const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
92
+
93
+ useEffect(() => {
94
+ emitRef.current = emit;
95
+ });
96
+ useEffect(() => () => clearTimeout(timerRef.current), []);
97
+
98
+ return useMemo<AddressChangePublisher>(
99
+ () => ({
100
+ publish: (owner, change) => {
101
+ pendingRef.current = { owner, change };
102
+ clearTimeout(timerRef.current);
103
+ timerRef.current = setTimeout(() => {
104
+ const next = pendingRef.current?.change;
105
+ if (!next) return;
106
+ if (emittedRef.current && sameAddressChange(emittedRef.current, next)) return;
107
+ emittedRef.current = next;
108
+ // a fresh payload per delivery: the retained dedupe value must not alias an object a
109
+ // direct-mode consumer holds and could mutate
110
+ emitRef.current({ complete: next.complete, address: { ...next.address } });
111
+ }, ADDRESS_CHANGE_DEBOUNCE_MS);
112
+ },
113
+ abandon: (owner) => {
114
+ if (pendingRef.current?.owner !== owner) return;
115
+ pendingRef.current = undefined;
116
+ clearTimeout(timerRef.current);
117
+ },
118
+ }),
119
+ [],
120
+ );
121
+ }
@@ -0,0 +1,91 @@
1
+ // ported verbatim from frontend/apps/web/src/elements/payments/lib/billing-merge.ts — keep in sync when the web element changes
2
+
3
+ /**
4
+ * The billing-details precedence merge for the confirm rail: explicit
5
+ * `createConfirmationToken({ billingDetails })` input > standalone address element values >
6
+ * the payment element's internal billing block. Applied twice along the cascade (controller
7
+ * merges the address element under the input; the payment element merges its block under the
8
+ * result) — scalars merge field-level, the address object is ATOMIC per source (a mixed-source
9
+ * address would be incoherent), which keeps the two applications associative.
10
+ */
11
+ import type { EmittedAddress } from '../address/engine';
12
+
13
+ interface BillingAddressBag {
14
+ country?: string;
15
+ line1?: string;
16
+ line2?: string;
17
+ city?: string;
18
+ state?: string;
19
+ postal_code?: string;
20
+ }
21
+
22
+ export interface BillingDetailsBag {
23
+ email?: string;
24
+ name?: string;
25
+ phone?: string;
26
+ address?: BillingAddressBag;
27
+ }
28
+
29
+ export interface CollectedAddressSnapshot {
30
+ complete: boolean;
31
+ completeWithoutPhone?: boolean;
32
+ phoneRequired?: boolean;
33
+ address: EmittedAddress;
34
+ phone?: string;
35
+ }
36
+
37
+ function billingNameOf(address: EmittedAddress): string | undefined {
38
+ if (address.name) return address.name;
39
+ const joined = [address.first_name, address.last_name].filter(Boolean).join(' ');
40
+ return joined || undefined;
41
+ }
42
+
43
+ function billingAddressOf(address: EmittedAddress): BillingAddressBag | undefined {
44
+ const bag: BillingAddressBag = {};
45
+ if (address.country) bag.country = address.country;
46
+ if (address.line1) bag.line1 = address.line1;
47
+ if (address.line2) bag.line2 = address.line2;
48
+ if (address.city) bag.city = address.city;
49
+ if (address.state) bag.state = address.state;
50
+ if (address.postal_code) bag.postal_code = address.postal_code;
51
+ return Object.keys(bag).length > 0 ? bag : undefined;
52
+ }
53
+
54
+ function hasAddressValues(address: BillingAddressBag | undefined): boolean {
55
+ return Boolean(
56
+ address && Object.values(address).some((value) => typeof value === 'string' && value.trim().length > 0),
57
+ );
58
+ }
59
+
60
+ /**
61
+ * Merge a collected form snapshot UNDER an explicit billing-details bag. When the collected
62
+ * form is the address source actually being used and it is incomplete, the merge throws — a
63
+ * confirm over a half-filled form must fail loud, exactly like the surfaces' own validation
64
+ * (an explicit complete input keeps winning silently).
65
+ */
66
+ export function mergeBillingWithCollected(
67
+ explicit: BillingDetailsBag | undefined,
68
+ collected: CollectedAddressSnapshot | undefined,
69
+ ): BillingDetailsBag | undefined {
70
+ if (!collected) return explicit;
71
+ const explicitAddress = hasAddressValues(explicit?.address) ? explicit?.address : undefined;
72
+ const explicitCompletesPhone = Boolean(explicit?.phone && collected.completeWithoutPhone);
73
+ const requiredPhoneMissing = collected.phoneRequired && !explicit?.phone && !collected.phone;
74
+ if (requiredPhoneMissing) {
75
+ throw new Error('The address form is incomplete — finish it or pass billingDetails to createConfirmationToken');
76
+ }
77
+ if (!explicitAddress && !explicitCompletesPhone && !collected.complete) {
78
+ throw new Error('The address form is incomplete — finish it or pass billingDetails to createConfirmationToken');
79
+ }
80
+ const merged: BillingDetailsBag = { ...explicit };
81
+ if (!merged.phone && collected.phone) merged.phone = collected.phone;
82
+ if (!merged.name) {
83
+ const name = billingNameOf(collected.address);
84
+ if (name) merged.name = name;
85
+ else delete merged.name;
86
+ }
87
+ const address = explicitAddress ?? billingAddressOf(collected.address);
88
+ if (address) merged.address = address;
89
+ else delete merged.address;
90
+ return Object.keys(merged).length > 0 ? merged : explicit;
91
+ }
@@ -0,0 +1,43 @@
1
+ import { Dimensions, NativeModules, PixelRatio, Platform } from 'react-native';
2
+
3
+ import type { BrowserInfo } from '../../api/types';
4
+
5
+ /**
6
+ * The React Native counterpart of the web element's `browser_info` capture.
7
+ *
8
+ * The field names are the wire's, because the backend feeds them to 3DS, which was specified for
9
+ * browsers. An app has no `navigator`, so each value is the nearest honest platform fact rather
10
+ * than a fabricated browser one — omitted where there is no honest answer.
11
+ */
12
+ export function capturedBrowserInfo(): BrowserInfo {
13
+ const { width, height } = Dimensions.get('screen');
14
+ const scale = PixelRatio.get();
15
+
16
+ return {
17
+ platform: Platform.OS === 'ios' ? 'iPhone' : 'Linux armv8l',
18
+ color_depth: 24,
19
+ screen_width: Math.round(width * scale),
20
+ screen_height: Math.round(height * scale),
21
+ language: resolveLanguage(),
22
+ java_enabled: false,
23
+ browser_time_difference: new Date().getTimezoneOffset(),
24
+ user_agent: `WhopElementsReactNative/${Platform.OS} ${String(Platform.Version)}`,
25
+ };
26
+ }
27
+
28
+ function resolveLanguage(): string {
29
+ const tag = deviceLocale();
30
+ return tag ? tag.replace(/_/g, '-') : 'en-US';
31
+ }
32
+
33
+ /** The one platform read; kept separate so the locale default can reuse it. */
34
+ export function deviceLocale(): string | undefined {
35
+ if (Platform.OS === 'ios') {
36
+ const settings = NativeModules.SettingsManager?.settings as
37
+ | { AppleLocale?: string; AppleLanguages?: string[] }
38
+ | undefined;
39
+ return settings?.AppleLocale ?? settings?.AppleLanguages?.[0];
40
+ }
41
+ const i18n = NativeModules.I18nManager as { localeIdentifier?: string } | undefined;
42
+ return i18n?.localeIdentifier;
43
+ }
@@ -0,0 +1,176 @@
1
+ import { sha256 } from '@noble/hashes/sha2';
2
+ import { bytesToHex } from '@noble/hashes/utils';
3
+ import type { useBasisTheory } from '@basis-theory/react-native-elements';
4
+
5
+ import type { WhopClient } from '../../api/client';
6
+ import { apiErrorMessage } from './errors';
7
+ import type { PaymentTokenizationErrorCode } from './errors';
8
+
9
+ /** The package re-exports the hook but not its return type, so derive it rather than reach into a
10
+ * deep path that could move. */
11
+ export type BasisTheoryElements = NonNullable<ReturnType<typeof useBasisTheory>['bt']>;
12
+
13
+ /** The opaque handle a Basis Theory element hands out. It carries an id and a formatter, never the
14
+ * value the buyer typed — which is the whole isolation guarantee. */
15
+ export interface CardRefs {
16
+ number: unknown;
17
+ expiryMonth: unknown;
18
+ expiryYear: unknown;
19
+ cvc: unknown;
20
+ }
21
+
22
+ export type CardFieldFailure = 'incomplete' | 'invalid' | undefined;
23
+
24
+ export interface CardFieldFailures {
25
+ number?: CardFieldFailure;
26
+ expiry?: CardFieldFailure;
27
+ cvc?: CardFieldFailure;
28
+ }
29
+
30
+ export interface CardTokenizationFailure {
31
+ ok: false;
32
+ code: PaymentTokenizationErrorCode;
33
+ message: string;
34
+ fields?: CardFieldFailures;
35
+ }
36
+
37
+ export type CardTokenizationResult = { ok: true; token: string } | CardTokenizationFailure;
38
+
39
+ const CARD_TOKEN_TTL_MS = 60 * 60 * 1000;
40
+
41
+ /**
42
+ * Assemble one Basis Theory card token from the three field refs.
43
+ *
44
+ * This is the web ceremony (`card-fields/bt.ts`) with the frame hops removed: a BT session anchors
45
+ * a nonce-derived container, the number seeds an ephemeral token into it, the Whop backend
46
+ * authorizes the session for that container alone, and the remaining fields patch in under the
47
+ * session key. The web element splits these across three iframes and threads a serializable grant
48
+ * between them; here all three refs live in one realm, so the grant never leaves this function.
49
+ *
50
+ * The ceremony is kept rather than collapsed into a single create because it is what the
51
+ * card-assembly Basis Theory application is configured to permit: the backend authorizes
52
+ * `token:update` scoped to `starts_with` the derived container, for ten minutes.
53
+ */
54
+ export async function tokenizeCard(
55
+ bt: BasisTheoryElements,
56
+ refs: CardRefs,
57
+ command: { accountId: string; publicKey: string },
58
+ sdk: WhopClient,
59
+ ): Promise<CardTokenizationResult> {
60
+ if (!refs.number || !refs.expiryMonth || !refs.expiryYear || !refs.cvc) {
61
+ return {
62
+ ok: false,
63
+ code: 'card_fields_not_ready',
64
+ message: 'Card fields are not ready yet',
65
+ };
66
+ }
67
+
68
+ let session: Awaited<ReturnType<BasisTheoryElements['sessions']['create']>>;
69
+ try {
70
+ session = await bt.sessions.create();
71
+ } catch (err) {
72
+ return collectFailure(err, 'card_tokenizer_session_failed', 'card_tokenizer_session_failed');
73
+ }
74
+ if (!session) {
75
+ return {
76
+ ok: false,
77
+ code: 'card_tokenizer_session_failed',
78
+ message: 'the tokenizer returned no session',
79
+ };
80
+ }
81
+
82
+ const container = `/card-assembly/${sha256Hex(session.nonce)}/`;
83
+
84
+ let tokenId: string;
85
+ try {
86
+ const token = await bt.tokens.create(
87
+ {
88
+ type: 'card',
89
+ data: { number: refs.number as never },
90
+ containers: [container],
91
+ expiresAt: new Date(Date.now() + CARD_TOKEN_TTL_MS).toISOString(),
92
+ },
93
+ // The key is passed per call, never left to the client's config: Basis Theory's client
94
+ // is a module-level singleton whose first key wins for the whole process, so a second
95
+ // client built with a different application's key (a method's secure fields) would
96
+ // otherwise silently sign these calls.
97
+ { apiKey: command.publicKey },
98
+ );
99
+ if (!token?.id) {
100
+ return { ok: false, code: 'card_number_tokenization_failed', message: 'the tokenizer returned no token' };
101
+ }
102
+ tokenId = token.id;
103
+ } catch (err) {
104
+ return collectFailure(err, 'card_number_validation_failed', 'card_number_tokenization_failed');
105
+ }
106
+
107
+ try {
108
+ await sdk.paymentMethodTypes.createSession('card', { account_id: command.accountId, nonce: session.nonce });
109
+ } catch (err) {
110
+ return {
111
+ ok: false,
112
+ code: 'card_session_authorization_failed',
113
+ message: apiErrorMessage(err, 'card session authorization failed'),
114
+ };
115
+ }
116
+
117
+ try {
118
+ await bt.tokens.update(
119
+ tokenId,
120
+ { data: { expiration_month: refs.expiryMonth as never, expiration_year: refs.expiryYear as never } },
121
+ { apiKey: session.sessionKey },
122
+ );
123
+ } catch (err) {
124
+ return collectFailure(err, 'card_expiry_validation_failed', 'card_expiry_tokenization_failed');
125
+ }
126
+
127
+ try {
128
+ await bt.tokens.update(tokenId, { data: { cvc: refs.cvc as never } }, { apiKey: session.sessionKey });
129
+ } catch (err) {
130
+ return collectFailure(err, 'card_cvc_validation_failed', 'card_cvc_tokenization_failed');
131
+ }
132
+
133
+ return { ok: true, token: tokenId };
134
+ }
135
+
136
+ /**
137
+ * Must byte-match the backend's `Digest::SHA256.hexdigest` — lowercase hex, no separators.
138
+ *
139
+ * `@noble/hashes` UTF-8 encodes a string input itself, which is also why this does not reach for
140
+ * `TextEncoder`: React Native has no DOM and does not guarantee one.
141
+ */
142
+ export function sha256Hex(value: string): string {
143
+ return bytesToHex(sha256(value));
144
+ }
145
+
146
+ interface ValidationDetails {
147
+ data?: Record<string, { type?: string } | undefined>;
148
+ }
149
+
150
+ function collectFailure(
151
+ err: unknown,
152
+ validationCode: PaymentTokenizationErrorCode,
153
+ failureCode: PaymentTokenizationErrorCode,
154
+ ): CardTokenizationFailure {
155
+ const details =
156
+ err instanceof Error && err.name === 'BasisTheoryValidationError'
157
+ ? (err as Error & { details?: ValidationDetails }).details?.data
158
+ : undefined;
159
+ if (details) {
160
+ return {
161
+ ok: false,
162
+ code: validationCode,
163
+ message: 'Your card details are incomplete or invalid',
164
+ fields: {
165
+ number: fieldFailure(details.number),
166
+ cvc: fieldFailure(details.cvc),
167
+ expiry: fieldFailure(details.expiration_month) ?? fieldFailure(details.expiration_year),
168
+ },
169
+ };
170
+ }
171
+ return { ok: false, code: failureCode, message: err instanceof Error ? err.message : 'Card tokenization failed' };
172
+ }
173
+
174
+ function fieldFailure(detail: { type?: string } | undefined): CardFieldFailure {
175
+ return detail?.type === 'invalid' || detail?.type === 'incomplete' ? detail.type : undefined;
176
+ }
@@ -0,0 +1,54 @@
1
+ // ported verbatim from frontend/apps/web/src/elements/payments/lib/currency.ts — keep in sync when the web element changes
2
+
3
+ /**
4
+ * The minor-unit factor, mirroring the BACKEND's conversion exactly (Utils.amount_in_cents /
5
+ * stripe_factor_decimal over ZERO_DECIMAL_CURRENCIES + THREE_DECIMAL_CURRENCIES in
6
+ * backend/lib/application_constants.rb) — the backend is the authority on a currency's factor,
7
+ * NOT Intl/CLDR: several acceptable plan currencies (huf, cop, idr, pkr, all) are zero-decimal
8
+ * in CLDR but charged in 1/100ths by the backend, so an Intl-derived factor is 100× off for
9
+ * them. Keep these lists in lockstep with the backend constants.
10
+ */
11
+
12
+ const ZERO_DECIMAL_CURRENCIES = new Set([
13
+ 'bif',
14
+ 'clp',
15
+ 'djf',
16
+ 'gnf',
17
+ 'jpy',
18
+ 'kmf',
19
+ 'krw',
20
+ 'mga',
21
+ 'pyg',
22
+ 'rwf',
23
+ 'ugx',
24
+ 'vnd',
25
+ 'vuv',
26
+ 'xaf',
27
+ 'xof',
28
+ 'xpf',
29
+ ]);
30
+
31
+ const THREE_DECIMAL_CURRENCIES = new Set(['bhd', 'jod', 'kwd', 'omr', 'tnd']);
32
+
33
+ export function currencyMinorDigits(currency: string): number {
34
+ const code = currency.toLowerCase();
35
+ if (ZERO_DECIMAL_CURRENCIES.has(code)) return 0;
36
+ if (THREE_DECIMAL_CURRENCIES.has(code)) return 3;
37
+ return 2;
38
+ }
39
+
40
+ /** Major-unit decimal (how money rides the wire) → the minor-unit integer a charge takes, on the
41
+ * backend's factor above — never Intl's. toFixed(4) first (kills float-scale artifacts like
42
+ * 29.99×100 → 2998.999…), then trunc (a genuinely sub-minor fraction never rounds the charge UP). */
43
+ export function toMinorUnits(majorAmount: number, currency: string): number {
44
+ return Math.trunc(Number((majorAmount * 10 ** currencyMinorDigits(currency)).toFixed(4)));
45
+ }
46
+
47
+ // Charged in minor units but shown without them (PaymentsConstants::NO_MINOR_UNIT_CURRENCIES).
48
+ const NO_MINOR_UNIT_CURRENCIES = new Set(['cop']);
49
+
50
+ export function currencyDisplayDigits(currency: string): number {
51
+ const code = currency.toLowerCase();
52
+ if (NO_MINOR_UNIT_CURRENCIES.has(code)) return 0;
53
+ return currencyMinorDigits(code);
54
+ }