@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
package/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# @whop/elements-react-native
|
|
2
|
+
|
|
3
|
+
Native React Native components for collecting payments with Whop.
|
|
4
|
+
|
|
5
|
+
No WebViews. The elements are real React Native views, card fields are PCI-isolated by Basis
|
|
6
|
+
Theory's React Native SDK, and the pieces that only the platform can do — the Apple Pay and Google
|
|
7
|
+
Pay sheets, their branded buttons, and the system browser a 3DS step opens in — are Swift and Kotlin
|
|
8
|
+
behind React Native Codegen.
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
import {
|
|
12
|
+
WhopElements, Payments, PaymentElement, AddressElement, EmailElement, BrandingElement,
|
|
13
|
+
} from '@whop/elements-react-native';
|
|
14
|
+
|
|
15
|
+
<WhopElements environment="production">
|
|
16
|
+
<Payments ref={payments} accountId="biz_…" plan="plan_…">
|
|
17
|
+
<EmailElement />
|
|
18
|
+
<AddressElement />
|
|
19
|
+
<PaymentElement onChange={(e) => setReady(e.complete)} />
|
|
20
|
+
<BrandingElement />
|
|
21
|
+
</Payments>
|
|
22
|
+
</WhopElements>;
|
|
23
|
+
|
|
24
|
+
const { confirmationToken } = await payments.current.createConfirmationToken();
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Confirm the token server-side, then hand any pending step to
|
|
28
|
+
`payments.current.handleNextAction({ clientSecret })`.
|
|
29
|
+
|
|
30
|
+
## Requirements
|
|
31
|
+
|
|
32
|
+
| | |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| React Native | `>= 0.87`, New Architecture on |
|
|
35
|
+
| iOS | 15.1+, **Swift Package Manager** — this package ships no podspec |
|
|
36
|
+
| Android | minSdk 24, compileSdk 37 |
|
|
37
|
+
| Peers | `react` and `react-native` only — no native peer dependencies |
|
|
38
|
+
|
|
39
|
+
## Elements
|
|
40
|
+
|
|
41
|
+
| Component | What it collects |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| `<PaymentElement />` | The offered payment methods and the selected one's required fields |
|
|
44
|
+
| `<AddressElement />` | A postal address, in the selected country's own format |
|
|
45
|
+
| `<CardElement />` | Card number, expiry, and security code, prearranged |
|
|
46
|
+
| `<CardFields>` + `<CardNumberElement />` `<CardExpiryElement />` `<CardCvcElement />` | The same three fields, placed individually |
|
|
47
|
+
| `<EmailElement />` | The buyer's email |
|
|
48
|
+
| `<TaxIdElement />` | A business tax registration |
|
|
49
|
+
| `<BrandingElement />` | Whop's merchant-of-record notice |
|
|
50
|
+
|
|
51
|
+
`<BrandingElement />` is required beside a payment surface: Whop is the merchant of record on these
|
|
52
|
+
sales, so `createConfirmationToken` refuses with `BRANDING_NOT_MOUNTED` without it.
|
|
53
|
+
|
|
54
|
+
`<AddressElement />` autocompletes by default from the **platform geocoder**, `MKLocalSearchCompleter`
|
|
55
|
+
on iOS and `android.location.Geocoder` on Android, so it needs no API key, no account, no location
|
|
56
|
+
permission and no extra dependency. Android resolves a complete address rather than predicting from a
|
|
57
|
+
fragment, so expect fewer and coarser suggestions there. A geocoder with no matches shows nothing and
|
|
58
|
+
the buyer types the address by hand, which is where they started: autocomplete never blocks a
|
|
59
|
+
checkout. Pass `autocomplete={false}` to turn it off.
|
|
60
|
+
|
|
61
|
+
## `<Payments>`
|
|
62
|
+
|
|
63
|
+
| Prop | |
|
|
64
|
+
| --- | --- |
|
|
65
|
+
| `accountId` | The Whop company the sale belongs to |
|
|
66
|
+
| `plan` | The plan being sold, which resolves the currency, amount and offered methods. Pass `currency` and `amount` instead for an ad-hoc charge |
|
|
67
|
+
| `returnUrl` | Where the issuer returns after a redirect or 3DS step. Must be **https** and hosted by you, because the API refuses anything else, so a custom app scheme will not work. You do not register a deep link: `handleNextAction` polls the payment to rest and closes the browser itself |
|
|
68
|
+
| `applePayMerchantId` | Overrides the merchant identifier from the account's own Apple Pay registration. The Apple Pay tile is hidden only when neither exists |
|
|
69
|
+
| `googlePayMerchantName` | The name shown in the Google Pay sheet, defaulting to `Whop`. Google Pay needs nothing else from you |
|
|
70
|
+
| `order` | The method tile order, with Stripe's `paymentMethodOrder` semantics |
|
|
71
|
+
|
|
72
|
+
## Appearance
|
|
73
|
+
|
|
74
|
+
One object, applied live — mounted elements restyle in place.
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
<WhopElements
|
|
78
|
+
appearance={{
|
|
79
|
+
theme: { appearance: 'dark', accentColor: 'blue' },
|
|
80
|
+
tokens: { radius: 12 },
|
|
81
|
+
parts: { 'whop-CardNumberField': { borderWidth: 2 } },
|
|
82
|
+
}}
|
|
83
|
+
>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`theme` is identical to the web elements'. The two deltas are deliberate, because React Native has
|
|
87
|
+
no CSS:
|
|
88
|
+
|
|
89
|
+
- `tokens` replaces `variables` — a closed, typed map of **numbers**, not `"8px"` strings.
|
|
90
|
+
- `parts` replaces `classes` — the **same** `whop-*` part names, with React Native style objects
|
|
91
|
+
instead of CSS declarations. Flat per-part overrides: no cascade, no pseudo-classes.
|
|
92
|
+
|
|
93
|
+
Passing `variables` or `classes` throws by name rather than being silently ignored.
|
|
94
|
+
|
|
95
|
+
An unset `theme.appearance` follows the device's colour scheme.
|
|
96
|
+
|
|
97
|
+
## Example app
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
cd example
|
|
101
|
+
pnpm install
|
|
102
|
+
pnpm run setup # generates ios/ and android/, converts iOS to SwiftPM
|
|
103
|
+
pnpm run ios # or: pnpm run android
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Set your `accountId` and `plan` in `example/src/config.ts` first. It runs against
|
|
107
|
+
`production` by default — switch `environment` to `sandbox` there to point at the sandbox API.
|
|
108
|
+
|
|
109
|
+
`ios/` and `android/` are generated by `pnpm run setup` from the official React Native app template
|
|
110
|
+
rather than committed: they are several hundred files that belong to a React Native version, and
|
|
111
|
+
regenerating is how the example follows a version bump instead of drifting from it. The script also
|
|
112
|
+
converts the project to Swift Package Manager without ever invoking `pod` — it strips CocoaPods out
|
|
113
|
+
of the `.pbxproj` directly, because `react-native spm add --deintegrate` shells out to it.
|
|
114
|
+
|
|
115
|
+
One deliberate leftover: `ios/Podfile` stays as a pod-free stub. The React Native CLI locates the
|
|
116
|
+
iOS project by finding a Podfile, and `react-native spm` reads that location, so deleting it makes
|
|
117
|
+
`project.ios` resolve to `null` and the SPM setup fail. The example has three
|
|
118
|
+
screens: a realistic **Checkout**, a **Gallery** of each element with live prop controls, and
|
|
119
|
+
**Theming** applied to a live form.
|
|
120
|
+
|
|
121
|
+
The example is its own pnpm install root. React Native's `settings.gradle` does
|
|
122
|
+
`includeBuild("../node_modules/@react-native/gradle-plugin")`, which pnpm's isolated linker does not
|
|
123
|
+
expose, so `example/.npmrc` sets `node-linker=hoisted` — scoped there so the rest of the monorepo
|
|
124
|
+
stays isolated.
|
|
125
|
+
|
|
126
|
+
## Native layer
|
|
127
|
+
|
|
128
|
+
Three units, all generated from `src/specs/` by React Native Codegen:
|
|
129
|
+
|
|
130
|
+
| Unit | Kind | iOS | Android |
|
|
131
|
+
| --- | --- | --- | --- |
|
|
132
|
+
| `WhopAuthSession` | Turbo Module | `ASWebAuthenticationSession` | Chrome Custom Tabs |
|
|
133
|
+
| `WhopPlatformPay` | Turbo Module | `PKPaymentAuthorizationController` | `PaymentsClient` |
|
|
134
|
+
| `WhopPayButton` | Fabric Component | `PKPaymentButton` | `PayButton` |
|
|
135
|
+
|
|
136
|
+
A redirect or 3DS step opens in the **system browser**, out of process — not a WebView the app can
|
|
137
|
+
read or script. `handleNextAction` then polls the payment to rest, because the API accepts only
|
|
138
|
+
https or loopback return URLs and a custom-scheme deep link back into the app is refused.
|
|
139
|
+
|
|
140
|
+
React Native cannot express a Fabric component or a Turbo Module in pure Swift: both need an
|
|
141
|
+
Objective-C++ class the C++ renderer can see. Nobody writes it here. `scripts/codegen-shims.mjs`
|
|
142
|
+
emits it from the same specs Codegen reads, into the gitignored `ios/generated/`, during `prepare`.
|
|
143
|
+
Every line a human writes on iOS is Swift and on Android is Kotlin.
|
|
144
|
+
|
|
145
|
+
The SDK has **no native peer dependencies**, deliberately: under Swift Package Manager, one peer
|
|
146
|
+
that ships no `Package.swift` makes the whole app unbuildable, and most of the React Native
|
|
147
|
+
ecosystem does not ship one yet. That is why the branding wordmark is drawn with plain views rather
|
|
148
|
+
than `react-native-svg`.
|
|
149
|
+
|
|
150
|
+
## Not in this release
|
|
151
|
+
|
|
152
|
+
Absent entirely rather than half-present, so nothing renders that cannot work: saved payment
|
|
153
|
+
methods, the email code sign-in, installment tiers, ledger-balance payment, and the post-confirm
|
|
154
|
+
instruction panes.
|
|
155
|
+
|
|
156
|
+
## Development
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
pnpm install
|
|
160
|
+
pnpm build # emits ios/generated, then module + typescript targets
|
|
161
|
+
pnpm test
|
|
162
|
+
pnpm check-types
|
|
163
|
+
pnpm theme # regenerate src/theme/scales.generated.ts from frosted-ui's stylesheet
|
|
164
|
+
pnpm mirror # which files here mirror the web elements changed on this branch
|
|
165
|
+
pnpm check-swift # typecheck the Swift against the iOS SDK, no app build
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Nothing in `ios/Sources` imports React, so the whole Swift implementation typechecks standalone —
|
|
169
|
+
a wrong PassKit or AuthenticationServices symbol surfaces in seconds rather than one at a time
|
|
170
|
+
inside an Xcode build.
|
|
171
|
+
|
|
172
|
+
`pnpm mirror` answers the question a web author cannot otherwise ask, which files here implement the
|
|
173
|
+
same behaviour as the web elements they just changed. `scripts/mirror.mjs` derives the ported files
|
|
174
|
+
from their own headers and keeps an explicit table for the element components; `pnpm mirror:validate`
|
|
175
|
+
fails if either side of a pair has moved. Add a row when you add an element.
|
|
176
|
+
|
|
177
|
+
`src/core/` is ported verbatim from `frontend/apps/web/src/elements/`. Each file names its source in
|
|
178
|
+
a header, and the address engine's own test suite is ported with it, so the port is proved against
|
|
179
|
+
the same assertions the web elements pass.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.WhopElements = [
|
|
3
|
+
kotlinVersion: "2.2.0",
|
|
4
|
+
minSdkVersion: 24,
|
|
5
|
+
compileSdkVersion: 37,
|
|
6
|
+
buildToolsVersion: "37.0.0",
|
|
7
|
+
ndkVersion: "27.1.12297006",
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
ext.getExtOrDefault = { prop ->
|
|
11
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : WhopElements[prop]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
repositories {
|
|
15
|
+
google()
|
|
16
|
+
mavenCentral()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
dependencies {
|
|
20
|
+
classpath "com.android.tools.build:gradle:9.0.0"
|
|
21
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
apply plugin: "com.android.library"
|
|
26
|
+
apply plugin: "kotlin-android"
|
|
27
|
+
apply plugin: "com.facebook.react"
|
|
28
|
+
|
|
29
|
+
android {
|
|
30
|
+
namespace "com.whop.elements"
|
|
31
|
+
compileSdkVersion getExtOrDefault("compileSdkVersion")
|
|
32
|
+
buildToolsVersion getExtOrDefault("buildToolsVersion")
|
|
33
|
+
|
|
34
|
+
defaultConfig {
|
|
35
|
+
minSdkVersion getExtOrDefault("minSdkVersion")
|
|
36
|
+
consumerProguardFiles "consumer-rules.pro"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
compileOptions {
|
|
40
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
41
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
kotlinOptions {
|
|
45
|
+
jvmTarget = "17"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
sourceSets {
|
|
49
|
+
main {
|
|
50
|
+
java.srcDirs += "src/main/java"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
repositories {
|
|
56
|
+
google()
|
|
57
|
+
mavenCentral()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
dependencies {
|
|
61
|
+
implementation "com.facebook.react:react-android"
|
|
62
|
+
implementation "androidx.browser:browser:1.8.0"
|
|
63
|
+
implementation "com.google.android.gms:play-services-wallet:19.4.0"
|
|
64
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
package com.whop.elements
|
|
2
|
+
|
|
3
|
+
import android.location.Address
|
|
4
|
+
import android.location.Geocoder
|
|
5
|
+
import com.facebook.react.bridge.Promise
|
|
6
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
7
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
8
|
+
import java.util.concurrent.Executors
|
|
9
|
+
import org.json.JSONArray
|
|
10
|
+
import org.json.JSONObject
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Street suggestions, through the platform geocoder.
|
|
14
|
+
*
|
|
15
|
+
* `android.location.Geocoder` rather than the Google Places SDK: Places adds ~8 MB of dex that R8
|
|
16
|
+
* cannot shrink, merges ACCESS_FINE_LOCATION and three more permissions into every consuming app,
|
|
17
|
+
* and holds its API key in one process-global that the host app and this SDK would silently
|
|
18
|
+
* overwrite for each other. Geocoder is in the framework — no dependency, no key, no permission,
|
|
19
|
+
* no manifest entry, and nothing for a consumer to provision.
|
|
20
|
+
*
|
|
21
|
+
* Unlike MapKit, one geocode already returns full components for every candidate, so `resolve`
|
|
22
|
+
* reads the row this module cached rather than making a second call. The two-step shape is kept
|
|
23
|
+
* because the spec is shared with iOS, where it is not optional.
|
|
24
|
+
*/
|
|
25
|
+
@ReactModule(name = WhopAddressSuggestionsModule.NAME)
|
|
26
|
+
class WhopAddressSuggestionsModule(reactContext: ReactApplicationContext) :
|
|
27
|
+
NativeWhopAddressSuggestionsSpec(reactContext) {
|
|
28
|
+
|
|
29
|
+
private val executor = Executors.newSingleThreadExecutor()
|
|
30
|
+
|
|
31
|
+
/** Content-keyed like the iOS port, so a resolve that lands after a newer query still finds its row. */
|
|
32
|
+
private val cache = object : LinkedHashMap<String, Address>(16, 0.75f, true) {
|
|
33
|
+
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<String, Address>?): Boolean = size > CACHE_LIMIT
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Not every device ships a geocoder backend, and a device without one answers nothing forever. */
|
|
37
|
+
override fun isAvailable(): Boolean = Geocoder.isPresent()
|
|
38
|
+
|
|
39
|
+
override fun suggest(query: String?, promise: Promise) {
|
|
40
|
+
val trimmed = query?.trim().orEmpty()
|
|
41
|
+
if (trimmed.length < MIN_QUERY_LENGTH) {
|
|
42
|
+
promise.resolve(EMPTY)
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
executor.execute {
|
|
46
|
+
// The blocking overload deliberately: the GeocodeListener form is API 33+, this module ships
|
|
47
|
+
// to minSdk 24, and one code path off the main thread beats two guarded by a version check.
|
|
48
|
+
@Suppress("DEPRECATION")
|
|
49
|
+
val results = runCatching { Geocoder(reactApplicationContext).getFromLocationName(trimmed, RESULT_LIMIT) }
|
|
50
|
+
.getOrNull()
|
|
51
|
+
.orEmpty()
|
|
52
|
+
|
|
53
|
+
val rows = JSONArray()
|
|
54
|
+
for (address in results) {
|
|
55
|
+
val label = label(address) ?: continue
|
|
56
|
+
synchronized(cache) { cache[label] = address }
|
|
57
|
+
rows.put(JSONObject().put("id", label).put("label", label))
|
|
58
|
+
}
|
|
59
|
+
promise.resolve(rows.toString())
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
override fun details(suggestionId: String?, promise: Promise) {
|
|
64
|
+
val address = suggestionId?.let { key -> synchronized(cache) { cache[key] } }
|
|
65
|
+
if (address == null) {
|
|
66
|
+
promise.resolve(EMPTY_OBJECT)
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
promise.resolve(components(address).toString())
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The one-line form Geocoder already formats, falling back to the components it did return. */
|
|
73
|
+
private fun label(address: Address): String? {
|
|
74
|
+
val line = runCatching { address.getAddressLine(0) }.getOrNull()
|
|
75
|
+
if (!line.isNullOrBlank()) return line
|
|
76
|
+
val parts = listOfNotNull(
|
|
77
|
+
listOfNotNull(address.subThoroughfare, address.thoroughfare).joinToString(" ").ifBlank { null },
|
|
78
|
+
address.locality,
|
|
79
|
+
address.adminArea,
|
|
80
|
+
)
|
|
81
|
+
return parts.joinToString(", ").ifBlank { null }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Raw components only. `line1` composition is per-country and lives on the JS side. */
|
|
85
|
+
private fun components(address: Address): JSONObject =
|
|
86
|
+
JSONObject()
|
|
87
|
+
.put("streetNumber", address.subThoroughfare.orEmpty())
|
|
88
|
+
.put("street", address.thoroughfare.orEmpty())
|
|
89
|
+
.put("city", address.locality.orEmpty())
|
|
90
|
+
.put("state", address.adminArea.orEmpty())
|
|
91
|
+
.put("postalCode", address.postalCode.orEmpty())
|
|
92
|
+
.put("country", address.countryCode?.uppercase().orEmpty())
|
|
93
|
+
|
|
94
|
+
companion object {
|
|
95
|
+
const val NAME = "WhopAddressSuggestions"
|
|
96
|
+
private const val MIN_QUERY_LENGTH = 3
|
|
97
|
+
private const val RESULT_LIMIT = 5
|
|
98
|
+
private const val CACHE_LIMIT = 100
|
|
99
|
+
private const val EMPTY = "[]"
|
|
100
|
+
private const val EMPTY_OBJECT = "{}"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
package com.whop.elements
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import android.net.Uri
|
|
6
|
+
import androidx.browser.customtabs.CustomTabsIntent
|
|
7
|
+
import com.facebook.react.bridge.ActivityEventListener
|
|
8
|
+
import com.facebook.react.bridge.LifecycleEventListener
|
|
9
|
+
import com.facebook.react.bridge.Promise
|
|
10
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
11
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The redirect and 3DS surface.
|
|
15
|
+
*
|
|
16
|
+
* A Custom Tab is Chrome rendering the page in its own process, so the host app can neither read the
|
|
17
|
+
* issuer's 3DS challenge nor script it. That is the difference between this and the `WebView` +
|
|
18
|
+
* `javaScriptEnabled` pattern, and it is the whole reason this module exists.
|
|
19
|
+
*/
|
|
20
|
+
@ReactModule(name = WhopAuthSessionModule.NAME)
|
|
21
|
+
class WhopAuthSessionModule(reactContext: ReactApplicationContext) :
|
|
22
|
+
NativeWhopAuthSessionSpec(reactContext), ActivityEventListener, LifecycleEventListener {
|
|
23
|
+
|
|
24
|
+
private var pending: Promise? = null
|
|
25
|
+
private var browserTookOver = false
|
|
26
|
+
|
|
27
|
+
init {
|
|
28
|
+
reactContext.addActivityEventListener(this)
|
|
29
|
+
reactContext.addLifecycleEventListener(this)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
override fun getName(): String = NAME
|
|
33
|
+
|
|
34
|
+
override fun isAvailable(): Boolean = true
|
|
35
|
+
|
|
36
|
+
override fun present(url: String?, callbackScheme: String?, prefersEphemeral: Boolean, promise: Promise) {
|
|
37
|
+
val target = url?.let(Uri::parse)
|
|
38
|
+
if (target == null || !target.scheme.equals("https", ignoreCase = true)) {
|
|
39
|
+
promise.reject("INVALID_URL", "whop elements: an https URL is required")
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
val activity: Activity? = reactApplicationContext.currentActivity
|
|
43
|
+
if (activity == null) {
|
|
44
|
+
promise.reject("NO_ACTIVITY", "whop elements: no activity is attached")
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
settle(null)
|
|
49
|
+
pending = promise
|
|
50
|
+
browserTookOver = false
|
|
51
|
+
|
|
52
|
+
val intent = CustomTabsIntent.Builder()
|
|
53
|
+
.setShowTitle(true)
|
|
54
|
+
.setUrlBarHidingEnabled(true)
|
|
55
|
+
.build()
|
|
56
|
+
if (prefersEphemeral) {
|
|
57
|
+
intent.intent.putExtra("com.android.browser.incognito", true)
|
|
58
|
+
}
|
|
59
|
+
intent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
|
|
60
|
+
runCatching { intent.launchUrl(activity, target) }
|
|
61
|
+
.onFailure {
|
|
62
|
+
pending = null
|
|
63
|
+
promise.reject("BROWSER_UNAVAILABLE", "whop elements: no browser could present the page")
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* A Custom Tab cannot be closed programmatically the way ASWebAuthenticationSession can, so
|
|
69
|
+
* settling the promise alone left the buyer looking at the return URL in Chrome while the app's
|
|
70
|
+
* result screen sat hidden behind it. The tab is an activity in this task, so relaunching the
|
|
71
|
+
* host activity on top of it pops it — REORDER_TO_FRONT alone would not, since the tab is above
|
|
72
|
+
* us and CLEAR_TOP is what removes it.
|
|
73
|
+
*/
|
|
74
|
+
override fun dismiss() {
|
|
75
|
+
settle(null)
|
|
76
|
+
val activity = reactApplicationContext.currentActivity ?: return
|
|
77
|
+
activity.startActivity(
|
|
78
|
+
Intent(activity, activity.javaClass).addFlags(
|
|
79
|
+
Intent.FLAG_ACTIVITY_REORDER_TO_FRONT or
|
|
80
|
+
Intent.FLAG_ACTIVITY_CLEAR_TOP or
|
|
81
|
+
Intent.FLAG_ACTIVITY_SINGLE_TOP,
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* A Custom Tab has no completion callback: the app simply comes back to the foreground. So the
|
|
88
|
+
* signal is a PAUSE followed by a RESUME — launching the tab pauses this activity, and coming
|
|
89
|
+
* back resumes it. Counting resumes instead would never settle, because the launch produces no
|
|
90
|
+
* resume of its own to skip.
|
|
91
|
+
*
|
|
92
|
+
* Resolving null is correct: a dismissal is an outcome, not a failure, and handleNextAction's
|
|
93
|
+
* poll is the authority on what actually happened to the payment. A completed redirect arrives
|
|
94
|
+
* as `onNewIntent` first and settles with the URL.
|
|
95
|
+
*/
|
|
96
|
+
override fun onHostResume() {
|
|
97
|
+
if (pending == null || !browserTookOver) return
|
|
98
|
+
settle(null)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
override fun onHostPause() {
|
|
102
|
+
if (pending != null) browserTookOver = true
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
override fun onHostDestroy() {
|
|
106
|
+
settle(null)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
override fun onNewIntent(intent: Intent) {
|
|
110
|
+
val data = intent.data ?: return
|
|
111
|
+
settle(data.toString())
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) = Unit
|
|
115
|
+
|
|
116
|
+
private fun settle(value: String?) {
|
|
117
|
+
val promise = pending ?: return
|
|
118
|
+
pending = null
|
|
119
|
+
browserTookOver = false
|
|
120
|
+
promise.resolve(value)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
companion object {
|
|
124
|
+
const val NAME = "WhopAuthSession"
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
package com.whop.elements
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
+
import com.facebook.react.uimanager.ViewManager
|
|
9
|
+
|
|
10
|
+
class WhopElementsPackage : BaseReactPackage() {
|
|
11
|
+
|
|
12
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? =
|
|
13
|
+
when (name) {
|
|
14
|
+
WhopAuthSessionModule.NAME -> WhopAuthSessionModule(reactContext)
|
|
15
|
+
WhopPlatformPayModule.NAME -> WhopPlatformPayModule(reactContext)
|
|
16
|
+
WhopAddressSuggestionsModule.NAME -> WhopAddressSuggestionsModule(reactContext)
|
|
17
|
+
else -> null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> =
|
|
21
|
+
listOf(WhopPayButtonManager())
|
|
22
|
+
|
|
23
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider {
|
|
24
|
+
listOf(
|
|
25
|
+
WhopAuthSessionModule.NAME,
|
|
26
|
+
WhopPlatformPayModule.NAME,
|
|
27
|
+
WhopAddressSuggestionsModule.NAME,
|
|
28
|
+
).associateWith { name ->
|
|
29
|
+
ReactModuleInfo(
|
|
30
|
+
name,
|
|
31
|
+
name,
|
|
32
|
+
false,
|
|
33
|
+
false,
|
|
34
|
+
false,
|
|
35
|
+
true,
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
package com.whop.elements
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
4
|
+
import com.facebook.react.uimanager.SimpleViewManager
|
|
5
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
6
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
7
|
+
import com.facebook.react.uimanager.common.UIManagerType
|
|
8
|
+
import com.facebook.react.uimanager.ViewManagerDelegate
|
|
9
|
+
import com.facebook.react.uimanager.annotations.ReactProp
|
|
10
|
+
import com.facebook.react.viewmanagers.WhopPayButtonManagerDelegate
|
|
11
|
+
import com.facebook.react.viewmanagers.WhopPayButtonManagerInterface
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Google Pay's brand guidelines require the supplied control rather than a drawn imitation, which is
|
|
15
|
+
* why this is the one element surface that is a native view on Android.
|
|
16
|
+
*
|
|
17
|
+
* The interface and delegate this implements are Codegen output from
|
|
18
|
+
* `src/specs/WhopPayButtonNativeComponent.ts` — nothing here is hand-written Java.
|
|
19
|
+
*/
|
|
20
|
+
@ReactModule(name = WhopPayButtonManager.NAME)
|
|
21
|
+
class WhopPayButtonManager :
|
|
22
|
+
SimpleViewManager<WhopPayButtonView>(), WhopPayButtonManagerInterface<WhopPayButtonView> {
|
|
23
|
+
|
|
24
|
+
private val delegate: ViewManagerDelegate<WhopPayButtonView> = WhopPayButtonManagerDelegate(this)
|
|
25
|
+
|
|
26
|
+
override fun getDelegate(): ViewManagerDelegate<WhopPayButtonView> = delegate
|
|
27
|
+
|
|
28
|
+
override fun getName(): String = NAME
|
|
29
|
+
|
|
30
|
+
public override fun createViewInstance(context: ThemedReactContext): WhopPayButtonView {
|
|
31
|
+
val button = WhopPayButtonView(context)
|
|
32
|
+
button.setOnPressListener {
|
|
33
|
+
val dispatcher = UIManagerHelper.getEventDispatcher(context, UIManagerType.FABRIC)
|
|
34
|
+
// Never dispatch a Fabric event from inside a layout or composition pass.
|
|
35
|
+
button.post {
|
|
36
|
+
dispatcher?.dispatchEvent(
|
|
37
|
+
WhopPayButtonPressEvent(UIManagerHelper.getSurfaceId(context), button.id),
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return button
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** A direct event reaches JS only if the manager maps its native name to the prop that receives
|
|
45
|
+
* it. Without this the press is dispatched and silently dropped. */
|
|
46
|
+
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> =
|
|
47
|
+
mutableMapOf(
|
|
48
|
+
WhopPayButtonPressEvent.EVENT_NAME to mutableMapOf("registrationName" to "onPayPress"),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@ReactProp(name = "variant")
|
|
52
|
+
override fun setVariant(view: WhopPayButtonView?, value: String?) {
|
|
53
|
+
view?.setVariant(value)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@ReactProp(name = "buttonAppearance")
|
|
57
|
+
override fun setButtonAppearance(view: WhopPayButtonView?, value: String?) {
|
|
58
|
+
view?.setButtonAppearance(value)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@ReactProp(name = "cornerRadius")
|
|
62
|
+
override fun setCornerRadius(view: WhopPayButtonView?, value: Double) {
|
|
63
|
+
view?.setCornerRadius(value)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@ReactProp(name = "disabled")
|
|
67
|
+
override fun setDisabled(view: WhopPayButtonView?, value: Boolean) {
|
|
68
|
+
view?.setDisabledState(value)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
companion object {
|
|
72
|
+
const val NAME = "WhopPayButton"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
package com.whop.elements
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.WritableMap
|
|
5
|
+
import com.facebook.react.uimanager.events.Event
|
|
6
|
+
|
|
7
|
+
class WhopPayButtonPressEvent(surfaceId: Int, viewId: Int) :
|
|
8
|
+
Event<WhopPayButtonPressEvent>(surfaceId, viewId) {
|
|
9
|
+
|
|
10
|
+
override fun getEventName(): String = EVENT_NAME
|
|
11
|
+
|
|
12
|
+
override fun getEventData(): WritableMap = Arguments.createMap()
|
|
13
|
+
|
|
14
|
+
companion object {
|
|
15
|
+
const val EVENT_NAME = "topPayPress"
|
|
16
|
+
}
|
|
17
|
+
}
|