@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.
- package/README.md +179 -0
- package/android/build.gradle +64 -0
- package/android/consumer-rules.pro +3 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/java/com/whop/elements/WhopAddressSuggestionsModule.kt +102 -0
- package/android/src/main/java/com/whop/elements/WhopAuthSessionModule.kt +126 -0
- package/android/src/main/java/com/whop/elements/WhopElementsPackage.kt +39 -0
- package/android/src/main/java/com/whop/elements/WhopPayButtonManager.kt +74 -0
- package/android/src/main/java/com/whop/elements/WhopPayButtonPressEvent.kt +17 -0
- package/android/src/main/java/com/whop/elements/WhopPayButtonView.kt +178 -0
- package/android/src/main/java/com/whop/elements/WhopPlatformPayModule.kt +212 -0
- package/assets/whop-wordmark-dark.png +0 -0
- package/assets/whop-wordmark-dark@2x.png +0 -0
- package/assets/whop-wordmark-dark@3x.png +0 -0
- package/assets/whop-wordmark-light.png +0 -0
- package/assets/whop-wordmark-light@2x.png +0 -0
- package/assets/whop-wordmark-light@3x.png +0 -0
- package/ios/Package.swift +59 -0
- package/ios/Sources/ElementsReactNative/WhopAddressSuggestionsImpl.swift +189 -0
- package/ios/Sources/ElementsReactNative/WhopAuthSessionImpl.swift +106 -0
- package/ios/Sources/ElementsReactNative/WhopPayButtonImpl.swift +89 -0
- package/ios/Sources/ElementsReactNative/WhopPlatformPayImpl.swift +181 -0
- package/ios/generated/WhopAddressSuggestionsModule.h +11 -0
- package/ios/generated/WhopAddressSuggestionsModule.mm +46 -0
- package/ios/generated/WhopAuthSessionModule.h +11 -0
- package/ios/generated/WhopAuthSessionModule.mm +45 -0
- package/ios/generated/WhopElementsShims.h +35 -0
- package/ios/generated/WhopPayButtonComponentView.h +12 -0
- package/ios/generated/WhopPayButtonComponentView.mm +82 -0
- package/ios/generated/WhopPlatformPayModule.h +11 -0
- package/ios/generated/WhopPlatformPayModule.mm +51 -0
- package/lib/module/api/client.js +139 -0
- package/lib/module/api/client.js.map +1 -0
- package/lib/module/api/client.test.js +137 -0
- package/lib/module/api/client.test.js.map +1 -0
- package/lib/module/api/types.js +2 -0
- package/lib/module/api/types.js.map +1 -0
- package/lib/module/controller/actions.js +148 -0
- package/lib/module/controller/actions.js.map +1 -0
- package/lib/module/controller/actions.test.js +102 -0
- package/lib/module/controller/actions.test.js.map +1 -0
- package/lib/module/controller/store.js +158 -0
- package/lib/module/controller/store.js.map +1 -0
- package/lib/module/core/address/country-data.js +1370 -0
- package/lib/module/core/address/country-data.js.map +1 -0
- package/lib/module/core/address/country-name.js +38 -0
- package/lib/module/core/address/country-name.js.map +1 -0
- package/lib/module/core/address/country-names.generated.js +249 -0
- package/lib/module/core/address/country-names.generated.js.map +1 -0
- package/lib/module/core/address/engine.js +245 -0
- package/lib/module/core/address/engine.js.map +1 -0
- package/lib/module/core/address/engine.test.js +216 -0
- package/lib/module/core/address/engine.test.js.map +1 -0
- package/lib/module/core/address/form-state.js +409 -0
- package/lib/module/core/address/form-state.js.map +1 -0
- package/lib/module/core/address/form-state.test.js +516 -0
- package/lib/module/core/address/form-state.test.js.map +1 -0
- package/lib/module/core/address/places.js +36 -0
- package/lib/module/core/address/places.js.map +1 -0
- package/lib/module/core/address/required-coverage.test.js +57 -0
- package/lib/module/core/address/required-coverage.test.js.map +1 -0
- package/lib/module/core/address/suggest.js +102 -0
- package/lib/module/core/address/suggest.js.map +1 -0
- package/lib/module/core/address/suggest.test.js +156 -0
- package/lib/module/core/address/suggest.test.js.map +1 -0
- package/lib/module/core/address/use-suggestions.js +33 -0
- package/lib/module/core/address/use-suggestions.js.map +1 -0
- package/lib/module/core/payments/address-change.js +91 -0
- package/lib/module/core/payments/address-change.js.map +1 -0
- package/lib/module/core/payments/billing-merge.js +62 -0
- package/lib/module/core/payments/billing-merge.js.map +1 -0
- package/lib/module/core/payments/browser-info.js +42 -0
- package/lib/module/core/payments/browser-info.js.map +1 -0
- package/lib/module/core/payments/bt.js +151 -0
- package/lib/module/core/payments/bt.js.map +1 -0
- package/lib/module/core/payments/bt.test.js +23 -0
- package/lib/module/core/payments/bt.test.js.map +1 -0
- package/lib/module/core/payments/currency.js +37 -0
- package/lib/module/core/payments/currency.js.map +1 -0
- package/lib/module/core/payments/detail.js +159 -0
- package/lib/module/core/payments/detail.js.map +1 -0
- package/lib/module/core/payments/detail.test.js +225 -0
- package/lib/module/core/payments/detail.test.js.map +1 -0
- package/lib/module/core/payments/errors.js +115 -0
- package/lib/module/core/payments/errors.js.map +1 -0
- package/lib/module/core/payments/mint.js +89 -0
- package/lib/module/core/payments/mint.js.map +1 -0
- package/lib/module/core/payments/plan.js +131 -0
- package/lib/module/core/payments/plan.js.map +1 -0
- package/lib/module/core/payments/refusals.js +17 -0
- package/lib/module/core/payments/refusals.js.map +1 -0
- package/lib/module/core/payments/secure-fields.js +87 -0
- package/lib/module/core/payments/secure-fields.js.map +1 -0
- package/lib/module/core/payments/tax-id-catalog.js +616 -0
- package/lib/module/core/payments/tax-id-catalog.js.map +1 -0
- package/lib/module/core/payments/wallet-contact.js +42 -0
- package/lib/module/core/payments/wallet-contact.js.map +1 -0
- package/lib/module/core/payments/wallet-contact.test.js +77 -0
- package/lib/module/core/payments/wallet-contact.test.js.map +1 -0
- package/lib/module/elements/address.js +467 -0
- package/lib/module/elements/address.js.map +1 -0
- package/lib/module/elements/branding.js +114 -0
- package/lib/module/elements/branding.js.map +1 -0
- package/lib/module/elements/card-fields/context.js +254 -0
- package/lib/module/elements/card-fields/context.js.map +1 -0
- package/lib/module/elements/card-fields/fields.js +309 -0
- package/lib/module/elements/card-fields/fields.js.map +1 -0
- package/lib/module/elements/card-fields/index.js +30 -0
- package/lib/module/elements/card-fields/index.js.map +1 -0
- package/lib/module/elements/card.js +107 -0
- package/lib/module/elements/card.js.map +1 -0
- package/lib/module/elements/email.js +84 -0
- package/lib/module/elements/email.js.map +1 -0
- package/lib/module/elements/payment/declared-fields.js +186 -0
- package/lib/module/elements/payment/declared-fields.js.map +1 -0
- package/lib/module/elements/payment/index.js +448 -0
- package/lib/module/elements/payment/index.js.map +1 -0
- package/lib/module/elements/payment/wallet.js +132 -0
- package/lib/module/elements/payment/wallet.js.map +1 -0
- package/lib/module/elements/tax-id.js +100 -0
- package/lib/module/elements/tax-id.js.map +1 -0
- package/lib/module/i18n/catalogs.js +103 -0
- package/lib/module/i18n/catalogs.js.map +1 -0
- package/lib/module/i18n/index.js +45 -0
- package/lib/module/i18n/index.js.map +1 -0
- package/lib/module/index.js +27 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/namespace.js +174 -0
- package/lib/module/namespace.js.map +1 -0
- package/lib/module/native/index.js +128 -0
- package/lib/module/native/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/provider.js +56 -0
- package/lib/module/provider.js.map +1 -0
- package/lib/module/specs/NativeWhopAddressSuggestions.js +6 -0
- package/lib/module/specs/NativeWhopAddressSuggestions.js.map +1 -0
- package/lib/module/specs/NativeWhopAuthSession.js +10 -0
- package/lib/module/specs/NativeWhopAuthSession.js.map +1 -0
- package/lib/module/specs/NativeWhopPlatformPay.js +10 -0
- package/lib/module/specs/NativeWhopPlatformPay.js.map +1 -0
- package/lib/module/specs/WhopPayButtonNativeComponent.ts +22 -0
- package/lib/module/theme/appearance.js +85 -0
- package/lib/module/theme/appearance.js.map +1 -0
- package/lib/module/theme/appearance.test.js +124 -0
- package/lib/module/theme/appearance.test.js.map +1 -0
- package/lib/module/theme/provider.js +25 -0
- package/lib/module/theme/provider.js.map +1 -0
- package/lib/module/theme/resolve.js +69 -0
- package/lib/module/theme/resolve.js.map +1 -0
- package/lib/module/theme/scales.generated.js +419 -0
- package/lib/module/theme/scales.generated.js.map +1 -0
- package/lib/module/ui/element-frame.js +83 -0
- package/lib/module/ui/element-frame.js.map +1 -0
- package/lib/module/ui/payment-method-icon.js +69 -0
- package/lib/module/ui/payment-method-icon.js.map +1 -0
- package/lib/module/ui/primitives.js +266 -0
- package/lib/module/ui/primitives.js.map +1 -0
- package/lib/module/ui/select.js +281 -0
- package/lib/module/ui/select.js.map +1 -0
- package/lib/module/ui/suggestions.js +71 -0
- package/lib/module/ui/suggestions.js.map +1 -0
- package/lib/module/ui/whop-logo.js +42 -0
- package/lib/module/ui/whop-logo.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/api/client.d.ts +87 -0
- package/lib/typescript/src/api/client.d.ts.map +1 -0
- package/lib/typescript/src/api/types.d.ts +175 -0
- package/lib/typescript/src/api/types.d.ts.map +1 -0
- package/lib/typescript/src/controller/actions.d.ts +42 -0
- package/lib/typescript/src/controller/actions.d.ts.map +1 -0
- package/lib/typescript/src/controller/store.d.ts +90 -0
- package/lib/typescript/src/controller/store.d.ts.map +1 -0
- package/lib/typescript/src/core/address/country-data.d.ts +4 -0
- package/lib/typescript/src/core/address/country-data.d.ts.map +1 -0
- package/lib/typescript/src/core/address/country-name.d.ts +2 -0
- package/lib/typescript/src/core/address/country-name.d.ts.map +1 -0
- package/lib/typescript/src/core/address/country-names.generated.d.ts +2 -0
- package/lib/typescript/src/core/address/country-names.generated.d.ts.map +1 -0
- package/lib/typescript/src/core/address/engine.d.ts +97 -0
- package/lib/typescript/src/core/address/engine.d.ts.map +1 -0
- package/lib/typescript/src/core/address/form-state.d.ts +144 -0
- package/lib/typescript/src/core/address/form-state.d.ts.map +1 -0
- package/lib/typescript/src/core/address/places.d.ts +23 -0
- package/lib/typescript/src/core/address/places.d.ts.map +1 -0
- package/lib/typescript/src/core/address/suggest.d.ts +47 -0
- package/lib/typescript/src/core/address/suggest.d.ts.map +1 -0
- package/lib/typescript/src/core/address/use-suggestions.d.ts +3 -0
- package/lib/typescript/src/core/address/use-suggestions.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/address-change.d.ts +52 -0
- package/lib/typescript/src/core/payments/address-change.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/billing-merge.d.ts +39 -0
- package/lib/typescript/src/core/payments/billing-merge.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/browser-info.d.ts +12 -0
- package/lib/typescript/src/core/payments/browser-info.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/bt.d.ts +55 -0
- package/lib/typescript/src/core/payments/bt.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/currency.d.ts +7 -0
- package/lib/typescript/src/core/payments/currency.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/detail.d.ts +53 -0
- package/lib/typescript/src/core/payments/detail.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/errors.d.ts +62 -0
- package/lib/typescript/src/core/payments/errors.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/mint.d.ts +74 -0
- package/lib/typescript/src/core/payments/mint.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/plan.d.ts +69 -0
- package/lib/typescript/src/core/payments/plan.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/refusals.d.ts +12 -0
- package/lib/typescript/src/core/payments/refusals.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/secure-fields.d.ts +33 -0
- package/lib/typescript/src/core/payments/secure-fields.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/tax-id-catalog.d.ts +481 -0
- package/lib/typescript/src/core/payments/tax-id-catalog.d.ts.map +1 -0
- package/lib/typescript/src/core/payments/wallet-contact.d.ts +40 -0
- package/lib/typescript/src/core/payments/wallet-contact.d.ts.map +1 -0
- package/lib/typescript/src/elements/address.d.ts +55 -0
- package/lib/typescript/src/elements/address.d.ts.map +1 -0
- package/lib/typescript/src/elements/branding.d.ts +18 -0
- package/lib/typescript/src/elements/branding.d.ts.map +1 -0
- package/lib/typescript/src/elements/card-fields/context.d.ts +67 -0
- package/lib/typescript/src/elements/card-fields/context.d.ts.map +1 -0
- package/lib/typescript/src/elements/card-fields/fields.d.ts +15 -0
- package/lib/typescript/src/elements/card-fields/fields.d.ts.map +1 -0
- package/lib/typescript/src/elements/card-fields/index.d.ts +21 -0
- package/lib/typescript/src/elements/card-fields/index.d.ts.map +1 -0
- package/lib/typescript/src/elements/card.d.ts +21 -0
- package/lib/typescript/src/elements/card.d.ts.map +1 -0
- package/lib/typescript/src/elements/email.d.ts +31 -0
- package/lib/typescript/src/elements/email.d.ts.map +1 -0
- package/lib/typescript/src/elements/payment/declared-fields.d.ts +43 -0
- package/lib/typescript/src/elements/payment/declared-fields.d.ts.map +1 -0
- package/lib/typescript/src/elements/payment/index.d.ts +37 -0
- package/lib/typescript/src/elements/payment/index.d.ts.map +1 -0
- package/lib/typescript/src/elements/payment/wallet.d.ts +39 -0
- package/lib/typescript/src/elements/payment/wallet.d.ts.map +1 -0
- package/lib/typescript/src/elements/tax-id.d.ts +41 -0
- package/lib/typescript/src/elements/tax-id.d.ts.map +1 -0
- package/lib/typescript/src/i18n/catalogs.d.ts +7 -0
- package/lib/typescript/src/i18n/catalogs.d.ts.map +1 -0
- package/lib/typescript/src/i18n/index.d.ts +17 -0
- package/lib/typescript/src/i18n/index.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +42 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/namespace.d.ts +36 -0
- package/lib/typescript/src/namespace.d.ts.map +1 -0
- package/lib/typescript/src/native/index.d.ts +62 -0
- package/lib/typescript/src/native/index.d.ts.map +1 -0
- package/lib/typescript/src/provider.d.ts +37 -0
- package/lib/typescript/src/provider.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeWhopAddressSuggestions.d.ts +13 -0
- package/lib/typescript/src/specs/NativeWhopAddressSuggestions.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeWhopAuthSession.d.ts +20 -0
- package/lib/typescript/src/specs/NativeWhopAuthSession.d.ts.map +1 -0
- package/lib/typescript/src/specs/NativeWhopPlatformPay.d.ts +24 -0
- package/lib/typescript/src/specs/NativeWhopPlatformPay.d.ts.map +1 -0
- package/lib/typescript/src/specs/WhopPayButtonNativeComponent.d.ts +21 -0
- package/lib/typescript/src/specs/WhopPayButtonNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/theme/appearance.d.ts +71 -0
- package/lib/typescript/src/theme/appearance.d.ts.map +1 -0
- package/lib/typescript/src/theme/provider.d.ts +11 -0
- package/lib/typescript/src/theme/provider.d.ts.map +1 -0
- package/lib/typescript/src/theme/resolve.d.ts +19 -0
- package/lib/typescript/src/theme/resolve.d.ts.map +1 -0
- package/lib/typescript/src/theme/scales.generated.d.ts +18 -0
- package/lib/typescript/src/theme/scales.generated.d.ts.map +1 -0
- package/lib/typescript/src/ui/element-frame.d.ts +32 -0
- package/lib/typescript/src/ui/element-frame.d.ts.map +1 -0
- package/lib/typescript/src/ui/payment-method-icon.d.ts +21 -0
- package/lib/typescript/src/ui/payment-method-icon.d.ts.map +1 -0
- package/lib/typescript/src/ui/primitives.d.ts +94 -0
- package/lib/typescript/src/ui/primitives.d.ts.map +1 -0
- package/lib/typescript/src/ui/select.d.ts +25 -0
- package/lib/typescript/src/ui/select.d.ts.map +1 -0
- package/lib/typescript/src/ui/suggestions.d.ts +15 -0
- package/lib/typescript/src/ui/suggestions.d.ts.map +1 -0
- package/lib/typescript/src/ui/whop-logo.d.ts +4 -0
- package/lib/typescript/src/ui/whop-logo.d.ts.map +1 -0
- package/package.json +121 -0
- package/scripts/check-swift.sh +27 -0
- package/scripts/codegen-shims.mjs +409 -0
- package/scripts/gen-country-names.mjs +58 -0
- package/scripts/gen-theme.mjs +124 -0
- package/scripts/gen-wordmark.mjs +68 -0
- package/scripts/mirror.mjs +112 -0
- package/src/api/client.ts +202 -0
- package/src/api/types.ts +166 -0
- package/src/controller/actions.ts +197 -0
- package/src/controller/store.ts +201 -0
- package/src/core/address/country-data.ts +927 -0
- package/src/core/address/country-name.ts +36 -0
- package/src/core/address/country-names.generated.ts +246 -0
- package/src/core/address/engine.ts +349 -0
- package/src/core/address/form-state.ts +479 -0
- package/src/core/address/places.ts +39 -0
- package/src/core/address/suggest.ts +153 -0
- package/src/core/address/use-suggestions.ts +37 -0
- package/src/core/payments/address-change.ts +121 -0
- package/src/core/payments/billing-merge.ts +91 -0
- package/src/core/payments/browser-info.ts +43 -0
- package/src/core/payments/bt.ts +176 -0
- package/src/core/payments/currency.ts +54 -0
- package/src/core/payments/detail.ts +204 -0
- package/src/core/payments/errors.ts +171 -0
- package/src/core/payments/mint.ts +130 -0
- package/src/core/payments/plan.ts +198 -0
- package/src/core/payments/refusals.ts +24 -0
- package/src/core/payments/secure-fields.ts +85 -0
- package/src/core/payments/tax-id-catalog.ts +320 -0
- package/src/core/payments/wallet-contact.ts +56 -0
- package/src/elements/address.tsx +570 -0
- package/src/elements/branding.tsx +87 -0
- package/src/elements/card-fields/context.tsx +310 -0
- package/src/elements/card-fields/fields.tsx +281 -0
- package/src/elements/card-fields/index.tsx +37 -0
- package/src/elements/card.tsx +101 -0
- package/src/elements/email.tsx +100 -0
- package/src/elements/payment/declared-fields.tsx +238 -0
- package/src/elements/payment/index.tsx +497 -0
- package/src/elements/payment/wallet.tsx +155 -0
- package/src/elements/tax-id.tsx +143 -0
- package/src/i18n/catalogs.ts +100 -0
- package/src/i18n/index.tsx +46 -0
- package/src/index.ts +68 -0
- package/src/namespace.tsx +231 -0
- package/src/native/index.ts +173 -0
- package/src/provider.tsx +76 -0
- package/src/specs/NativeWhopAddressSuggestions.ts +14 -0
- package/src/specs/NativeWhopAuthSession.ts +21 -0
- package/src/specs/NativeWhopPlatformPay.ts +25 -0
- package/src/specs/WhopPayButtonNativeComponent.ts +22 -0
- package/src/theme/appearance.ts +150 -0
- package/src/theme/provider.tsx +28 -0
- package/src/theme/resolve.ts +68 -0
- package/src/theme/scales.generated.ts +427 -0
- package/src/ui/element-frame.tsx +94 -0
- package/src/ui/payment-method-icon.tsx +55 -0
- package/src/ui/primitives.tsx +269 -0
- package/src/ui/select.tsx +255 -0
- package/src/ui/suggestions.tsx +69 -0
- package/src/ui/whop-logo.tsx +37 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { CardFieldsProvider, type CardFieldsChangePayload, useCardCollection } from './context';
|
|
4
|
+
|
|
5
|
+
export interface CardFieldsProps {
|
|
6
|
+
/** Advanced Basis Theory publishable key. Omit it to fetch the key automatically. */
|
|
7
|
+
publicKey?: string;
|
|
8
|
+
/** `stacked` shapes composed fields as a number row above expiry and cvc. `compact` is one row.
|
|
9
|
+
* Separately mounted fields ignore this and render with full borders and rounded corners. */
|
|
10
|
+
layout?: 'stacked' | 'compact';
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
onChange?: (payload: CardFieldsChangePayload) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Three separately mountable, PCI-isolated card fields for custom layouts. Place each field where
|
|
17
|
+
* the design wants it, enable the pay button from `onChange`, and confirm with
|
|
18
|
+
* `payments.createConfirmationToken()`.
|
|
19
|
+
*/
|
|
20
|
+
export function CardFields({ publicKey, layout = 'stacked', children, onChange }: CardFieldsProps) {
|
|
21
|
+
return (
|
|
22
|
+
<CardFieldsProvider publicKey={publicKey} layout={layout} onChange={onChange}>
|
|
23
|
+
<CardFieldsCollection />
|
|
24
|
+
{children}
|
|
25
|
+
</CardFieldsProvider>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Registration lives in a child so it runs under the provider it registers for. */
|
|
30
|
+
function CardFieldsCollection() {
|
|
31
|
+
useCardCollection('cardFields');
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export { CardCvcElement, CardExpiryElement, CardNumberElement } from './fields';
|
|
36
|
+
export type { CardFieldProps } from './fields';
|
|
37
|
+
export type { CardFieldsChangePayload } from './context';
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { type StyleProp, StyleSheet, View, type ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { useTheme } from '../theme/provider';
|
|
5
|
+
import { part } from '../ui/primitives';
|
|
6
|
+
import {
|
|
7
|
+
CardComposed,
|
|
8
|
+
type CardFieldsChangePayload,
|
|
9
|
+
CardFieldsProvider,
|
|
10
|
+
useCardCollection,
|
|
11
|
+
} from './card-fields/context';
|
|
12
|
+
import { CardCvcElement, CardExpiryElement, CardNumberElement } from './card-fields/fields';
|
|
13
|
+
|
|
14
|
+
export interface CardElementProps {
|
|
15
|
+
/** `stacked` places the number above expiry and cvc. `compact` places all three in one row. */
|
|
16
|
+
layout?: 'stacked' | 'compact';
|
|
17
|
+
style?: StyleProp<ViewStyle>;
|
|
18
|
+
fallback?: ReactNode;
|
|
19
|
+
onChange?: (payload: CardFieldsChangePayload) => void;
|
|
20
|
+
onReady?: () => void;
|
|
21
|
+
onError?: (error: { message: string; code?: string }) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Prearranged fields for card number, expiration, and security code. Card numbers stay inside the
|
|
26
|
+
* PCI-isolated Basis Theory components; this element only arranges them.
|
|
27
|
+
*/
|
|
28
|
+
export function CardElement({ layout = 'stacked', style, fallback, onChange, onReady, onError }: CardElementProps) {
|
|
29
|
+
return (
|
|
30
|
+
<CardFieldsProvider layout={layout} onChange={onChange}>
|
|
31
|
+
<CardComposed>
|
|
32
|
+
<CardCollection />
|
|
33
|
+
<CardGroup layout={layout} style={style} fallback={fallback} onReady={onReady} onError={onError} />
|
|
34
|
+
</CardComposed>
|
|
35
|
+
</CardFieldsProvider>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function CardCollection() {
|
|
40
|
+
useCardCollection('card');
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function CardGroup({
|
|
45
|
+
layout,
|
|
46
|
+
style,
|
|
47
|
+
fallback,
|
|
48
|
+
onReady,
|
|
49
|
+
onError,
|
|
50
|
+
}: {
|
|
51
|
+
layout: 'stacked' | 'compact';
|
|
52
|
+
style?: StyleProp<ViewStyle>;
|
|
53
|
+
fallback?: ReactNode;
|
|
54
|
+
onReady?: () => void;
|
|
55
|
+
onError?: (error: { message: string; code?: string }) => void;
|
|
56
|
+
}) {
|
|
57
|
+
const theme = useTheme();
|
|
58
|
+
|
|
59
|
+
// Every field draws its own rounded border, so the group needs a real gap between them: flush
|
|
60
|
+
// fields read as one mis-rendered box rather than three inputs.
|
|
61
|
+
const seam = { gap: theme.tokens.spacing * 2 };
|
|
62
|
+
|
|
63
|
+
// Only the number reports ready for the group; the other two settle with it, and reporting all
|
|
64
|
+
// three would make the grouped latch wait on three identical signals.
|
|
65
|
+
if (layout === 'compact') {
|
|
66
|
+
return (
|
|
67
|
+
<View style={[styles.row, seam, part(theme, 'whop-CardFieldGroup'), style]}>
|
|
68
|
+
<View style={styles.number}>
|
|
69
|
+
<CardNumberElement fallback={fallback} onReady={onReady} onError={onError} />
|
|
70
|
+
</View>
|
|
71
|
+
<View style={styles.short}>
|
|
72
|
+
<CardExpiryElement />
|
|
73
|
+
</View>
|
|
74
|
+
<View style={styles.short}>
|
|
75
|
+
<CardCvcElement />
|
|
76
|
+
</View>
|
|
77
|
+
</View>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<View style={[seam, part(theme, 'whop-CardFieldGroup'), style]}>
|
|
83
|
+
<CardNumberElement fallback={fallback} onReady={onReady} onError={onError} />
|
|
84
|
+
<View style={[styles.row, seam]}>
|
|
85
|
+
<View style={styles.grow}>
|
|
86
|
+
<CardExpiryElement />
|
|
87
|
+
</View>
|
|
88
|
+
<View style={styles.grow}>
|
|
89
|
+
<CardCvcElement />
|
|
90
|
+
</View>
|
|
91
|
+
</View>
|
|
92
|
+
</View>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const styles = StyleSheet.create({
|
|
97
|
+
row: { flexDirection: 'row' },
|
|
98
|
+
grow: { flex: 1 },
|
|
99
|
+
number: { flex: 2 },
|
|
100
|
+
short: { flex: 1 },
|
|
101
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { type ReactNode, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import type { StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { useT } from '../i18n';
|
|
5
|
+
import { usePaymentsStore } from '../namespace';
|
|
6
|
+
import { ElementFrame } from '../ui/element-frame';
|
|
7
|
+
import { Field, FieldSkeleton } from '../ui/primitives';
|
|
8
|
+
|
|
9
|
+
/** The same plausibility bar the web element applies — a shape check, never a deliverability one. */
|
|
10
|
+
const PLAUSIBLE = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
|
|
11
|
+
|
|
12
|
+
export interface EmailChangePayload {
|
|
13
|
+
email: string;
|
|
14
|
+
complete: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface EmailElementProps {
|
|
18
|
+
/** Seed value applied once at mount. */
|
|
19
|
+
defaultValue?: string;
|
|
20
|
+
/** Shows the localized label. Set false when supplying your own; the input keeps its a11y label. */
|
|
21
|
+
label?: boolean;
|
|
22
|
+
/** Empty renders `you@example.com`. */
|
|
23
|
+
placeholder?: string;
|
|
24
|
+
style?: StyleProp<ViewStyle>;
|
|
25
|
+
fallback?: ReactNode;
|
|
26
|
+
onChange?: (payload: EmailChangePayload) => void;
|
|
27
|
+
onReady?: () => void;
|
|
28
|
+
onError?: (error: { message: string; code?: string }) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Collects the buyer's email and feeds it to `createConfirmationToken()` while mounted. An explicit
|
|
33
|
+
* `billingDetails.email` still wins.
|
|
34
|
+
*
|
|
35
|
+
* Account recognition and code sign-in are not in this release, so no sign-in affordance renders —
|
|
36
|
+
* nothing appears that cannot work.
|
|
37
|
+
*/
|
|
38
|
+
export function EmailElement({
|
|
39
|
+
defaultValue = '',
|
|
40
|
+
label = true,
|
|
41
|
+
placeholder = '',
|
|
42
|
+
style,
|
|
43
|
+
fallback,
|
|
44
|
+
onChange,
|
|
45
|
+
onReady,
|
|
46
|
+
onError,
|
|
47
|
+
}: EmailElementProps) {
|
|
48
|
+
const t = useT();
|
|
49
|
+
const store = usePaymentsStore();
|
|
50
|
+
const [value, setValue] = useState(defaultValue);
|
|
51
|
+
const [touched, setTouched] = useState(false);
|
|
52
|
+
|
|
53
|
+
const latestChange = useRef(onChange);
|
|
54
|
+
latestChange.current = onChange;
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
const email = value.trim();
|
|
58
|
+
const complete = PLAUSIBLE.test(email);
|
|
59
|
+
// Only a plausible address counts as collected. Publishing every keystroke meant a half-typed
|
|
60
|
+
// address read as an email the form had: the wallet sheet stopped asking for one, and the
|
|
61
|
+
// mint sent the fragment.
|
|
62
|
+
store.patch({ buyerEmail: complete ? email : '' });
|
|
63
|
+
latestChange.current?.({ email, complete });
|
|
64
|
+
}, [value, store]);
|
|
65
|
+
|
|
66
|
+
// The value must not outlive the element. Left behind, an unmounted email element still answers
|
|
67
|
+
// for a form that no longer collects one.
|
|
68
|
+
useEffect(() => () => store.patch({ buyerEmail: '' }), [store]);
|
|
69
|
+
|
|
70
|
+
const invalid = touched && value.trim().length > 0 && !PLAUSIBLE.test(value.trim());
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<ElementFrame
|
|
74
|
+
style={style}
|
|
75
|
+
fallback={fallback}
|
|
76
|
+
skeleton={<FieldSkeleton label={label} />}
|
|
77
|
+
ready
|
|
78
|
+
onReady={onReady}
|
|
79
|
+
onError={onError}
|
|
80
|
+
>
|
|
81
|
+
<Field
|
|
82
|
+
label={label ? t('Email') : undefined}
|
|
83
|
+
accessibilityLabel={t('Email')}
|
|
84
|
+
value={value}
|
|
85
|
+
onChangeText={setValue}
|
|
86
|
+
onBlur={() => setTouched(true)}
|
|
87
|
+
placeholder={placeholder || 'you@example.com'}
|
|
88
|
+
error={invalid ? t('Enter a valid email address') : undefined}
|
|
89
|
+
keyboardType="email-address"
|
|
90
|
+
autoCapitalize="none"
|
|
91
|
+
autoCorrect={false}
|
|
92
|
+
autoComplete="email"
|
|
93
|
+
textContentType="username"
|
|
94
|
+
inputMode="email"
|
|
95
|
+
returnKeyType="next"
|
|
96
|
+
partName="whop-Input"
|
|
97
|
+
/>
|
|
98
|
+
</ElementFrame>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { type MutableRefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { TextElement, useBasisTheory } from '@basis-theory/react-native-elements';
|
|
3
|
+
|
|
4
|
+
import type { RequiredField } from '../../api/types';
|
|
5
|
+
import type { BasisTheoryElements } from '../../core/payments/bt';
|
|
6
|
+
import { fieldLabel, fieldMatches, type MatrixEntry, methodTokenizerKey } from '../../core/payments/detail';
|
|
7
|
+
import { tokenizeSecureFields } from '../../core/payments/secure-fields';
|
|
8
|
+
import { useT } from '../../i18n';
|
|
9
|
+
import { alphaStep } from '../../theme/provider';
|
|
10
|
+
import { Field, FieldShell, Stack, useFieldStyles } from '../../ui/primitives';
|
|
11
|
+
|
|
12
|
+
export interface DeclaredFieldValues {
|
|
13
|
+
/** Fields the method wants on its own category payload. */
|
|
14
|
+
paymentMethod: Record<string, string>;
|
|
15
|
+
/** Fields the method wants on `billing_details`. */
|
|
16
|
+
billingExtra: Record<string, string>;
|
|
17
|
+
/** One token intent standing in for every `secure: true` field, keyed by field name. */
|
|
18
|
+
secureTokenIntent: string | undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface DeclaredFieldsState {
|
|
22
|
+
fields: RequiredField[];
|
|
23
|
+
values: Record<string, string>;
|
|
24
|
+
setValue: (name: string, value: string) => void;
|
|
25
|
+
errorFor: (field: RequiredField) => 'required' | 'invalid' | undefined;
|
|
26
|
+
complete: boolean;
|
|
27
|
+
collect: () => Promise<DeclaredFieldValues>;
|
|
28
|
+
showErrors: boolean;
|
|
29
|
+
revealErrors: () => void;
|
|
30
|
+
secureRefs: MutableRefObject<Record<string, unknown>>;
|
|
31
|
+
/** The tokenizer key for this method's secure fields, if it declares any. */
|
|
32
|
+
tokenizerKey: string | undefined;
|
|
33
|
+
btRef: MutableRefObject<BasisTheoryElements | undefined>;
|
|
34
|
+
/** A hosted field's instance settles after its own first commit; this re-derives completeness
|
|
35
|
+
* when it does, since a ref assignment is invisible to React. */
|
|
36
|
+
noteSecureComplete: (key: string, complete: boolean) => void;
|
|
37
|
+
secureKey: (name: string) => string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A method's `required_fields`, rendered generically from what the matrix publishes.
|
|
42
|
+
*
|
|
43
|
+
* A `secure: true` field renders as a Basis Theory hosted text element, so the raw value never
|
|
44
|
+
* exists in this JavaScript. At collect time every secure instance is tokenized into ONE token
|
|
45
|
+
* intent keyed by field name, and the category payload carries `{ token_intent }` — the shape the
|
|
46
|
+
* web element produces and the backend already reads.
|
|
47
|
+
*/
|
|
48
|
+
export function useDeclaredFields(entry: MatrixEntry | undefined, country: string | undefined): DeclaredFieldsState {
|
|
49
|
+
const fields = useMemo(() => entry?.required_fields ?? [], [entry]);
|
|
50
|
+
// A method's secure fields tokenize under ITS OWN config-block key, which is a different Basis
|
|
51
|
+
// Theory application from the card one — the card key cannot mint token intents at all.
|
|
52
|
+
//
|
|
53
|
+
// The client is NOT constructed here: most methods declare no secure field, and constructing
|
|
54
|
+
// Basis Theory with a missing key makes its SDK show a "Please enter a valid API key" toast to
|
|
55
|
+
// the buyer. `SecureTokenizer` below mounts only when a secure field and a key both exist, and
|
|
56
|
+
// hands the instance back through this ref.
|
|
57
|
+
const btRef = useRef<BasisTheoryElements | undefined>(undefined);
|
|
58
|
+
const [values, setValues] = useState<Record<string, string>>({});
|
|
59
|
+
const [showErrors, setShowErrors] = useState(false);
|
|
60
|
+
const [secureComplete, setSecureComplete] = useState<Record<string, boolean>>({});
|
|
61
|
+
const secureRefs = useRef<Record<string, unknown>>({});
|
|
62
|
+
|
|
63
|
+
// Declared fields belong to the METHOD, and this state is keyed by field NAME — so two methods
|
|
64
|
+
// that both declare `bank_account` shared everything: a value typed for one, and its secure
|
|
65
|
+
// completeness, carried into the other and reported it ready before the buyer touched it.
|
|
66
|
+
const entryKey = entry?.type;
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
setValues({});
|
|
69
|
+
setShowErrors(false);
|
|
70
|
+
secureRefs.current = {};
|
|
71
|
+
}, [entryKey]);
|
|
72
|
+
|
|
73
|
+
const setValue = useCallback((name: string, value: string) => {
|
|
74
|
+
setValues((prev) => (prev[name] === value ? prev : { ...prev, [name]: value }));
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
const errorFor = useCallback(
|
|
78
|
+
(field: RequiredField): 'required' | 'invalid' | undefined => {
|
|
79
|
+
if (field.secure) return undefined;
|
|
80
|
+
const value = values[field.name] ?? '';
|
|
81
|
+
if (!value.trim()) return 'required';
|
|
82
|
+
// `input_formats` is what a buyer may TYPE; `formats` is the stored electronic value. The
|
|
83
|
+
// typing tolerance wins where the matrix publishes it.
|
|
84
|
+
const patterns = field.input_formats ?? field.formats;
|
|
85
|
+
return fieldMatches(patterns, country, value) ? undefined : 'invalid';
|
|
86
|
+
},
|
|
87
|
+
[values, country],
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const complete = useMemo(
|
|
91
|
+
() =>
|
|
92
|
+
// A MOUNTED tokenizer field is not a filled one. Keying off the ref meant completeness
|
|
93
|
+
// flipped true the moment the element attached and stayed true after the buyer cleared it,
|
|
94
|
+
// so the form reported itself ready to submit values that were never entered.
|
|
95
|
+
fields.every((field) =>
|
|
96
|
+
field.secure ? secureComplete[`${entryKey ?? ''}:${field.name}`] === true : !errorFor(field),
|
|
97
|
+
),
|
|
98
|
+
[fields, errorFor, secureComplete, entryKey],
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const collect = useCallback(async (): Promise<DeclaredFieldValues> => {
|
|
102
|
+
const paymentMethod: Record<string, string> = {};
|
|
103
|
+
const billingExtra: Record<string, string> = {};
|
|
104
|
+
const secure: Record<string, unknown> = {};
|
|
105
|
+
|
|
106
|
+
for (const field of fields) {
|
|
107
|
+
if (field.secure) {
|
|
108
|
+
const instance = secureRefs.current[field.name];
|
|
109
|
+
if (instance) secure[field.name] = instance;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const value = (values[field.name] ?? '').trim();
|
|
113
|
+
if (field.kind === 'billing_details') billingExtra[field.name] = value;
|
|
114
|
+
else paymentMethod[field.name] = value;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let secureTokenIntent: string | undefined;
|
|
118
|
+
if (Object.keys(secure).length > 0) {
|
|
119
|
+
const key = entry ? methodTokenizerKey(entry) : undefined;
|
|
120
|
+
if (!key) throw new Error('This payment method is missing its tokenizer key');
|
|
121
|
+
// The values live inside the hosted elements, so the SDK has to interpolate them. A raw
|
|
122
|
+
// REST post would serialize the element objects instead of what the buyer typed — that
|
|
123
|
+
// path exists only for the wallet sheets, whose payload really is plain data.
|
|
124
|
+
secureTokenIntent = await tokenizeSecureFields(btRef.current, secure, key);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return { paymentMethod, billingExtra, secureTokenIntent };
|
|
128
|
+
}, [fields, values, entry]);
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
fields,
|
|
132
|
+
values,
|
|
133
|
+
setValue,
|
|
134
|
+
errorFor,
|
|
135
|
+
complete,
|
|
136
|
+
collect,
|
|
137
|
+
showErrors,
|
|
138
|
+
revealErrors: () => setShowErrors(true),
|
|
139
|
+
secureRefs,
|
|
140
|
+
tokenizerKey: fields.some((field) => field.secure) && entry ? methodTokenizerKey(entry) : undefined,
|
|
141
|
+
btRef,
|
|
142
|
+
noteSecureComplete: (key: string, value: boolean) =>
|
|
143
|
+
setSecureComplete((previous) => (previous[key] === value ? previous : { ...previous, [key]: value })),
|
|
144
|
+
secureKey: (name: string) => `${entryKey ?? ''}:${name}`,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function DeclaredFields({ state }: { state: DeclaredFieldsState }) {
|
|
149
|
+
const t = useT();
|
|
150
|
+
|
|
151
|
+
if (state.fields.length === 0) return null;
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<Stack>
|
|
155
|
+
{state.tokenizerKey ? <SecureTokenizer publicKey={state.tokenizerKey} btRef={state.btRef} /> : null}
|
|
156
|
+
{state.fields.map((field) => {
|
|
157
|
+
const label = fieldLabel(field.name);
|
|
158
|
+
const code = state.showErrors ? state.errorFor(field) : undefined;
|
|
159
|
+
const error =
|
|
160
|
+
code === 'required' ? t('Required') : code === 'invalid' ? t("That doesn't look right") : undefined;
|
|
161
|
+
|
|
162
|
+
if (field.secure) {
|
|
163
|
+
return <SecureField key={field.name} field={field} label={label} state={state} />;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<Field
|
|
168
|
+
key={field.name}
|
|
169
|
+
label={label}
|
|
170
|
+
accessibilityLabel={label}
|
|
171
|
+
value={state.values[field.name] ?? ''}
|
|
172
|
+
onChangeText={(value) => state.setValue(field.name, value)}
|
|
173
|
+
placeholder={field.placeholder}
|
|
174
|
+
error={error}
|
|
175
|
+
autoCapitalize="characters"
|
|
176
|
+
autoCorrect={false}
|
|
177
|
+
/>
|
|
178
|
+
);
|
|
179
|
+
})}
|
|
180
|
+
</Stack>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Constructs the Basis Theory client for a method that actually declares a secure field. */
|
|
185
|
+
function SecureTokenizer({
|
|
186
|
+
publicKey,
|
|
187
|
+
btRef,
|
|
188
|
+
}: {
|
|
189
|
+
publicKey: string;
|
|
190
|
+
btRef: MutableRefObject<BasisTheoryElements | undefined>;
|
|
191
|
+
}) {
|
|
192
|
+
const { bt } = useBasisTheory(publicKey);
|
|
193
|
+
btRef.current = bt;
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function SecureField({
|
|
198
|
+
field,
|
|
199
|
+
label,
|
|
200
|
+
state,
|
|
201
|
+
}: {
|
|
202
|
+
field: RequiredField;
|
|
203
|
+
label: string;
|
|
204
|
+
state: DeclaredFieldsState;
|
|
205
|
+
}) {
|
|
206
|
+
const s = useFieldStyles();
|
|
207
|
+
const [focused, setFocused] = useState(false);
|
|
208
|
+
const note = state.noteSecureComplete;
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<FieldShell label={label} focused={focused}>
|
|
212
|
+
<TextElement
|
|
213
|
+
btRef={
|
|
214
|
+
((instance: unknown) => {
|
|
215
|
+
state.secureRefs.current[field.name] = instance;
|
|
216
|
+
if (!instance) note(state.secureKey(field.name), false);
|
|
217
|
+
}) as never
|
|
218
|
+
}
|
|
219
|
+
onChange={
|
|
220
|
+
((event: { complete?: boolean }) => note(state.secureKey(field.name), event?.complete === true)) as never
|
|
221
|
+
}
|
|
222
|
+
placeholder={field.placeholder}
|
|
223
|
+
placeholderTextColor={alphaStep(s.theme.gray, 10)}
|
|
224
|
+
style={
|
|
225
|
+
{
|
|
226
|
+
color: s.input.color,
|
|
227
|
+
fontSize: s.input.fontSize,
|
|
228
|
+
fontFamily: s.input.fontFamily,
|
|
229
|
+
height: s.theme.tokens.controlHeight - 2,
|
|
230
|
+
padding: 0,
|
|
231
|
+
} as never
|
|
232
|
+
}
|
|
233
|
+
onFocus={() => setFocused(true)}
|
|
234
|
+
onBlur={() => setFocused(false)}
|
|
235
|
+
/>
|
|
236
|
+
</FieldShell>
|
|
237
|
+
);
|
|
238
|
+
}
|