@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,56 @@
1
+ /** The contact fields an OS wallet sheet collected, in the wire keys the mint sends. */
2
+ export interface WalletContact {
3
+ email?: string;
4
+ name?: string;
5
+ address?: {
6
+ line1?: string;
7
+ line2?: string;
8
+ city?: string;
9
+ state?: string;
10
+ postal_code?: string;
11
+ country?: string;
12
+ };
13
+ }
14
+
15
+ /**
16
+ * The native envelope, read tolerantly.
17
+ *
18
+ * Both platform modules answer with `{ paymentData, email?, name?, address? }`. Anything else is
19
+ * treated as the bare token, so an app whose native side has not been rebuilt still tokenizes — it
20
+ * just gets no contacts, which is exactly the behaviour it had before.
21
+ */
22
+ export function readSheetResult(raw: string): { paymentData: string } & WalletContact {
23
+ try {
24
+ const parsed: unknown = JSON.parse(raw);
25
+ if (
26
+ parsed !== null &&
27
+ typeof parsed === 'object' &&
28
+ typeof (parsed as { paymentData?: unknown }).paymentData === 'string'
29
+ ) {
30
+ return parsed as { paymentData: string } & WalletContact;
31
+ }
32
+ } catch {
33
+ // Not JSON at all: Google Pay's opaque gateway token.
34
+ }
35
+ return { paymentData: raw };
36
+ }
37
+
38
+ /**
39
+ * Folds the sheet's own contacts under whatever the caller and the mounted elements already supply.
40
+ *
41
+ * Explicit input always wins. The sheet is only ever asked for an email or an address when no
42
+ * element is mounted to collect them, so in practice it fills exactly the gaps — which is the whole
43
+ * point: without this the mint refused a buyer who had just typed their email into Apple Pay.
44
+ */
45
+ export function mergeWalletContact(
46
+ explicit: Partial<{ email: string; name: string }> | undefined,
47
+ contact: WalletContact,
48
+ ): Partial<{ email: string; name: string }> & { address?: WalletContact['address'] } {
49
+ const merged: Partial<{ email: string; name: string }> & { address?: WalletContact['address'] } = {
50
+ ...explicit,
51
+ };
52
+ if (!merged.email && contact.email) merged.email = contact.email;
53
+ if (!merged.name && contact.name) merged.name = contact.name;
54
+ if (contact.address && !merged.address) merged.address = contact.address;
55
+ return merged;
56
+ }
@@ -0,0 +1,570 @@
1
+ import { type ReactNode, useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';
2
+ import { type StyleProp, View, type ViewStyle } from 'react-native';
3
+
4
+ import {
5
+ type CountryFormat,
6
+ type EmittedAddress,
7
+ loadCountryCatalog,
8
+ normalizeCountry,
9
+ parseCountryFormat,
10
+ type RemoteCountryData,
11
+ type StateOption,
12
+ } from '../core/address/engine';
13
+ import {
14
+ type AddressFormAction,
15
+ type AddressFormConfig,
16
+ addressFormReducer,
17
+ type AddressSnapshot,
18
+ type AddressValueKey,
19
+ allowedCountryList,
20
+ type CustomFieldSpec,
21
+ pairedGridRows,
22
+ planFields,
23
+ requiredFloorFor,
24
+ seedAddressForm,
25
+ snapshotAddressForm,
26
+ placeAllowList,
27
+ } from '../core/address/form-state';
28
+ import { countryName } from '../core/address/country-name';
29
+ import type { AddressSuggestion } from '../core/address/suggest';
30
+ import { resolveSubdivision } from '../core/address/suggest';
31
+ import { useAddressSuggestions } from '../core/address/use-suggestions';
32
+ import { WhopAddressSuggestions } from '../native';
33
+ import { SuggestionList } from '../ui/suggestions';
34
+ import { detectIpCountry } from '../core/address/places';
35
+ import { useLocale, useT } from '../i18n';
36
+ import { usePaymentsState, usePaymentsStore } from '../namespace';
37
+ import { useTheme } from '../theme/provider';
38
+ import { ElementFrame } from '../ui/element-frame';
39
+ import { Field, FieldSkeleton, Row, Stack, TextButton } from '../ui/primitives';
40
+ import { Select } from '../ui/select';
41
+
42
+ export interface AddressChangePayload {
43
+ complete: boolean;
44
+ address: EmittedAddress;
45
+ custom: Record<string, string>;
46
+ }
47
+
48
+ export interface AddressElementProps {
49
+ /** Autocomplete purpose: `billing` (default) or `shipping`. */
50
+ mode?: 'billing' | 'shipping';
51
+ /** `full` stacks labeled fields. `compact` groups placeholder-labeled fields. */
52
+ layout?: 'compact' | 'full';
53
+ /** `full` follows the country's complete format. `minimal` collects country and postal code. */
54
+ scope?: 'minimal' | 'full';
55
+ defaultValues?: { name?: string; address?: Partial<EmittedAddress> };
56
+ /** Default the country from the buyer's IP. Falls back to `countryHint`, then US. */
57
+ detectCountry?: boolean;
58
+ allowedCountries?: string[];
59
+ name?: 'combined' | 'split' | 'none';
60
+ organization?: 'none' | 'name' | 'name_with_type';
61
+ line2?: 'toggle' | 'always' | 'never';
62
+ /**
63
+ * Street suggestions from the platform geocoder — MapKit on iOS, `android.location.Geocoder` on
64
+ * Android. No API key and no account: where the web element asks Google Places, this asks the OS.
65
+ * Falls back silently to manual entry on a device whose geocoder answers nothing.
66
+ *
67
+ * @default true — the same default the web element takes.
68
+ */
69
+ autocomplete?: boolean;
70
+ customFields?: CustomFieldSpec[];
71
+ /** Fallback country after `defaultValues` and IP detection. ISO 3166-1 alpha-2. */
72
+ countryHint?: string;
73
+ style?: StyleProp<ViewStyle>;
74
+ fallback?: ReactNode;
75
+ onChange?: (payload: AddressChangePayload) => void;
76
+ onReady?: () => void;
77
+ onError?: (error: { message: string; code?: string }) => void;
78
+ }
79
+
80
+ /**
81
+ * Collects a postal address using the per-country format engine ported from the web element, so
82
+ * field order, labels, required-ness, postal validation, and the state lists are identical across
83
+ * the two surfaces for all 241 countries in the catalog.
84
+ */
85
+ export function AddressElement({
86
+ mode = 'billing',
87
+ layout = 'full',
88
+ scope = 'full',
89
+ defaultValues,
90
+ detectCountry = true,
91
+ allowedCountries,
92
+ name = 'combined',
93
+ organization = 'none',
94
+ line2 = 'toggle',
95
+ autocomplete = true,
96
+ customFields,
97
+ countryHint = '',
98
+ style,
99
+ fallback,
100
+ onChange,
101
+ onReady,
102
+ onError,
103
+ }: AddressElementProps) {
104
+ const t = useT();
105
+ const theme = useTheme();
106
+ const store = usePaymentsStore();
107
+ const methodCountries = usePaymentsState((s) => s.methodCountries);
108
+
109
+ const [catalog, setCatalog] = useState<Record<string, RemoteCountryData> | null>(null);
110
+ const [detected, setDetected] = useState<string | undefined>(undefined);
111
+ const [detecting, setDetecting] = useState(detectCountry);
112
+ const [failure, setFailure] = useState<{ message: string; code?: string } | null>(null);
113
+
114
+ useEffect(() => {
115
+ let alive = true;
116
+ loadCountryCatalog()
117
+ .then((data) => alive && setCatalog(data))
118
+ .catch((err: Error) => alive && setFailure({ message: err.message, code: 'ADDRESS_CATALOG_FAILED' }));
119
+ return () => {
120
+ alive = false;
121
+ };
122
+ }, []);
123
+
124
+ useEffect(() => {
125
+ if (!detectCountry) return;
126
+ let alive = true;
127
+ detectIpCountry()
128
+ .then((country) => {
129
+ if (!alive) return;
130
+ setDetected(country);
131
+ setDetecting(false);
132
+ })
133
+ .catch(() => alive && setDetecting(false));
134
+ return () => {
135
+ alive = false;
136
+ };
137
+ }, [detectCountry]);
138
+
139
+ // The method constraint NARROWS the consumer's allow list, never replaces it — replacing would
140
+ // let a tier's countries widen a restriction the integrator set deliberately.
141
+ const effectiveAllowed = useMemo(
142
+ () => effectiveAllowedCountries(methodCountries ?? undefined, allowedCountries),
143
+ [methodCountries, allowedCountries],
144
+ );
145
+
146
+ const config = useMemo<AddressFormConfig>(
147
+ () => ({
148
+ mode,
149
+ layout,
150
+ scope,
151
+ name,
152
+ organization,
153
+ line2,
154
+ autocomplete,
155
+ requiredFloor: requiredFloorFor(scope),
156
+ allowedCountries: effectiveAllowed,
157
+ customFields,
158
+ }),
159
+ [mode, layout, scope, name, organization, line2, autocomplete, effectiveAllowed, customFields],
160
+ );
161
+
162
+ const ready = Boolean(catalog) && !detecting;
163
+
164
+ if (!catalog || detecting) {
165
+ return (
166
+ <ElementFrame
167
+ style={style}
168
+ fallback={fallback}
169
+ skeleton={
170
+ <Stack>
171
+ <FieldSkeleton />
172
+ <FieldSkeleton />
173
+ <FieldSkeleton />
174
+ </Stack>
175
+ }
176
+ ready={false}
177
+ error={failure}
178
+ onReady={onReady}
179
+ onError={onError}
180
+ >
181
+ <View />
182
+ </ElementFrame>
183
+ );
184
+ }
185
+
186
+ return (
187
+ <AddressForm
188
+ catalog={catalog}
189
+ config={config}
190
+ detected={detected}
191
+ countryHint={countryHint}
192
+ defaultValues={defaultValues}
193
+ ready={ready}
194
+ style={style}
195
+ fallback={fallback}
196
+ onChange={onChange}
197
+ onReady={onReady}
198
+ onError={onError}
199
+ store={store}
200
+ t={t}
201
+ spacing={theme.tokens.spacing}
202
+ />
203
+ );
204
+ }
205
+
206
+ /** Intersection when both exist; a disjoint pair keeps the consumer's list rather than widening. */
207
+ export function effectiveAllowedCountries(
208
+ methodCountries: string[] | undefined,
209
+ allowedCountries: string[] | undefined,
210
+ ): string[] | undefined {
211
+ if (!methodCountries?.length) return allowedCountries;
212
+ if (!allowedCountries?.length) return methodCountries;
213
+ const consumerAllowed = new Set(allowedCountries.map((code) => code.toLowerCase()));
214
+ const intersection = methodCountries.filter((code) => consumerAllowed.has(code.toLowerCase()));
215
+ return intersection.length > 0 ? intersection : allowedCountries;
216
+ }
217
+
218
+ interface AddressFormProps {
219
+ catalog: Record<string, RemoteCountryData>;
220
+ config: AddressFormConfig;
221
+ detected: string | undefined;
222
+ countryHint: string;
223
+ defaultValues: AddressElementProps['defaultValues'];
224
+ ready: boolean;
225
+ style?: StyleProp<ViewStyle>;
226
+ fallback?: ReactNode;
227
+ onChange?: (payload: AddressChangePayload) => void;
228
+ onReady?: () => void;
229
+ onError?: (error: { message: string; code?: string }) => void;
230
+ store: ReturnType<typeof usePaymentsStore>;
231
+ t: ReturnType<typeof useT>;
232
+ spacing: number;
233
+ }
234
+
235
+ function AddressForm({
236
+ catalog,
237
+ config,
238
+ detected,
239
+ countryHint,
240
+ defaultValues,
241
+ ready,
242
+ style,
243
+ fallback,
244
+ onChange,
245
+ onReady,
246
+ onError,
247
+ store,
248
+ t,
249
+ spacing,
250
+ }: AddressFormProps) {
251
+ const catalogCountries = useMemo(() => Object.keys(catalog), [catalog]);
252
+
253
+ const [state, rawDispatch] = useReducer(
254
+ addressFormReducer,
255
+ undefined,
256
+ () => seedAddressForm(config, { defaultValues, detectedCountry: detected, countryHint }, catalogCountries),
257
+ );
258
+
259
+ const format: CountryFormat = useMemo(
260
+ () => parseCountryFormat(state.country, catalog[state.country]),
261
+ [state.country, catalog],
262
+ );
263
+ const plan = useMemo(() => planFields(config, format), [config, format]);
264
+ const snapshot: AddressSnapshot = useMemo(
265
+ () => snapshotAddressForm(config, format, state),
266
+ [config, format, state],
267
+ );
268
+
269
+ const locale = useLocale();
270
+ const countryOptions = useMemo(() => {
271
+ const allowed = allowedCountryList(config, catalogCountries);
272
+ return allowed
273
+ .map((code) => ({ value: code, label: countryName(code, locale) }))
274
+ .sort((a, b) => a.label.localeCompare(b.label));
275
+ }, [config, catalogCountries, locale]);
276
+
277
+ const latestChange = useRef(onChange);
278
+ latestChange.current = onChange;
279
+
280
+ // The address element is the billing source while it is mounted, so the controller reads its
281
+ // snapshot at confirm rather than the payment element's internal block.
282
+ useEffect(() => {
283
+ store.patch({ addressLive: true });
284
+ return () => store.patch({ addressLive: false, addressCollected: undefined });
285
+ }, [store]);
286
+
287
+ useEffect(() => {
288
+ store.patch({
289
+ addressCollected: {
290
+ complete: snapshot.complete,
291
+ address: snapshot.address,
292
+ custom: snapshot.custom,
293
+ } as never,
294
+ });
295
+ latestChange.current?.({ complete: snapshot.complete, address: snapshot.address, custom: snapshot.custom });
296
+ }, [snapshot, store]);
297
+
298
+ const [line1Focused, setLine1Focused] = useState(false);
299
+ const suggestions = useAddressSuggestions(config.autocomplete, state.values.line1 ?? '');
300
+ const placeAllowed = useMemo(() => placeAllowList(config, catalogCountries), [config, catalogCountries]);
301
+ const showSuggestions = config.autocomplete && line1Focused && suggestions.length > 0;
302
+
303
+ // `apply-place` WHOLE-REPLACES the locality grid, so a resolve landing after the buyer moved on
304
+ // would silently rewrite line1, city, state and postal under them.
305
+ //
306
+ // The supersede is tracked with a counter bumped INSIDE dispatch rather than by reading the
307
+ // reducer's own `selecting` flag through a ref. A ref is only assigned during render, and an
308
+ // Android resolve is a cache hit that can settle before that render happens — which raced both
309
+ // ways: a fresh pick read a stale `false` and no-opped, and a superseding edit had not yet been
310
+ // seen, so a stale place still applied.
311
+ const pickSequence = useRef(0);
312
+ const dispatch = useCallback((action: AddressFormAction) => {
313
+ if (action.type === 'edit' || action.type === 'set-country') pickSequence.current += 1;
314
+ rawDispatch(action);
315
+ }, []);
316
+
317
+ const pickSuggestion = (suggestion: AddressSuggestion) => {
318
+ setLine1Focused(false);
319
+ const sequence = pickSequence.current + 1;
320
+ pickSequence.current = sequence;
321
+ dispatch({ type: 'begin-select' });
322
+ void WhopAddressSuggestions.resolve(suggestion.id, state.country).then((parsed) => {
323
+ if (pickSequence.current !== sequence) return;
324
+ if (!parsed) {
325
+ dispatch({ type: 'select-failed' });
326
+ return;
327
+ }
328
+ // The state Select is keyed on the catalog's sub_key, and a geocoder may answer with the
329
+ // display name instead, so an unresolved subdivision must not reach the form as-is.
330
+ dispatch({
331
+ type: 'apply-place',
332
+ parsed: { ...parsed, state: resolveSubdivision(parsed.state, format.stateOptions) },
333
+ allowed: placeAllowed,
334
+ });
335
+ });
336
+ };
337
+
338
+ const showError = (key: string) => (state.showAllErrors || state.touched[key] ? errorText(t, snapshot.errors[key]) : undefined);
339
+ const compact = config.layout === 'compact';
340
+
341
+ const textField = (key: AddressValueKey, label: string, extra?: Partial<Parameters<typeof Field>[0]>) => (
342
+ <Field
343
+ key={key}
344
+ label={compact ? undefined : label}
345
+ placeholder={compact ? label : undefined}
346
+ accessibilityLabel={label}
347
+ value={state.values[key] ?? ''}
348
+ onChangeText={(value) => dispatch({ type: 'edit', field: key, value })}
349
+ onBlur={() => dispatch({ type: 'blur', field: key })}
350
+ error={showError(key)}
351
+ containerStyle={{ flex: 1 }}
352
+ {...extra}
353
+ />
354
+ );
355
+
356
+ const nameFields =
357
+ config.name === 'none'
358
+ ? null
359
+ : config.name === 'split'
360
+ ? (
361
+ <Row>
362
+ {textField('first_name', t('First name'), { autoComplete: 'given-name', textContentType: 'givenName' })}
363
+ {textField('last_name', t('Last name'), { autoComplete: 'family-name', textContentType: 'familyName' })}
364
+ </Row>
365
+ )
366
+ : textField('name', t('Name'), { autoComplete: 'name', textContentType: 'name' });
367
+
368
+ const organizationFields =
369
+ config.organization === 'none' ? null : (
370
+ <Stack gap={spacing * 2}>
371
+ {textField('organization', t('Organization'), { autoComplete: 'organization' })}
372
+ {config.organization === 'name_with_type' ? (
373
+ <Select
374
+ label={t('Organization type')}
375
+ value={state.organizationType || undefined}
376
+ options={[
377
+ { value: 'business', label: t('Business') },
378
+ { value: 'individual', label: t('Individual') },
379
+ ]}
380
+ onChange={(value) =>
381
+ dispatch({ type: 'set-organization-type', value: value as 'business' | 'individual' })
382
+ }
383
+ />
384
+ ) : null}
385
+ </Stack>
386
+ );
387
+
388
+ const customAt = (position: CustomFieldSpec['position']) =>
389
+ (config.customFields ?? [])
390
+ .filter((field) => field.position === position)
391
+ .map((field) => {
392
+ const key = `custom:${field.key}`;
393
+ const value = state.custom[field.key] ?? '';
394
+ const onChange = (next: string) => dispatch({ type: 'edit-custom', key: field.key, value: next });
395
+ if (field.type === 'select') {
396
+ return (
397
+ <Select
398
+ key={key}
399
+ label={compact ? undefined : field.label}
400
+ placeholder={compact ? field.label : undefined}
401
+ accessibilityLabel={field.label}
402
+ value={value || undefined}
403
+ options={(field.options ?? []).map((option) => ({ value: option, label: option }))}
404
+ onChange={onChange}
405
+ error={showError(key)}
406
+ searchable={(field.options ?? []).length > 12}
407
+ />
408
+ );
409
+ }
410
+ return (
411
+ <Field
412
+ key={key}
413
+ label={compact ? undefined : field.label}
414
+ placeholder={compact ? field.label : undefined}
415
+ accessibilityLabel={field.label}
416
+ value={value}
417
+ onChangeText={onChange}
418
+ onBlur={() => dispatch({ type: 'blur', field: key })}
419
+ error={showError(key)}
420
+ keyboardType={field.type === 'phone' ? 'phone-pad' : 'default'}
421
+ autoComplete={field.type === 'phone' ? 'tel' : undefined}
422
+ />
423
+ );
424
+ });
425
+
426
+ const gridRows = pairedGridRows(plan.gridFields);
427
+
428
+ return (
429
+ <ElementFrame
430
+ style={style}
431
+ fallback={fallback}
432
+ skeleton={
433
+ <Stack>
434
+ <FieldSkeleton />
435
+ <FieldSkeleton />
436
+ <FieldSkeleton />
437
+ </Stack>
438
+ }
439
+ ready={ready}
440
+ onReady={onReady}
441
+ onError={onError}
442
+ >
443
+ <Stack>
444
+ {nameFields}
445
+ {customAt('after_name')}
446
+ {organizationFields}
447
+ {customAt('after_organization')}
448
+ {customAt('before_country')}
449
+ <Select
450
+ label={t('Country')}
451
+ value={state.country}
452
+ options={countryOptions}
453
+ onChange={(country) => dispatch({ type: 'set-country', country: normalizeCountry(country) ?? country })}
454
+ searchable
455
+ partName="whop-AddressCountrySelect"
456
+ />
457
+ {plan.gridFields.includes('line1') ? (
458
+ <View>
459
+ {textField('line1', gridLabel(t, 'line1', format), {
460
+ ...gridExtras('line1', format),
461
+ onFocus: () => setLine1Focused(true),
462
+ onBlur: () => {
463
+ setLine1Focused(false);
464
+ dispatch({ type: 'blur', field: 'line1' });
465
+ },
466
+ })}
467
+ {showSuggestions ? <SuggestionList suggestions={suggestions} onPick={pickSuggestion} /> : null}
468
+ </View>
469
+ ) : null}
470
+ {config.line2 === 'always' || state.line2Open ? textField('line2', t('Address line 2')) : null}
471
+ {gridRows.map((row) => (
472
+ <Row key={row.join('-')}>
473
+ {row.map((key) =>
474
+ key === 'state' && format.stateOptions.length > 0
475
+ ? (
476
+ <View key={key} style={{ flex: 1 }}>
477
+ <Select
478
+ label={compact ? undefined : stateLabel(t, format)}
479
+ placeholder={compact ? stateLabel(t, format) : undefined}
480
+ accessibilityLabel={stateLabel(t, format)}
481
+ value={state.values.state || undefined}
482
+ options={format.stateOptions.map(toOption)}
483
+ onChange={(value) => dispatch({ type: 'edit', field: 'state', value })}
484
+ error={showError('state')}
485
+ searchable={format.stateOptions.length > 12}
486
+ />
487
+ </View>
488
+ )
489
+ : textField(key, gridLabel(t, key, format), gridExtras(key, format)),
490
+ )}
491
+ </Row>
492
+ ))}
493
+ {customAt('after_address')}
494
+ {config.line2 === 'toggle' && !state.line2Open ? (
495
+ <TextButton label={t('Add address line 2')} onPress={() => dispatch({ type: 'toggle-line2' })} />
496
+ ) : null}
497
+ </Stack>
498
+ </ElementFrame>
499
+ );
500
+ }
501
+
502
+ function toOption(option: StateOption): { value: string; label: string } {
503
+ // The engine keys options by the catalog's own sub_key; `iso` is display metadata, not the value
504
+ // the format engine validates against.
505
+ return { value: option.key, label: option.name };
506
+ }
507
+
508
+ function stateLabel(t: ReturnType<typeof useT>, format: CountryFormat): string {
509
+ switch (format.stateLabel) {
510
+ case 'province':
511
+ return t('Province');
512
+ case 'emirate':
513
+ return t('Emirate');
514
+ case 'county':
515
+ return t('County');
516
+ case 'prefecture':
517
+ return t('Prefecture');
518
+ case 'island':
519
+ return t('Island');
520
+ case 'parish':
521
+ return t('Parish');
522
+ case 'department':
523
+ return t('Department');
524
+ case 'do_si':
525
+ return t('Province');
526
+ case 'oblast':
527
+ return t('Oblast');
528
+ case 'area':
529
+ return t('Area');
530
+ default:
531
+ return t('State');
532
+ }
533
+ }
534
+
535
+ function gridLabel(t: ReturnType<typeof useT>, key: string, format: CountryFormat): string {
536
+ switch (key) {
537
+ case 'line1':
538
+ return t('Address line 1');
539
+ case 'city':
540
+ return t('City');
541
+ case 'state':
542
+ return stateLabel(t, format);
543
+ case 'postal_code':
544
+ return format.postalLabel === 'zip' ? t('ZIP code') : format.postalLabel === 'pin' ? t('PIN code') : t('Postal code');
545
+ default:
546
+ return key;
547
+ }
548
+ }
549
+
550
+ function gridExtras(key: string, format: CountryFormat): Partial<Parameters<typeof Field>[0]> {
551
+ if (key === 'postal_code') {
552
+ const field = format.fields.find((f) => f.type === 'postal_code');
553
+ return {
554
+ autoComplete: 'postal-code',
555
+ textContentType: 'postalCode',
556
+ autoCapitalize: 'characters',
557
+ placeholder: field?.postalPlaceholder,
558
+ };
559
+ }
560
+ if (key === 'line1') return { autoComplete: 'address-line1', textContentType: 'streetAddressLine1' };
561
+ if (key === 'city') return { autoComplete: 'postal-address-locality', textContentType: 'addressCity' };
562
+ return {};
563
+ }
564
+
565
+ function errorText(t: ReturnType<typeof useT>, code: string | undefined): string | undefined {
566
+ if (code === 'required') return t('Required');
567
+ if (code === 'invalid') return t("That doesn't look right");
568
+ return undefined;
569
+ }
570
+