@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,51 @@
|
|
|
1
|
+
// GENERATED by scripts/codegen-shims.mjs — do not edit, do not commit.
|
|
2
|
+
// Source of truth: src/specs/. Implementations are Swift, in ios/Sources/WhopElements.
|
|
3
|
+
|
|
4
|
+
#import "WhopPlatformPayModule.h"
|
|
5
|
+
#import "WhopElementsShims.h"
|
|
6
|
+
|
|
7
|
+
@implementation WhopPlatformPayModule {
|
|
8
|
+
WhopPlatformPayImpl *_impl;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// The JS name, explicitly: bare RCT_EXPORT_MODULE() registers under the CLASS name
|
|
12
|
+
// ("WhopPlatformPayModule"), and TurboModuleRegistry then cannot find "WhopPlatformPay".
|
|
13
|
+
RCT_EXPORT_MODULE(WhopPlatformPay)
|
|
14
|
+
|
|
15
|
+
- (instancetype)init
|
|
16
|
+
{
|
|
17
|
+
if (self = [super init]) {
|
|
18
|
+
_impl = [WhopPlatformPayImpl new];
|
|
19
|
+
}
|
|
20
|
+
return self;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
24
|
+
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
25
|
+
{
|
|
26
|
+
return std::make_shared<facebook::react::NativeWhopPlatformPaySpecJSI>(params);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
- (NSString *)capability
|
|
30
|
+
{
|
|
31
|
+
return [_impl capability];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
- (void)canPay:(NSString *)configJson resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
|
|
35
|
+
{
|
|
36
|
+
[_impl canPay:configJson resolve:^(id _Nullable value) { resolve(value); }
|
|
37
|
+
reject:^(NSString *code, NSString *message) { reject(code, message, nil); }];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
- (void)present:(NSString *)requestJson resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
|
|
41
|
+
{
|
|
42
|
+
[_impl present:requestJson resolve:^(id _Nullable value) { resolve(value); }
|
|
43
|
+
reject:^(NSString *code, NSString *message) { reject(code, message, nil); }];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
- (void)complete:(BOOL)success
|
|
47
|
+
{
|
|
48
|
+
[_impl complete:success];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The API origin is an ENUM over two Whop-owned hosts, never a consumer-supplied string.
|
|
5
|
+
*
|
|
6
|
+
* A consumer-settable origin would let the embedding app point a payment surface at a host it
|
|
7
|
+
* controls and read everything a buyer types into it. The web elements hold the same line
|
|
8
|
+
* (`elements-framework/src/builder/api-base.ts`); this is that rule on React Native.
|
|
9
|
+
*/
|
|
10
|
+
export const API_BASES = {
|
|
11
|
+
production: 'https://api.whop.com',
|
|
12
|
+
sandbox: 'https://api.sandbox.whop.com'
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Pinned so a server-side schema roll cannot change what a shipped app parses.
|
|
16
|
+
*
|
|
17
|
+
* Must be a REAL released version: the API rejects an unknown date outright, before it even looks
|
|
18
|
+
* at the request. The published list is `Api::Versioning::RELEASED` in
|
|
19
|
+
* backend/lib/api/versioning.rb — bump this to a date from there, never to today's date.
|
|
20
|
+
*/
|
|
21
|
+
const API_VERSION_DATE = '2026-08-25';
|
|
22
|
+
export class WhopError extends Error {
|
|
23
|
+
/** Alias of `status`; the ported payments error helpers read this name. */
|
|
24
|
+
|
|
25
|
+
constructor(message, status, code, body) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = 'WhopError';
|
|
28
|
+
this.status = status;
|
|
29
|
+
this.statusCode = status;
|
|
30
|
+
this.code = code;
|
|
31
|
+
this.body = body;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const RETRYABLE = new Set([408, 429, 500, 502, 503, 504]);
|
|
35
|
+
export class WhopClient {
|
|
36
|
+
constructor(options = {}) {
|
|
37
|
+
this.options = options;
|
|
38
|
+
this.baseUrl = API_BASES[options.environment ?? 'production'];
|
|
39
|
+
this.doFetch = options.fetch ?? globalThis.fetch;
|
|
40
|
+
}
|
|
41
|
+
async request(method, path, opts = {}) {
|
|
42
|
+
const url = new URL(`/api/v1${path}`, this.baseUrl);
|
|
43
|
+
for (const [key, value] of Object.entries(opts.query ?? {})) {
|
|
44
|
+
if (value !== undefined) url.searchParams.set(key, String(value));
|
|
45
|
+
}
|
|
46
|
+
const token = opts.token !== undefined ? opts.token : await this.options.getToken?.();
|
|
47
|
+
const sessionKey = this.options.getSessionKey?.();
|
|
48
|
+
const headers = {
|
|
49
|
+
accept: 'application/json',
|
|
50
|
+
'api-version-date': API_VERSION_DATE
|
|
51
|
+
};
|
|
52
|
+
if (opts.body !== undefined) headers['content-type'] = 'application/json';
|
|
53
|
+
if (token) headers.authorization = `Bearer ${token}`;
|
|
54
|
+
if (sessionKey) headers['x-ssk'] = sessionKey;
|
|
55
|
+
const maxRetries = opts.maxRetries ?? 2;
|
|
56
|
+
let lastError;
|
|
57
|
+
for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
|
|
58
|
+
let response;
|
|
59
|
+
try {
|
|
60
|
+
response = await this.doFetch(url.toString(), {
|
|
61
|
+
method,
|
|
62
|
+
headers,
|
|
63
|
+
body: opts.body === undefined ? undefined : JSON.stringify(opts.body),
|
|
64
|
+
signal: opts.signal
|
|
65
|
+
});
|
|
66
|
+
} catch (err) {
|
|
67
|
+
lastError = new WhopError(err instanceof Error ? err.message : 'the network request failed', 0, 'NETWORK_UNAVAILABLE');
|
|
68
|
+
if (attempt === maxRetries) throw lastError;
|
|
69
|
+
await backoff(attempt);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (response.ok) {
|
|
73
|
+
if (response.status === 204) return undefined;
|
|
74
|
+
return await response.json();
|
|
75
|
+
}
|
|
76
|
+
const body = await response.json().catch(() => undefined);
|
|
77
|
+
const error = new WhopError(errorMessage(body, response.status), response.status, errorCode(body), body);
|
|
78
|
+
if (!RETRYABLE.has(response.status) || attempt === maxRetries) throw error;
|
|
79
|
+
lastError = error;
|
|
80
|
+
await backoff(attempt);
|
|
81
|
+
}
|
|
82
|
+
throw lastError instanceof Error ? lastError : new WhopError('the request failed', 0);
|
|
83
|
+
}
|
|
84
|
+
plans = {
|
|
85
|
+
retrieve: id => this.request('GET', `/plans/${encodeURIComponent(id)}`)
|
|
86
|
+
};
|
|
87
|
+
paymentMethodTypes = {
|
|
88
|
+
list: query => this.request('GET', '/payment_method_types', {
|
|
89
|
+
query
|
|
90
|
+
}),
|
|
91
|
+
retrieve: (type, query) => this.request('GET', `/payment_method_types/${encodeURIComponent(type)}`, {
|
|
92
|
+
query
|
|
93
|
+
}),
|
|
94
|
+
/** Authorizes a Basis Theory session against the nonce-derived card-assembly container. */
|
|
95
|
+
createSession: (type, body) => this.request('POST', `/payment_method_types/${encodeURIComponent(type)}/session`, {
|
|
96
|
+
body
|
|
97
|
+
})
|
|
98
|
+
};
|
|
99
|
+
confirmationTokens = {
|
|
100
|
+
create: body => this.request('POST', '/confirmation_tokens', {
|
|
101
|
+
body,
|
|
102
|
+
maxRetries: 0
|
|
103
|
+
}),
|
|
104
|
+
retrieve: id => this.request('GET', `/confirmation_tokens/${encodeURIComponent(id)}`)
|
|
105
|
+
};
|
|
106
|
+
payments = {
|
|
107
|
+
/** The client secret both identifies and authorizes, so no payment id is needed. */
|
|
108
|
+
retrieveBySecret: clientSecret => this.request('GET', `/payments/${encodeURIComponent(paymentTagFrom(clientSecret))}`, {
|
|
109
|
+
query: {
|
|
110
|
+
client_secret: clientSecret
|
|
111
|
+
}
|
|
112
|
+
}),
|
|
113
|
+
updateBySecret: (clientSecret, body) => this.request('PATCH', `/payments/${encodeURIComponent(paymentTagFrom(clientSecret))}`, {
|
|
114
|
+
query: {
|
|
115
|
+
client_secret: clientSecret
|
|
116
|
+
},
|
|
117
|
+
body,
|
|
118
|
+
maxRetries: 0
|
|
119
|
+
})
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** `pay_<tag>_secret_v<n>` — the tag is embedded, which is why the secret is the whole reference. */
|
|
124
|
+
export function paymentTagFrom(clientSecret) {
|
|
125
|
+
const marker = clientSecret.indexOf('_secret_');
|
|
126
|
+
return marker === -1 ? clientSecret : clientSecret.slice(0, marker);
|
|
127
|
+
}
|
|
128
|
+
function errorMessage(body, status) {
|
|
129
|
+
const detail = body ?? undefined;
|
|
130
|
+
return detail?.error?.message ?? detail?.message ?? `the request failed with status ${status}`;
|
|
131
|
+
}
|
|
132
|
+
function errorCode(body) {
|
|
133
|
+
const detail = body ?? undefined;
|
|
134
|
+
return detail?.error?.code ?? detail?.error?.type;
|
|
135
|
+
}
|
|
136
|
+
function backoff(attempt) {
|
|
137
|
+
return new Promise(resolve => setTimeout(resolve, 2 ** attempt * 250));
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["API_BASES","production","sandbox","API_VERSION_DATE","WhopError","Error","constructor","message","status","code","body","name","statusCode","RETRYABLE","Set","WhopClient","options","baseUrl","environment","doFetch","fetch","globalThis","request","method","path","opts","url","URL","key","value","Object","entries","query","undefined","searchParams","set","String","token","getToken","sessionKey","getSessionKey","headers","accept","authorization","maxRetries","lastError","attempt","response","toString","JSON","stringify","signal","err","backoff","ok","json","catch","error","errorMessage","errorCode","has","plans","retrieve","id","encodeURIComponent","paymentMethodTypes","list","type","createSession","confirmationTokens","create","payments","retrieveBySecret","clientSecret","paymentTagFrom","client_secret","updateBySecret","marker","indexOf","slice","detail","Promise","resolve","setTimeout"],"sourceRoot":"../../../src","sources":["api/client.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import { API_BASES, paymentTagFrom, WhopClient, WhopError } from "./client.js";
|
|
5
|
+
describe('API_BASES', () => {
|
|
6
|
+
// The origin is an enum over two Whop-owned hosts; a consumer-settable one would let an
|
|
7
|
+
// embedding app read everything a buyer types.
|
|
8
|
+
it('is a closed set of Whop origins', () => {
|
|
9
|
+
expect(Object.keys(API_BASES).sort()).toEqual(['production', 'sandbox']);
|
|
10
|
+
for (const origin of Object.values(API_BASES)) {
|
|
11
|
+
expect(origin).toMatch(/^https:\/\/[a-z.]*whop\.com$/);
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
describe('paymentTagFrom', () => {
|
|
16
|
+
it('reads the tag embedded in the client secret', () => {
|
|
17
|
+
expect(paymentTagFrom('pay_abc123_secret_v1')).toBe('pay_abc123');
|
|
18
|
+
});
|
|
19
|
+
it('passes a bare id through', () => {
|
|
20
|
+
expect(paymentTagFrom('pay_abc123')).toBe('pay_abc123');
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
describe('WhopClient', () => {
|
|
24
|
+
function stub(handler) {
|
|
25
|
+
const calls = [];
|
|
26
|
+
const client = new WhopClient({
|
|
27
|
+
environment: 'sandbox',
|
|
28
|
+
getToken: () => 'tok_buyer',
|
|
29
|
+
fetch: async (url, init) => {
|
|
30
|
+
calls.push({
|
|
31
|
+
url,
|
|
32
|
+
init
|
|
33
|
+
});
|
|
34
|
+
return handler(url, init);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
client,
|
|
39
|
+
calls
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const ok = body => new Response(JSON.stringify(body), {
|
|
43
|
+
status: 200,
|
|
44
|
+
headers: {
|
|
45
|
+
'content-type': 'application/json'
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// The pinned value must be a REAL entry in Api::Versioning::RELEASED
|
|
50
|
+
// (backend/lib/api/versioning.rb). The API rejects an unknown date before it looks at anything
|
|
51
|
+
// else, so an invented one 400s every request the SDK makes.
|
|
52
|
+
it('pins a released api version and forwards the resolved token', async () => {
|
|
53
|
+
const {
|
|
54
|
+
client,
|
|
55
|
+
calls
|
|
56
|
+
} = stub(() => ok({
|
|
57
|
+
id: 'plan_1',
|
|
58
|
+
object: 'plan',
|
|
59
|
+
currency: 'usd'
|
|
60
|
+
}));
|
|
61
|
+
await client.plans.retrieve('plan_1');
|
|
62
|
+
const headers = calls[0].init.headers;
|
|
63
|
+
expect(calls[0].url).toBe('https://api.sandbox.whop.com/api/v1/plans/plan_1');
|
|
64
|
+
expect(headers['api-version-date']).toBe('2026-08-25');
|
|
65
|
+
expect(headers.authorization).toBe('Bearer tok_buyer');
|
|
66
|
+
// A date shape, never something like "latest" or a build timestamp.
|
|
67
|
+
expect(headers['api-version-date']).toMatch(/^\d{4}-\d{2}-\d{2}(-\d+)?$/);
|
|
68
|
+
});
|
|
69
|
+
it('drops undefined query values instead of sending the string "undefined"', async () => {
|
|
70
|
+
const {
|
|
71
|
+
client,
|
|
72
|
+
calls
|
|
73
|
+
} = stub(() => ok({
|
|
74
|
+
data: [],
|
|
75
|
+
page_info: {}
|
|
76
|
+
}));
|
|
77
|
+
await client.paymentMethodTypes.list({
|
|
78
|
+
account_id: 'biz_1',
|
|
79
|
+
currency: 'usd',
|
|
80
|
+
amount: undefined
|
|
81
|
+
});
|
|
82
|
+
expect(calls[0].url).toContain('account_id=biz_1');
|
|
83
|
+
expect(calls[0].url).toContain('currency=usd');
|
|
84
|
+
expect(calls[0].url).not.toContain('amount');
|
|
85
|
+
});
|
|
86
|
+
it('surfaces the API error message and code', async () => {
|
|
87
|
+
const {
|
|
88
|
+
client
|
|
89
|
+
} = stub(() => new Response(JSON.stringify({
|
|
90
|
+
error: {
|
|
91
|
+
message: 'plan not found',
|
|
92
|
+
code: 'not_found'
|
|
93
|
+
}
|
|
94
|
+
}), {
|
|
95
|
+
status: 404
|
|
96
|
+
}));
|
|
97
|
+
await expect(client.plans.retrieve('plan_missing')).rejects.toMatchObject({
|
|
98
|
+
message: 'plan not found',
|
|
99
|
+
code: 'not_found',
|
|
100
|
+
status: 404,
|
|
101
|
+
statusCode: 404
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
it('never retries a confirmation-token create', async () => {
|
|
105
|
+
let attempts = 0;
|
|
106
|
+
const {
|
|
107
|
+
client
|
|
108
|
+
} = stub(() => {
|
|
109
|
+
attempts += 1;
|
|
110
|
+
return new Response(JSON.stringify({
|
|
111
|
+
error: {
|
|
112
|
+
message: 'boom'
|
|
113
|
+
}
|
|
114
|
+
}), {
|
|
115
|
+
status: 500
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
await expect(client.confirmationTokens.create({
|
|
119
|
+
account_id: 'biz_1',
|
|
120
|
+
payment_method: {
|
|
121
|
+
category: 'card'
|
|
122
|
+
},
|
|
123
|
+
billing_details: {}
|
|
124
|
+
})).rejects.toBeInstanceOf(WhopError);
|
|
125
|
+
expect(attempts).toBe(1);
|
|
126
|
+
});
|
|
127
|
+
it('reports an unreachable network as a coded failure', async () => {
|
|
128
|
+
const client = new WhopClient({
|
|
129
|
+
environment: 'sandbox',
|
|
130
|
+
fetch: () => Promise.reject(new Error('offline'))
|
|
131
|
+
});
|
|
132
|
+
await expect(client.plans.retrieve('plan_1')).rejects.toMatchObject({
|
|
133
|
+
code: 'NETWORK_UNAVAILABLE'
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
//# sourceMappingURL=client.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["describe","expect","it","API_BASES","paymentTagFrom","WhopClient","WhopError","Object","keys","sort","toEqual","origin","values","toMatch","toBe","stub","handler","calls","client","environment","getToken","fetch","url","init","push","ok","body","Response","JSON","stringify","status","headers","id","object","currency","plans","retrieve","authorization","data","page_info","paymentMethodTypes","list","account_id","amount","undefined","toContain","not","error","message","code","rejects","toMatchObject","statusCode","attempts","confirmationTokens","create","payment_method","category","billing_details","toBeInstanceOf","Promise","reject","Error"],"sourceRoot":"../../../src","sources":["api/client.test.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sourceRoot":"../../../src","sources":["api/types.ts"],"mappings":"","ignoreList":[]}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { mergeBillingWithCollected } from "../core/payments/billing-merge.js";
|
|
4
|
+
import { BrandingRequiredError } from "../core/payments/errors.js";
|
|
5
|
+
import { PaymentsRefusalError } from "../core/payments/refusals.js";
|
|
6
|
+
import { mintConfirmationToken, validateBillingEmail } from "../core/payments/mint.js";
|
|
7
|
+
import { WhopAuthSession } from "../native/index.js";
|
|
8
|
+
/**
|
|
9
|
+
* The one mint. Resolves the live collection surface, merges the billing block underneath any
|
|
10
|
+
* explicit input, and posts `POST /confirmation_tokens`.
|
|
11
|
+
*/
|
|
12
|
+
export async function createConfirmationToken(store, input = {}) {
|
|
13
|
+
const state = store.getSnapshot();
|
|
14
|
+
if (state.confirmInFlight) {
|
|
15
|
+
throw new PaymentsRefusalError('CONFIRMATION_IN_FLIGHT', 'A confirmation token is already being created for this handle.');
|
|
16
|
+
}
|
|
17
|
+
// Whop is the merchant of record on these sales, so the buyer has to be told so on the screen
|
|
18
|
+
// where they pay. The web element cannot demand this of already-shipped integrations; a first
|
|
19
|
+
// release can, and does.
|
|
20
|
+
if (!state.brandingMounted) {
|
|
21
|
+
throw new BrandingRequiredError('BRANDING_NOT_MOUNTED', 'Mount <BrandingElement /> alongside your payment surface — it carries the merchant-of-record notice.');
|
|
22
|
+
}
|
|
23
|
+
if (!state.charge.configured) {
|
|
24
|
+
throw new PaymentsRefusalError('CHARGE_CONFIG_MISSING', 'This handle has no charge config — pass a plan or currency and amount.');
|
|
25
|
+
}
|
|
26
|
+
const surface = store.liveSurface();
|
|
27
|
+
if (!surface) {
|
|
28
|
+
throw new PaymentsRefusalError('NO_COLLECTION_SURFACE', 'No collection surface is mounted — render <PaymentElement />, <CardElement />, or <CardFields>.');
|
|
29
|
+
}
|
|
30
|
+
store.patch({
|
|
31
|
+
confirmInFlight: true
|
|
32
|
+
});
|
|
33
|
+
try {
|
|
34
|
+
const seeded = {
|
|
35
|
+
...input.billingDetails,
|
|
36
|
+
email: input.billingDetails?.email ?? state.buyerEmail
|
|
37
|
+
};
|
|
38
|
+
const collected = await surface.collect({
|
|
39
|
+
billingDetails: seeded
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Re-read AFTER collect: card and wallet collection take seconds, and a buyer editing their
|
|
43
|
+
// address or email meanwhile must have the live values minted, not the pre-collect snapshot.
|
|
44
|
+
const settled = store.getSnapshot();
|
|
45
|
+
const email = input.billingDetails?.email ?? settled.buyerEmail ?? seeded.email;
|
|
46
|
+
const billing = mergeBillingWithCollected({
|
|
47
|
+
...collected.billingDetails,
|
|
48
|
+
...input.billingDetails,
|
|
49
|
+
email: email || collected.billingDetails.email
|
|
50
|
+
}, settled.addressLive ? settled.addressCollected : undefined);
|
|
51
|
+
return await mintConfirmationToken(store.client, {
|
|
52
|
+
accountId: settled.accountId,
|
|
53
|
+
returnUrl: settled.returnUrl,
|
|
54
|
+
setupFutureUsage: settled.charge.configured ? settled.charge.setupFutureUsage : undefined
|
|
55
|
+
}, collected.paymentMethod, validateBillingEmail(billing), {
|
|
56
|
+
...collected.billingExtra,
|
|
57
|
+
...input.billingExtra
|
|
58
|
+
});
|
|
59
|
+
} finally {
|
|
60
|
+
store.patch({
|
|
61
|
+
confirmInFlight: false
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const RESTING = new Set(['succeeded', 'failed', 'canceled', 'cancelled', 'requires_payment_method']);
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Completes a confirmed payment's pending step.
|
|
69
|
+
*
|
|
70
|
+
* The redirect leg opens the system browser and then **polls the payment to rest**. Polling is the
|
|
71
|
+
* authority rather than a callback because the API refuses anything but https or loopback for a
|
|
72
|
+
* return URL (`PaymentsApi::ValidateReturnUrl`), so a custom-scheme deep link back into the app is
|
|
73
|
+
* not available. Dismissing the browser therefore returns the payment's current status, which may
|
|
74
|
+
* still be pending — the same contract the web action documents.
|
|
75
|
+
*/
|
|
76
|
+
export async function handleNextAction(store, input) {
|
|
77
|
+
// One at a time, like the mint. Two overlapping calls each open a system browser and each run
|
|
78
|
+
// their own poll, and whichever finishes first calls dismiss() — closing the OTHER buyer-facing
|
|
79
|
+
// challenge mid-flow and orphaning its presentation. A double-tapped submit is enough to do it.
|
|
80
|
+
if (store.getSnapshot().nextActionInFlight) {
|
|
81
|
+
throw new PaymentsRefusalError('NEXT_ACTION_IN_FLIGHT', 'A payment step is already being handled for this handle.');
|
|
82
|
+
}
|
|
83
|
+
store.patch({
|
|
84
|
+
nextActionInFlight: true
|
|
85
|
+
});
|
|
86
|
+
try {
|
|
87
|
+
return await runNextAction(store, input);
|
|
88
|
+
} finally {
|
|
89
|
+
store.patch({
|
|
90
|
+
nextActionInFlight: false
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async function runNextAction(store, input) {
|
|
95
|
+
const pollIntervalMs = input.pollIntervalMs ?? 2500;
|
|
96
|
+
const timeoutMs = input.timeoutMs ?? 300_000;
|
|
97
|
+
if (input.returnUrl) {
|
|
98
|
+
// A failed update rejects BEFORE any step runs, so the buyer is never sent somewhere the
|
|
99
|
+
// payment does not know about.
|
|
100
|
+
await store.client.payments.updateBySecret(input.clientSecret, {
|
|
101
|
+
return_url: input.returnUrl
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
let payment = await store.client.payments.retrieveBySecret(input.clientSecret);
|
|
105
|
+
const url = payment.next_action?.redirect?.url;
|
|
106
|
+
if (!payment.next_action || !url) {
|
|
107
|
+
return {
|
|
108
|
+
status: payment.status,
|
|
109
|
+
redirected: false,
|
|
110
|
+
lastPaymentError: payment.last_payment_error ?? null
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// A mutable box rather than a bare `let`: the assignment happens inside a callback, which control
|
|
115
|
+
// flow analysis does not see, so a plain binding narrows to `null` at every read below.
|
|
116
|
+
const presented = {
|
|
117
|
+
error: null
|
|
118
|
+
};
|
|
119
|
+
const presentation = WhopAuthSession.present(url).catch(error => {
|
|
120
|
+
presented.error = error instanceof Error ? error : new Error(String(error));
|
|
121
|
+
});
|
|
122
|
+
const deadline = Date.now() + timeoutMs;
|
|
123
|
+
try {
|
|
124
|
+
while (Date.now() < deadline) {
|
|
125
|
+
await delay(pollIntervalMs);
|
|
126
|
+
// The buyer cannot be completing a step that never opened. Without this the poll runs the
|
|
127
|
+
// full timeout and then reports a redirect that never happened.
|
|
128
|
+
if (presented.error) break;
|
|
129
|
+
payment = await store.client.payments.retrieveBySecret(input.clientSecret).catch(() => payment);
|
|
130
|
+
if (RESTING.has(payment.status) || !payment.next_action) break;
|
|
131
|
+
}
|
|
132
|
+
} finally {
|
|
133
|
+
WhopAuthSession.dismiss();
|
|
134
|
+
await presentation;
|
|
135
|
+
}
|
|
136
|
+
if (presented.error) {
|
|
137
|
+
throw new PaymentsRefusalError('NEXT_ACTION_PRESENTATION_FAILED', `The payment step could not be opened: ${presented.error.message}`);
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
status: payment.status,
|
|
141
|
+
redirected: true,
|
|
142
|
+
lastPaymentError: payment.last_payment_error ?? null
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function delay(ms) {
|
|
146
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=actions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["mergeBillingWithCollected","BrandingRequiredError","PaymentsRefusalError","mintConfirmationToken","validateBillingEmail","WhopAuthSession","createConfirmationToken","store","input","state","getSnapshot","confirmInFlight","brandingMounted","charge","configured","surface","liveSurface","patch","seeded","billingDetails","email","buyerEmail","collected","collect","settled","billing","addressLive","addressCollected","undefined","client","accountId","returnUrl","setupFutureUsage","paymentMethod","billingExtra","RESTING","Set","handleNextAction","nextActionInFlight","runNextAction","pollIntervalMs","timeoutMs","payments","updateBySecret","clientSecret","return_url","payment","retrieveBySecret","url","next_action","redirect","status","redirected","lastPaymentError","last_payment_error","presented","error","presentation","present","catch","Error","String","deadline","Date","now","delay","has","dismiss","message","ms","Promise","resolve","setTimeout"],"sourceRoot":"../../../src","sources":["controller/actions.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
// `../native` reaches react-native, whose Flow source vitest cannot parse. The redirect leg is not
|
|
6
|
+
// what these tests exercise: they are about the in-flight guard around it.
|
|
7
|
+
vi.mock('../native', () => ({
|
|
8
|
+
WhopAuthSession: {
|
|
9
|
+
present: vi.fn(async () => null),
|
|
10
|
+
dismiss: vi.fn()
|
|
11
|
+
},
|
|
12
|
+
WhopPlatformPay: {
|
|
13
|
+
capability: vi.fn(() => 'none')
|
|
14
|
+
},
|
|
15
|
+
WALLET_TYPE_FOR_PLATFORM: 'apple_pay'
|
|
16
|
+
}));
|
|
17
|
+
import { PaymentsRefusalError } from "../core/payments/refusals.js";
|
|
18
|
+
import { handleNextAction } from "./actions.js";
|
|
19
|
+
/**
|
|
20
|
+
* The store as `handleNextAction` uses it: a state bag it reads and patches, and a client whose
|
|
21
|
+
* payments resource it polls. Building the real one would open a network client and resolve a
|
|
22
|
+
* charge, neither of which this behaviour depends on.
|
|
23
|
+
*/
|
|
24
|
+
function fakeStore(payment) {
|
|
25
|
+
const state = {
|
|
26
|
+
nextActionInFlight: false
|
|
27
|
+
};
|
|
28
|
+
let retrieves = 0;
|
|
29
|
+
return {
|
|
30
|
+
store: {
|
|
31
|
+
getSnapshot: () => state,
|
|
32
|
+
patch: next => Object.assign(state, next),
|
|
33
|
+
client: {
|
|
34
|
+
payments: {
|
|
35
|
+
updateBySecret: vi.fn(async () => payment),
|
|
36
|
+
retrieveBySecret: vi.fn(async () => {
|
|
37
|
+
retrieves += 1;
|
|
38
|
+
return payment;
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
state,
|
|
44
|
+
retrieves: () => retrieves
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
describe('handleNextAction', () => {
|
|
48
|
+
it('returns without redirecting when the payment has no next action', async () => {
|
|
49
|
+
const {
|
|
50
|
+
store,
|
|
51
|
+
state
|
|
52
|
+
} = fakeStore({
|
|
53
|
+
status: 'succeeded',
|
|
54
|
+
next_action: null,
|
|
55
|
+
last_payment_error: null
|
|
56
|
+
});
|
|
57
|
+
const result = await handleNextAction(store, {
|
|
58
|
+
clientSecret: 'pay_secret'
|
|
59
|
+
});
|
|
60
|
+
expect(result).toEqual({
|
|
61
|
+
status: 'succeeded',
|
|
62
|
+
redirected: false,
|
|
63
|
+
lastPaymentError: null
|
|
64
|
+
});
|
|
65
|
+
// the guard always clears, so a second call is not locked out by the first
|
|
66
|
+
expect(state.nextActionInFlight).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
it('refuses a second call while one is in flight', async () => {
|
|
69
|
+
const {
|
|
70
|
+
store,
|
|
71
|
+
state
|
|
72
|
+
} = fakeStore({
|
|
73
|
+
status: 'succeeded',
|
|
74
|
+
next_action: null,
|
|
75
|
+
last_payment_error: null
|
|
76
|
+
});
|
|
77
|
+
state.nextActionInFlight = true;
|
|
78
|
+
await expect(handleNextAction(store, {
|
|
79
|
+
clientSecret: 'pay_secret'
|
|
80
|
+
})).rejects.toThrow(PaymentsRefusalError);
|
|
81
|
+
await expect(handleNextAction(store, {
|
|
82
|
+
clientSecret: 'pay_secret'
|
|
83
|
+
})).rejects.toThrow(/NEXT_ACTION_IN_FLIGHT/);
|
|
84
|
+
});
|
|
85
|
+
it('clears the guard even when the call throws', async () => {
|
|
86
|
+
const {
|
|
87
|
+
store,
|
|
88
|
+
state
|
|
89
|
+
} = fakeStore({
|
|
90
|
+
status: 'requires_action',
|
|
91
|
+
next_action: {},
|
|
92
|
+
last_payment_error: null
|
|
93
|
+
});
|
|
94
|
+
store.client.payments.retrieveBySecret.mockRejectedValueOnce(new Error('network'));
|
|
95
|
+
await expect(handleNextAction(store, {
|
|
96
|
+
clientSecret: 'pay_secret'
|
|
97
|
+
})).rejects.toThrow('network');
|
|
98
|
+
// a failed step must not lock the handle out of every later attempt
|
|
99
|
+
expect(state.nextActionInFlight).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
//# sourceMappingURL=actions.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["describe","expect","it","vi","mock","WhopAuthSession","present","fn","dismiss","WhopPlatformPay","capability","WALLET_TYPE_FOR_PLATFORM","PaymentsRefusalError","handleNextAction","fakeStore","payment","state","nextActionInFlight","retrieves","store","getSnapshot","patch","next","Object","assign","client","payments","updateBySecret","retrieveBySecret","status","next_action","last_payment_error","result","clientSecret","toEqual","redirected","lastPaymentError","toBe","rejects","toThrow","mockRejectedValueOnce","Error"],"sourceRoot":"../../../src","sources":["controller/actions.test.ts"],"mappings":";;AAAA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ,QAAQ;;AAEjD;AACA;AACAA,EAAE,CAACC,IAAI,CAAC,WAAW,EAAE,OAAO;EAC3BC,eAAe,EAAE;IAAEC,OAAO,EAAEH,EAAE,CAACI,EAAE,CAAC,YAAY,IAAI,CAAC;IAAEC,OAAO,EAAEL,EAAE,CAACI,EAAE,CAAC;EAAE,CAAC;EACvEE,eAAe,EAAE;IAAEC,UAAU,EAAEP,EAAE,CAACI,EAAE,CAAC,MAAM,MAAM;EAAE,CAAC;EACpDI,wBAAwB,EAAE;AAC3B,CAAC,CAAC,CAAC;AAEH,SAASC,oBAAoB,QAAQ,8BAA2B;AAChE,SAASC,gBAAgB,QAAQ,cAAW;AAG5C;AACA;AACA;AACA;AACA;AACA,SAASC,SAASA,CAACC,OAAgC,EAAE;EACpD,MAAMC,KAA8B,GAAG;IAAEC,kBAAkB,EAAE;EAAM,CAAC;EACpE,IAAIC,SAAS,GAAG,CAAC;EACjB,OAAO;IACNC,KAAK,EAAE;MACNC,WAAW,EAAEA,CAAA,KAAMJ,KAAK;MACxBK,KAAK,EAAGC,IAA6B,IAAKC,MAAM,CAACC,MAAM,CAACR,KAAK,EAAEM,IAAI,CAAC;MACpEG,MAAM,EAAE;QACPC,QAAQ,EAAE;UACTC,cAAc,EAAExB,EAAE,CAACI,EAAE,CAAC,YAAYQ,OAAO,CAAC;UAC1Ca,gBAAgB,EAAEzB,EAAE,CAACI,EAAE,CAAC,YAAY;YACnCW,SAAS,IAAI,CAAC;YACd,OAAOH,OAAO;UACf,CAAC;QACF;MACD;IACD,CAA6B;IAC7BC,KAAK;IACLE,SAAS,EAAEA,CAAA,KAAMA;EAClB,CAAC;AACF;AAEAlB,QAAQ,CAAC,kBAAkB,EAAE,MAAM;EAClCE,EAAE,CAAC,iEAAiE,EAAE,YAAY;IACjF,MAAM;MAAEiB,KAAK;MAAEH;IAAM,CAAC,GAAGF,SAAS,CAAC;MAAEe,MAAM,EAAE,WAAW;MAAEC,WAAW,EAAE,IAAI;MAAEC,kBAAkB,EAAE;IAAK,CAAC,CAAC;IAExG,MAAMC,MAAM,GAAG,MAAMnB,gBAAgB,CAACM,KAAK,EAAE;MAAEc,YAAY,EAAE;IAAa,CAAC,CAAC;IAE5EhC,MAAM,CAAC+B,MAAM,CAAC,CAACE,OAAO,CAAC;MAAEL,MAAM,EAAE,WAAW;MAAEM,UAAU,EAAE,KAAK;MAAEC,gBAAgB,EAAE;IAAK,CAAC,CAAC;IAC1F;IACAnC,MAAM,CAACe,KAAK,CAACC,kBAAkB,CAAC,CAACoB,IAAI,CAAC,KAAK,CAAC;EAC7C,CAAC,CAAC;EAEFnC,EAAE,CAAC,8CAA8C,EAAE,YAAY;IAC9D,MAAM;MAAEiB,KAAK;MAAEH;IAAM,CAAC,GAAGF,SAAS,CAAC;MAAEe,MAAM,EAAE,WAAW;MAAEC,WAAW,EAAE,IAAI;MAAEC,kBAAkB,EAAE;IAAK,CAAC,CAAC;IACxGf,KAAK,CAACC,kBAAkB,GAAG,IAAI;IAE/B,MAAMhB,MAAM,CAACY,gBAAgB,CAACM,KAAK,EAAE;MAAEc,YAAY,EAAE;IAAa,CAAC,CAAC,CAAC,CAACK,OAAO,CAACC,OAAO,CAAC3B,oBAAoB,CAAC;IAC3G,MAAMX,MAAM,CAACY,gBAAgB,CAACM,KAAK,EAAE;MAAEc,YAAY,EAAE;IAAa,CAAC,CAAC,CAAC,CAACK,OAAO,CAACC,OAAO,CAAC,uBAAuB,CAAC;EAC/G,CAAC,CAAC;EAEFrC,EAAE,CAAC,4CAA4C,EAAE,YAAY;IAC5D,MAAM;MAAEiB,KAAK;MAAEH;IAAM,CAAC,GAAGF,SAAS,CAAC;MAAEe,MAAM,EAAE,iBAAiB;MAAEC,WAAW,EAAE,CAAC,CAAC;MAAEC,kBAAkB,EAAE;IAAK,CAAC,CAAC;IAC3GZ,KAAK,CAACM,MAAM,CAACC,QAAQ,CAACE,gBAAgB,CAA8BY,qBAAqB,CAAC,IAAIC,KAAK,CAAC,SAAS,CAAC,CAAC;IAEhH,MAAMxC,MAAM,CAACY,gBAAgB,CAACM,KAAK,EAAE;MAAEc,YAAY,EAAE;IAAa,CAAC,CAAC,CAAC,CAACK,OAAO,CAACC,OAAO,CAAC,SAAS,CAAC;IAChG;IACAtC,MAAM,CAACe,KAAK,CAACC,kBAAkB,CAAC,CAACoB,IAAI,CAAC,KAAK,CAAC;EAC7C,CAAC,CAAC;AACH,CAAC,CAAC","ignoreList":[]}
|