framepayments-react-native 2.0.9 → 2.1.1

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 CHANGED
@@ -299,32 +299,37 @@ On non-Android platforms `Frame.presentGooglePay` rejects synchronously with a n
299
299
 
300
300
  ---
301
301
 
302
- ### `Frame.setTheme(theme)` (iOS)
302
+ ### Theming (iOS)
303
303
 
304
304
  Customizes colors, fonts, and corner radii on Frame's reusable components — checkout, cart, and the onboarding flow. Backed by `FrameTheme` introduced in Frame-iOS 2.1.2.
305
305
 
306
- Call once at app startup (after `Frame.initialize`). The theme applies to every subsequent `present*` call. Modals already on screen are not re-themed mid-flow. Pass `null` or `{}` to reset to defaults; pass a partial dict to override only specific tokens.
306
+ Pass an optional `theme` to `Frame.initialize`. It's stored on `FrameNetworking.shared` and read by every Frame view at present time, so all subsequent `present*` calls render with it. Modals already on screen are not re-themed if the theme is changed mid-flow. Omit the field, or pass `{}`, to use SDK defaults; pass a partial dict to override only specific tokens.
307
307
 
308
308
  ```ts
309
309
  import Frame from 'framepayments-react-native';
310
310
 
311
- await Frame.setTheme({
312
- colors: {
313
- primaryButton: '#5B2DFF',
314
- primaryButtonText: '#FFFFFF',
315
- surface: '#0A0A0A',
316
- textPrimary: '#FFFFFF',
317
- error: '#E53935',
318
- },
319
- fonts: {
320
- title: { name: 'Inter-Bold', size: 24 },
321
- button: { name: 'Inter-SemiBold', size: 16 },
311
+ await Frame.initialize({
312
+ secretKey: 'sk_sandbox_...',
313
+ publishableKey: 'pk_sandbox_...',
314
+ debugMode: __DEV__,
315
+ theme: {
316
+ colors: {
317
+ primaryButton: '#5B2DFF',
318
+ primaryButtonText: '#FFFFFF',
319
+ surface: '#0A0A0A',
320
+ textPrimary: '#FFFFFF',
321
+ error: '#E53935',
322
+ },
323
+ fonts: {
324
+ title: { name: 'Inter-Bold', size: 24 },
325
+ button: { name: 'Inter-SemiBold', size: 16 },
326
+ },
327
+ radii: { medium: 16 },
322
328
  },
323
- radii: { medium: 16 },
324
329
  });
325
330
  ```
326
331
 
327
- **Android**: `setTheme()` resolves immediately and has no effect — `frame-android` does not yet have a matching theme API.
332
+ **Android**: the `theme` field is accepted for cross-platform parity but currently has no effect — `frame-android` does not yet have a matching theme API.
328
333
 
329
334
  #### Tokens
330
335
 
@@ -31,7 +31,15 @@ class FrameSDKModule(reactContext: ReactApplicationContext) :
31
31
  }
32
32
 
33
33
  @ReactMethod
34
- fun initialize(secretKey: String, publishableKey: String, debugMode: Boolean, promise: Promise) {
34
+ fun initialize(
35
+ secretKey: String,
36
+ publishableKey: String,
37
+ debugMode: Boolean,
38
+ @Suppress("UNUSED_PARAMETER") theme: com.facebook.react.bridge.ReadableMap?,
39
+ promise: Promise
40
+ ) {
41
+ // theme is accepted for cross-platform parity with iOS but is currently a
42
+ // no-op on Android — frame-android does not yet have a matching theme API.
35
43
  try {
36
44
  val ctx = reactApplicationContext.applicationContext
37
45
  FrameNetworking.initializeWithAPIKey(ctx, secretKey, publishableKey, debugMode)
@@ -2,8 +2,9 @@
2
2
  // FrameRNTheme.swift
3
3
  // FrameReactNative
4
4
  //
5
- // Bridges JS theme dictionaries to Frame-iOS's FrameTheme and applies it to
6
- // every SwiftUI root view we present. Read/written only on the main thread.
5
+ // Parses JS theme dictionaries into Frame-iOS's FrameTheme. The parsed
6
+ // theme is handed to FrameNetworking.shared.configureTheme(_:); the SDK
7
+ // owns storage and applies it via its FrameThemeKey environment default.
7
8
  //
8
9
 
9
10
  import Foundation
@@ -11,12 +12,6 @@ import SwiftUI
11
12
  import Frame
12
13
 
13
14
  enum FrameRNTheme {
14
- // Main-thread only: written by FrameSDKBridge.setTheme (dispatched to main),
15
- // read by present* methods (already on main).
16
- static var current: FrameTheme? = nil
17
-
18
- static func resolved() -> FrameTheme { current ?? .default }
19
-
20
15
  static func parse(_ dict: [String: Any]) -> FrameTheme {
21
16
  var theme = FrameTheme.default
22
17
 
@@ -75,22 +70,6 @@ enum FrameRNTheme {
75
70
  }
76
71
  }
77
72
 
78
- // Applies the supplied FrameTheme to a single root view. Captured at present
79
- // time so each UIHostingController has a stable, concrete rootView type.
80
- struct ThemedRoot<Content: View>: View {
81
- let theme: FrameTheme
82
- let content: Content
83
-
84
- init(_ content: Content, theme: FrameTheme) {
85
- self.content = content
86
- self.theme = theme
87
- }
88
-
89
- var body: some View {
90
- content.frameTheme(theme)
91
- }
92
- }
93
-
94
73
  extension Color {
95
74
  // Accepts "#RGB", "#RRGGBB", "#RRGGBBAA" with or without the leading '#'.
96
75
  init?(rnHex hex: String) {
@@ -28,10 +28,7 @@
28
28
  RCT_EXTERN_METHOD(initialize:(NSString *)secretKey
29
29
  publishableKey:(NSString *)publishableKey
30
30
  debugMode:(BOOL)debugMode
31
- resolver:(RCTPromiseResolveBlock)resolve
32
- rejecter:(RCTPromiseRejectBlock)reject)
33
-
34
- RCT_EXTERN_METHOD(setTheme:(NSDictionary *)theme
31
+ theme:(NSDictionary *)theme
35
32
  resolver:(RCTPromiseResolveBlock)resolve
36
33
  rejecter:(RCTPromiseRejectBlock)reject)
37
34
 
@@ -47,11 +44,6 @@ RCT_EXTERN_METHOD(presentCart:(id)customerId
47
44
  rejecter:(RCTPromiseRejectBlock)reject)
48
45
 
49
46
  RCT_EXTERN_METHOD(presentOnboarding:(id)accountId
50
- capabilities:(NSArray *)capabilities
51
- resolver:(RCTPromiseResolveBlock)resolve
52
- rejecter:(RCTPromiseRejectBlock)reject)
53
-
54
- RCT_EXTERN_METHOD(presentOnboardingWithApplePay:(id)accountId
55
47
  capabilities:(NSArray *)capabilities
56
48
  applePayMerchantId:(id)applePayMerchantId
57
49
  resolver:(RCTPromiseResolveBlock)resolve
@@ -76,12 +68,8 @@ RCT_EXTERN_METHOD(presentApplePay:(NSString *)ownerType
76
68
  return YES;
77
69
  }
78
70
 
79
- - (void)initialize:(NSString *)secretKey publishableKey:(NSString *)publishableKey debugMode:(BOOL)debugMode resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
80
- [[[ObjCFrameSDKBridge alloc] init] initialize:secretKey publishableKey:publishableKey debugMode:debugMode resolver:resolve rejecter:reject];
81
- }
82
-
83
- - (void)setTheme:(NSDictionary *)theme resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
84
- [[[ObjCFrameSDKBridge alloc] init] setTheme:theme resolver:resolve rejecter:reject];
71
+ - (void)initialize:(NSString *)secretKey publishableKey:(NSString *)publishableKey debugMode:(BOOL)debugMode theme:(NSDictionary *)theme resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
72
+ [[[ObjCFrameSDKBridge alloc] init] initialize:secretKey publishableKey:publishableKey debugMode:debugMode theme:theme resolver:resolve rejecter:reject];
85
73
  }
86
74
 
87
75
  - (void)presentCheckout:(id)customerId amount:(NSNumber *)amount resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
@@ -107,25 +95,14 @@ RCT_EXTERN_METHOD(presentApplePay:(NSString *)ownerType
107
95
  });
108
96
  }
109
97
 
110
- - (void)presentOnboarding:(id)accountId capabilities:(NSArray *)capabilities resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
111
- dispatch_async(dispatch_get_main_queue(), ^{
112
- UIViewController *topVC = FrameGetTopViewController();
113
- if (!topVC) {
114
- reject(@"NO_ROOT_VC", @"Could not find root view controller to present onboarding", nil);
115
- return;
116
- }
117
- [[[ObjCFrameSDKBridge alloc] init] presentOnboardingFrom:topVC accountId:accountId capabilities:capabilities resolver:resolve rejecter:reject];
118
- });
119
- }
120
-
121
- - (void)presentOnboardingWithApplePay:(id)accountId capabilities:(NSArray *)capabilities applePayMerchantId:(id)applePayMerchantId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
98
+ - (void)presentOnboarding:(id)accountId capabilities:(NSArray *)capabilities applePayMerchantId:(id)applePayMerchantId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
122
99
  dispatch_async(dispatch_get_main_queue(), ^{
123
100
  UIViewController *topVC = FrameGetTopViewController();
124
101
  if (!topVC) {
125
102
  reject(@"NO_ROOT_VC", @"Could not find root view controller to present onboarding", nil);
126
103
  return;
127
104
  }
128
- [[[ObjCFrameSDKBridge alloc] init] presentOnboardingWithApplePayFrom:topVC accountId:accountId capabilities:capabilities applePayMerchantId:applePayMerchantId resolver:resolve rejecter:reject];
105
+ [[[ObjCFrameSDKBridge alloc] init] presentOnboardingFrom:topVC accountId:accountId capabilities:capabilities applePayMerchantId:applePayMerchantId resolver:resolve rejecter:reject];
129
106
  });
130
107
  }
131
108
 
@@ -20,18 +20,11 @@ public class FrameSDKBridge: NSObject {
20
20
  }
21
21
 
22
22
  @objc public
23
- func initialize(_ secretKey: String, publishableKey: String, debugMode: Bool, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
23
+ func initialize(_ secretKey: String, publishableKey: String, debugMode: Bool, theme: NSDictionary?, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
24
24
  DispatchQueue.main.async {
25
- FrameNetworking.shared.initializeWithAPIKey(secretKey, publishableKey: publishableKey, debugMode: debugMode)
26
- resolve(nil)
27
- }
28
- }
29
-
30
- @objc public
31
- func setTheme(_ theme: NSDictionary, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
32
- DispatchQueue.main.async {
33
- let dict = theme as? [String: Any] ?? [:]
34
- FrameRNTheme.current = dict.isEmpty ? nil : FrameRNTheme.parse(dict)
25
+ let themeDict = theme as? [String: Any] ?? [:]
26
+ let resolvedTheme = themeDict.isEmpty ? FrameTheme.default : FrameRNTheme.parse(themeDict)
27
+ FrameNetworking.shared.initializeWithAPIKey(secretKey, publishableKey: publishableKey, theme: resolvedTheme, debugMode: debugMode)
35
28
  resolve(nil)
36
29
  }
37
30
  }
@@ -51,14 +44,7 @@ public class FrameSDKBridge: NSObject {
51
44
  }
52
45
 
53
46
  @objc public
54
- func presentOnboarding(from viewController: UIViewController, accountId: NSObject?, capabilities: NSArray, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
55
- let parsedCapabilities = parseCapabilities(capabilities)
56
- let accountIdString = accountId as? String
57
- presentOnboardingOnMain(from: viewController, accountId: accountIdString, capabilities: parsedCapabilities, applePayMerchantId: nil, resolve: resolve, reject: reject)
58
- }
59
-
60
- @objc public
61
- func presentOnboardingWithApplePay(from viewController: UIViewController, accountId: NSObject?, capabilities: NSArray, applePayMerchantId: NSObject?, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
47
+ func presentOnboarding(from viewController: UIViewController, accountId: NSObject?, capabilities: NSArray, applePayMerchantId: NSObject?, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
62
48
  let parsedCapabilities = parseCapabilities(capabilities)
63
49
  let accountIdString = accountId as? String
64
50
  let merchantIdString = applePayMerchantId as? String
@@ -113,23 +99,20 @@ public class FrameSDKBridge: NSObject {
113
99
 
114
100
  private func presentCheckoutOnMain(from top: UIViewController, customerId: String?, amount: Int, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
115
101
  var hosting: CheckoutHostingController!
116
- hosting = CheckoutHostingController(rootView: ThemedRoot(
117
- FrameCheckoutView(
118
- customerId: customerId,
119
- paymentAmount: amount,
120
- checkoutCallback: { [weak hosting] chargeIntent in
121
- hosting?.didComplete = true
122
- top.dismiss(animated: true)
123
- DispatchQueue.main.async {
124
- if let dict = Self.encodeChargeIntent(chargeIntent) {
125
- resolve(dict)
126
- } else {
127
- reject("ENCODE_ERROR", "Failed to encode charge intent", nil)
128
- }
102
+ hosting = CheckoutHostingController(rootView: FrameCheckoutView(
103
+ customerId: customerId,
104
+ paymentAmount: amount,
105
+ checkoutCallback: { [weak hosting] chargeIntent in
106
+ hosting?.didComplete = true
107
+ top.dismiss(animated: true)
108
+ DispatchQueue.main.async {
109
+ if let dict = Self.encodeChargeIntent(chargeIntent) {
110
+ resolve(dict)
111
+ } else {
112
+ reject("ENCODE_ERROR", "Failed to encode charge intent", nil)
129
113
  }
130
114
  }
131
- ),
132
- theme: FrameRNTheme.resolved()
115
+ }
133
116
  ))
134
117
  hosting.onCancel = {
135
118
  DispatchQueue.main.async {
@@ -186,7 +169,7 @@ public class FrameSDKBridge: NSObject {
186
169
  cartItems: cartItems,
187
170
  shippingAmountInCents: shippingAmountInCents
188
171
  )
189
- let hosting = UIHostingController(rootView: ThemedRoot(cartView, theme: FrameRNTheme.resolved()))
172
+ let hosting = UIHostingController(rootView: cartView)
190
173
  hosting.modalPresentationStyle = UIModalPresentationStyle.pageSheet
191
174
  if let sheet = hosting.sheetPresentationController {
192
175
  sheet.detents = [UISheetPresentationController.Detent.large()]
@@ -200,20 +183,22 @@ public class FrameSDKBridge: NSObject {
200
183
  }
201
184
 
202
185
  private func presentOnboardingOnMain(from top: UIViewController, accountId: String?, capabilities: [FrameObjects.Capabilities], applePayMerchantId: String?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
203
- var hosting: OnboardingHostingController<ThemedRoot<OnboardingContainerView>>!
204
- var delegate: OnboardingDismissDelegate!
205
- hosting = OnboardingHostingController(
206
- rootView: ThemedRoot(
207
- OnboardingContainerView(
208
- accountId: accountId,
209
- requiredCapabilities: capabilities,
210
- applePayMerchantId: applePayMerchantId,
211
- onComplete: { [weak top] in
212
- delegate?.finish(completed: true)
213
- top?.dismiss(animated: true)
214
- }
215
- ),
216
- theme: FrameRNTheme.resolved()
186
+ // Build the dismiss delegate up-front so onComplete captures a non-nil
187
+ // instance directly. Earlier shape declared `var delegate: …!` and assigned
188
+ // it AFTER constructing OnboardingContainerView; the onComplete closure
189
+ // captured the local by reference, so any path that fired the callback
190
+ // before assignment landed on a nil delegate and silently no-op'd via `?.`.
191
+ let delegate = OnboardingDismissDelegate(resolve: resolve)
192
+
193
+ let hosting = OnboardingHostingController(
194
+ rootView: OnboardingContainerView(
195
+ accountId: accountId,
196
+ requiredCapabilities: capabilities,
197
+ applePayMerchantId: applePayMerchantId,
198
+ onComplete: { [delegate, weak top] in
199
+ delegate.finish(completed: true)
200
+ top?.dismiss(animated: true)
201
+ }
217
202
  )
218
203
  )
219
204
  // Embed in a UINavigationController so the outer sheet is a UIKit container,
@@ -229,7 +214,6 @@ public class FrameSDKBridge: NSObject {
229
214
  if let sheet = nav.sheetPresentationController {
230
215
  sheet.detents = [UISheetPresentationController.Detent.large()]
231
216
  }
232
- delegate = OnboardingDismissDelegate(resolve: resolve)
233
217
  delegate.hostingController = nav
234
218
  objc_setAssociatedObject(nav, &onboardingDismissKey, delegate, .OBJC_ASSOCIATION_RETAIN)
235
219
  NSLog("[FrameRN][onb] presenting OnboardingHostingController (wrapped in UINavigationController) from \(type(of: top))")
@@ -242,7 +226,7 @@ public class FrameSDKBridge: NSObject {
242
226
 
243
227
  // MARK: - CheckoutHostingController
244
228
 
245
- private final class CheckoutHostingController: UIHostingController<ThemedRoot<FrameCheckoutView>>, UIAdaptivePresentationControllerDelegate {
229
+ private final class CheckoutHostingController: UIHostingController<FrameCheckoutView>, UIAdaptivePresentationControllerDelegate {
246
230
  var didComplete = false
247
231
  var onCancel: (() -> Void)?
248
232
  private var cancelled = false
package/lib/index.d.ts CHANGED
@@ -7,12 +7,12 @@
7
7
  * - Use presentApplePay / presentGooglePay to launch the platform wallet sheet from your own button UI.
8
8
  * - For API calls (customers, charge intents, refunds), use the framepayments (frame-node) package from JS.
9
9
  */
10
- import { initialize, presentCheckout, presentCart, presentOnboarding, presentApplePay, presentGooglePay, setTheme } from './native';
11
- export { initialize, presentCheckout, presentCart, presentOnboarding, presentApplePay, presentGooglePay, setTheme, } from './native';
10
+ import { initialize, presentCheckout, presentCart, presentOnboarding, presentApplePay, presentGooglePay } from './native';
11
+ export { initialize, presentCheckout, presentCart, presentOnboarding, presentApplePay, presentGooglePay, } from './native';
12
12
  export type { FrameCartItem, ChargeIntent, ChargeIntentStatus, AuthorizationMode, FrameError, BillingAddress, PaymentCard, BankAccount, PaymentMethod, OnboardingCapability, OnboardingResult, OnboardingResultStatus, ApplePayOwner, PresentApplePayOptions, PresentGooglePayOptions, FrameTheme, FrameThemeColor, FrameThemeFont, FrameThemeColors, FrameThemeFonts, FrameThemeRadii, } from './types';
13
13
  export { ErrorCodes } from './errors';
14
14
  export type { FrameErrorShape, FrameErrorCode } from './errors';
15
- /** Default export for Frame.initialize(), Frame.presentCheckout(), Frame.presentCart(), Frame.presentOnboarding(), Frame.presentApplePay(), Frame.presentGooglePay(), Frame.setTheme() */
15
+ /** Default export for Frame.initialize(), Frame.presentCheckout(), Frame.presentCart(), Frame.presentOnboarding(), Frame.presentApplePay(), Frame.presentGooglePay() */
16
16
  declare const _default: {
17
17
  initialize: typeof initialize;
18
18
  presentCheckout: typeof presentCheckout;
@@ -20,7 +20,6 @@ declare const _default: {
20
20
  presentOnboarding: typeof presentOnboarding;
21
21
  presentApplePay: typeof presentApplePay;
22
22
  presentGooglePay: typeof presentGooglePay;
23
- setTheme: typeof setTheme;
24
23
  };
25
24
  export default _default;
26
25
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,UAAU,EACV,eAAe,EACf,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,QAAQ,EACT,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,UAAU,EACV,eAAe,EACf,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,QAAQ,GACT,MAAM,UAAU,CAAC;AAClB,YAAY,EACV,aAAa,EACb,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,UAAU,EACV,cAAc,EACd,WAAW,EACX,WAAW,EACX,aAAa,EACb,oBAAoB,EACpB,gBAAgB,EAChB,sBAAsB,EACtB,aAAa,EACb,sBAAsB,EACtB,uBAAuB,EACvB,UAAU,EACV,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,eAAe,GAChB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAEhE,0LAA0L;;;;;;;;;;AAC1L,wBAQE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,UAAU,EACV,eAAe,EACf,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EACjB,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,UAAU,EACV,eAAe,EACf,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,gBAAgB,GACjB,MAAM,UAAU,CAAC;AAClB,YAAY,EACV,aAAa,EACb,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,UAAU,EACV,cAAc,EACd,WAAW,EACX,WAAW,EACX,aAAa,EACb,oBAAoB,EACpB,gBAAgB,EAChB,sBAAsB,EACtB,aAAa,EACb,sBAAsB,EACtB,uBAAuB,EACvB,UAAU,EACV,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,eAAe,GAChB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAEhE,wKAAwK;;;;;;;;;AACxK,wBAOE"}
package/lib/index.js CHANGED
@@ -7,10 +7,10 @@
7
7
  * - Use presentApplePay / presentGooglePay to launch the platform wallet sheet from your own button UI.
8
8
  * - For API calls (customers, charge intents, refunds), use the framepayments (frame-node) package from JS.
9
9
  */
10
- import { initialize, presentCheckout, presentCart, presentOnboarding, presentApplePay, presentGooglePay, setTheme, } from './native';
11
- export { initialize, presentCheckout, presentCart, presentOnboarding, presentApplePay, presentGooglePay, setTheme, } from './native';
10
+ import { initialize, presentCheckout, presentCart, presentOnboarding, presentApplePay, presentGooglePay, } from './native';
11
+ export { initialize, presentCheckout, presentCart, presentOnboarding, presentApplePay, presentGooglePay, } from './native';
12
12
  export { ErrorCodes } from './errors';
13
- /** Default export for Frame.initialize(), Frame.presentCheckout(), Frame.presentCart(), Frame.presentOnboarding(), Frame.presentApplePay(), Frame.presentGooglePay(), Frame.setTheme() */
13
+ /** Default export for Frame.initialize(), Frame.presentCheckout(), Frame.presentCart(), Frame.presentOnboarding(), Frame.presentApplePay(), Frame.presentGooglePay() */
14
14
  export default {
15
15
  initialize,
16
16
  presentCheckout,
@@ -18,5 +18,4 @@ export default {
18
18
  presentOnboarding,
19
19
  presentApplePay,
20
20
  presentGooglePay,
21
- setTheme,
22
21
  };
package/lib/native.d.ts CHANGED
@@ -6,6 +6,13 @@ export declare function initialize(options: {
6
6
  secretKey: string;
7
7
  publishableKey: string;
8
8
  debugMode?: boolean;
9
+ /**
10
+ * Optional theme applied SDK-wide to Frame's reusable iOS components
11
+ * (checkout, cart, onboarding). Pass any subset — unspecified tokens fall
12
+ * back to SDK defaults. No-op on Android until frame-android ships a
13
+ * matching theme API.
14
+ */
15
+ theme?: FrameTheme;
9
16
  }): Promise<void>;
10
17
  export declare function presentCheckout(options: {
11
18
  customerId?: string | null;
@@ -24,13 +31,4 @@ export declare function presentOnboarding(options: {
24
31
  }): Promise<OnboardingResult>;
25
32
  export declare function presentApplePay(options: PresentApplePayOptions): Promise<ChargeIntent>;
26
33
  export declare function presentGooglePay(options: PresentGooglePayOptions): Promise<ChargeIntent>;
27
- /**
28
- * Configure colors, fonts, and corner radii for Frame's reusable iOS
29
- * components. Applied to every subsequent `present*` call; an in-flight modal
30
- * is not re-themed mid-flow.
31
- *
32
- * Pass `null` or `{}` to reset to SDK defaults. Android is a no-op until
33
- * frame-android ships a matching theme API.
34
- */
35
- export declare function setTheme(theme: FrameTheme | null): Promise<void>;
36
34
  //# sourceMappingURL=native.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,UAAU,EACV,oBAAoB,EACpB,gBAAgB,EAChB,sBAAsB,EACtB,uBAAuB,EACxB,MAAM,SAAS,CAAC;AAmBjB,wBAAgB,UAAU,CAAC,OAAO,EAAE;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBhB;AAwBD,wBAAgB,eAAe,CAAC,OAAO,EAAE;IACvC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,YAAY,CAAC,CAKxB;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE;IACnC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,qBAAqB,EAAE,MAAM,CAAC;CAC/B,GAAG,OAAO,CAAC,YAAY,CAAC,CASxB;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IACzC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,oBAAoB,EAAE,CAAC;IACtC,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA0B5B;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,YAAY,CAAC,CAoBtF;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,YAAY,CAAC,CAUxF;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAMhE"}
1
+ {"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,UAAU,EACV,oBAAoB,EACpB,gBAAgB,EAChB,sBAAsB,EACtB,uBAAuB,EACxB,MAAM,SAAS,CAAC;AAsBjB,wBAAgB,UAAU,CAAC,OAAO,EAAE;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBhB;AAwBD,wBAAgB,eAAe,CAAC,OAAO,EAAE;IACvC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,YAAY,CAAC,CAKxB;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE;IACnC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,qBAAqB,EAAE,MAAM,CAAC;CAC/B,GAAG,OAAO,CAAC,YAAY,CAAC,CASxB;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IACzC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,oBAAoB,EAAE,CAAC;IACtC,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAkB5B;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,YAAY,CAAC,CAoBtF;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,YAAY,CAAC,CAUxF"}
package/lib/native.js CHANGED
@@ -3,6 +3,8 @@
3
3
  */
4
4
  import { NativeModules, Platform } from 'react-native';
5
5
  import { ErrorCodes } from './errors';
6
+ // theme is iOS-only today: frame-android does not yet have a matching theme API,
7
+ // so the field is accepted on both platforms but ignored on Android until it does.
6
8
  const LINKING_ERROR = `The package 'framepayments-react-native' doesn't seem to be linked. Make sure you have run 'pod install' (iOS) or rebuilt the app (Android).`;
7
9
  const FrameSDK = NativeModules.FrameSDK
8
10
  ? NativeModules.FrameSDK
@@ -19,7 +21,10 @@ export function initialize(options) {
19
21
  if (!options?.publishableKey) {
20
22
  throw new Error('Frame.initialize requires publishableKey');
21
23
  }
22
- return wrapPromise(FrameSDK.initialize(options.secretKey, options.publishableKey, options.debugMode ?? false)).then(() => {
24
+ if (options.theme !== undefined && (typeof options.theme !== 'object' || Array.isArray(options.theme))) {
25
+ throw new Error('Frame.initialize: theme must be an object');
26
+ }
27
+ return wrapPromise(FrameSDK.initialize(options.secretKey, options.publishableKey, options.debugMode ?? false, options.theme ?? null)).then(() => {
23
28
  isInitialized = true;
24
29
  });
25
30
  }
@@ -53,11 +58,8 @@ export function presentCart(options) {
53
58
  }
54
59
  export function presentOnboarding(options) {
55
60
  guardInitialized();
56
- if (Platform.OS === 'ios' && options.applePayMerchantId) {
57
- return wrapPromise(FrameSDK.presentOnboardingWithApplePay(options.accountId ?? null, options.capabilities ?? [], options.applePayMerchantId));
58
- }
59
61
  if (Platform.OS === 'ios') {
60
- return wrapPromise(FrameSDK.presentOnboarding(options.accountId ?? null, options.capabilities ?? []));
62
+ return wrapPromise(FrameSDK.presentOnboarding(options.accountId ?? null, options.capabilities ?? [], options.applePayMerchantId ?? null));
61
63
  }
62
64
  return wrapPromise(FrameSDK.presentOnboarding(options.accountId ?? null, options.capabilities ?? [], options.googlePayMerchantId ?? null));
63
65
  }
@@ -78,19 +80,3 @@ export function presentGooglePay(options) {
78
80
  guardInitialized();
79
81
  return wrapPromise(FrameSDK.presentGooglePay(options.amountCents, options.customerId ?? null, options.currencyCode ?? 'USD', options.googlePayMerchantId ?? null));
80
82
  }
81
- /**
82
- * Configure colors, fonts, and corner radii for Frame's reusable iOS
83
- * components. Applied to every subsequent `present*` call; an in-flight modal
84
- * is not re-themed mid-flow.
85
- *
86
- * Pass `null` or `{}` to reset to SDK defaults. Android is a no-op until
87
- * frame-android ships a matching theme API.
88
- */
89
- export function setTheme(theme) {
90
- if (theme !== null && (typeof theme !== 'object' || Array.isArray(theme))) {
91
- throw new Error('Frame.setTheme requires a theme object or null');
92
- }
93
- if (Platform.OS !== 'ios')
94
- return Promise.resolve();
95
- return wrapPromise(FrameSDK.setTheme(theme ?? {}));
96
- }
package/package.json CHANGED
@@ -1,14 +1,16 @@
1
1
  {
2
2
  "name": "framepayments-react-native",
3
- "version": "2.0.9",
3
+ "version": "2.1.1",
4
4
  "description": "React Native SDK for Frame Payments - modal checkout and cart, with API usage via frame-node.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
7
7
  "scripts": {
8
8
  "build": "tsc",
9
+ "watch": "tsc --watch",
9
10
  "typecheck": "tsc --noEmit",
10
11
  "lint": "eslint src --ext .ts,.tsx",
11
12
  "test": "jest",
13
+ "prepare": "npm run build",
12
14
  "prepublishOnly": "npm run build"
13
15
  },
14
16
  "keywords": [
@@ -6,8 +6,7 @@
6
6
  const mockInitialize = jest.fn((_secretKey: string, _publishableKey: string, _debugMode: boolean) => Promise.resolve());
7
7
  const mockPresentCheckout = jest.fn((_customerId: unknown, _amount: number) => Promise.resolve({ id: 'ci_1', amount: 10000 }));
8
8
  const mockPresentCart = jest.fn((_customerId: unknown, _items: unknown[], _shipping: number) => Promise.resolve({ id: 'ci_2', amount: 15000 }));
9
- const mockPresentOnboarding = jest.fn((_accountId: unknown, _capabilities: unknown[]) => Promise.resolve({ status: 'completed', paymentMethodId: 'pm_1' }));
10
- const mockPresentOnboardingWithApplePay = jest.fn((_accountId: unknown, _capabilities: unknown[], _merchantId: string) => Promise.resolve({ status: 'completed', paymentMethodId: 'pm_2' }));
9
+ const mockPresentOnboarding = jest.fn((_accountId: unknown, _capabilities: unknown[], _merchantId: string | null) => Promise.resolve({ status: 'completed', paymentMethodId: 'pm_1' }));
11
10
 
12
11
  const mockPlatform = { OS: 'ios' as 'ios' | 'android' };
13
12
 
@@ -18,7 +17,6 @@ jest.mock('react-native', () => ({
18
17
  presentCheckout: mockPresentCheckout,
19
18
  presentCart: mockPresentCart,
20
19
  presentOnboarding: mockPresentOnboarding,
21
- presentOnboardingWithApplePay: mockPresentOnboardingWithApplePay,
22
20
  },
23
21
  },
24
22
  Platform: mockPlatform,
@@ -40,7 +38,6 @@ beforeEach(() => {
40
38
  mockPresentCheckout.mockClear();
41
39
  mockPresentCart.mockClear();
42
40
  mockPresentOnboarding.mockClear();
43
- mockPresentOnboardingWithApplePay.mockClear();
44
41
  mockPlatform.OS = 'ios';
45
42
  const native = require('../native');
46
43
  initialize = native.initialize;
@@ -145,33 +142,33 @@ describe('presentOnboarding', () => {
145
142
  expect(mockPresentOnboarding).not.toHaveBeenCalled();
146
143
  });
147
144
 
148
- it('calls native presentOnboarding with accountId and capabilities after initialize', async () => {
145
+ it('calls native presentOnboarding with accountId, capabilities, and null merchantId after initialize on iOS', async () => {
146
+ mockPlatform.OS = 'ios';
149
147
  await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
150
148
  const result = await presentOnboarding({ accountId: 'acct_1', capabilities: ['kyc', 'bank_account_verification'] });
151
- expect(mockPresentOnboarding).toHaveBeenCalledWith('acct_1', ['kyc', 'bank_account_verification']);
149
+ expect(mockPresentOnboarding).toHaveBeenCalledWith('acct_1', ['kyc', 'bank_account_verification'], null);
152
150
  expect(result).toEqual({ status: 'completed', paymentMethodId: 'pm_1' });
153
151
  });
154
152
 
155
- it('passes null for accountId and empty array for capabilities when not provided', async () => {
153
+ it('passes null for accountId and empty array for capabilities when not provided on iOS', async () => {
154
+ mockPlatform.OS = 'ios';
156
155
  await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
157
156
  await presentOnboarding({});
158
- expect(mockPresentOnboarding).toHaveBeenCalledWith(null, []);
157
+ expect(mockPresentOnboarding).toHaveBeenCalledWith(null, [], null);
159
158
  });
160
159
 
161
- it('routes to presentOnboardingWithApplePay on iOS when applePayMerchantId is set', async () => {
160
+ it('forwards applePayMerchantId on iOS', async () => {
162
161
  mockPlatform.OS = 'ios';
163
162
  await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
164
163
  await presentOnboarding({ accountId: 'acct_1', capabilities: ['kyc'], applePayMerchantId: 'merchant.com.example' });
165
- expect(mockPresentOnboardingWithApplePay).toHaveBeenCalledWith('acct_1', ['kyc'], 'merchant.com.example');
166
- expect(mockPresentOnboarding).not.toHaveBeenCalled();
164
+ expect(mockPresentOnboarding).toHaveBeenCalledWith('acct_1', ['kyc'], 'merchant.com.example');
167
165
  });
168
166
 
169
- it('still routes to presentOnboarding on Android even when applePayMerchantId is set', async () => {
167
+ it('ignores applePayMerchantId on Android', async () => {
170
168
  mockPlatform.OS = 'android';
171
169
  await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
172
170
  await presentOnboarding({ accountId: 'acct_1', capabilities: ['kyc'], applePayMerchantId: 'merchant.com.example' });
173
171
  expect(mockPresentOnboarding).toHaveBeenCalledWith('acct_1', ['kyc'], null);
174
- expect(mockPresentOnboardingWithApplePay).not.toHaveBeenCalled();
175
172
  });
176
173
 
177
174
  it('forwards googlePayMerchantId to native presentOnboarding on Android', async () => {
@@ -181,10 +178,10 @@ describe('presentOnboarding', () => {
181
178
  expect(mockPresentOnboarding).toHaveBeenCalledWith('acct_1', ['kyc'], 'BCR2DN4T...');
182
179
  });
183
180
 
184
- it('does not forward googlePayMerchantId to native presentOnboarding on iOS', async () => {
181
+ it('ignores googlePayMerchantId on iOS', async () => {
185
182
  mockPlatform.OS = 'ios';
186
183
  await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
187
184
  await presentOnboarding({ accountId: 'acct_1', capabilities: ['kyc'], googlePayMerchantId: 'BCR2DN4T...' });
188
- expect(mockPresentOnboarding).toHaveBeenCalledWith('acct_1', ['kyc']);
185
+ expect(mockPresentOnboarding).toHaveBeenCalledWith('acct_1', ['kyc'], null);
189
186
  });
190
187
  });
package/src/index.ts CHANGED
@@ -15,7 +15,6 @@ import {
15
15
  presentOnboarding,
16
16
  presentApplePay,
17
17
  presentGooglePay,
18
- setTheme,
19
18
  } from './native';
20
19
 
21
20
  export {
@@ -25,7 +24,6 @@ export {
25
24
  presentOnboarding,
26
25
  presentApplePay,
27
26
  presentGooglePay,
28
- setTheme,
29
27
  } from './native';
30
28
  export type {
31
29
  FrameCartItem,
@@ -53,7 +51,7 @@ export type {
53
51
  export { ErrorCodes } from './errors';
54
52
  export type { FrameErrorShape, FrameErrorCode } from './errors';
55
53
 
56
- /** Default export for Frame.initialize(), Frame.presentCheckout(), Frame.presentCart(), Frame.presentOnboarding(), Frame.presentApplePay(), Frame.presentGooglePay(), Frame.setTheme() */
54
+ /** Default export for Frame.initialize(), Frame.presentCheckout(), Frame.presentCart(), Frame.presentOnboarding(), Frame.presentApplePay(), Frame.presentGooglePay() */
57
55
  export default {
58
56
  initialize,
59
57
  presentCheckout,
@@ -61,5 +59,4 @@ export default {
61
59
  presentOnboarding,
62
60
  presentApplePay,
63
61
  presentGooglePay,
64
- setTheme,
65
62
  };
package/src/native.ts CHANGED
@@ -14,6 +14,9 @@ import type {
14
14
  } from './types';
15
15
  import { ErrorCodes } from './errors';
16
16
 
17
+ // theme is iOS-only today: frame-android does not yet have a matching theme API,
18
+ // so the field is accepted on both platforms but ignored on Android until it does.
19
+
17
20
  const LINKING_ERROR =
18
21
  `The package 'framepayments-react-native' doesn't seem to be linked. Make sure you have run 'pod install' (iOS) or rebuilt the app (Android).`;
19
22
 
@@ -34,6 +37,13 @@ export function initialize(options: {
34
37
  secretKey: string;
35
38
  publishableKey: string;
36
39
  debugMode?: boolean;
40
+ /**
41
+ * Optional theme applied SDK-wide to Frame's reusable iOS components
42
+ * (checkout, cart, onboarding). Pass any subset — unspecified tokens fall
43
+ * back to SDK defaults. No-op on Android until frame-android ships a
44
+ * matching theme API.
45
+ */
46
+ theme?: FrameTheme;
37
47
  }): Promise<void> {
38
48
  if (!options?.secretKey) {
39
49
  throw new Error('Frame.initialize requires secretKey');
@@ -41,11 +51,15 @@ export function initialize(options: {
41
51
  if (!options?.publishableKey) {
42
52
  throw new Error('Frame.initialize requires publishableKey');
43
53
  }
54
+ if (options.theme !== undefined && (typeof options.theme !== 'object' || Array.isArray(options.theme))) {
55
+ throw new Error('Frame.initialize: theme must be an object');
56
+ }
44
57
  return wrapPromise(
45
58
  FrameSDK.initialize(
46
59
  options.secretKey,
47
60
  options.publishableKey,
48
- options.debugMode ?? false
61
+ options.debugMode ?? false,
62
+ options.theme ?? null
49
63
  )
50
64
  ).then(() => {
51
65
  isInitialized = true;
@@ -106,20 +120,12 @@ export function presentOnboarding(options: {
106
120
  googlePayMerchantId?: string | null;
107
121
  }): Promise<OnboardingResult> {
108
122
  guardInitialized();
109
- if (Platform.OS === 'ios' && options.applePayMerchantId) {
110
- return wrapPromise(
111
- FrameSDK.presentOnboardingWithApplePay(
112
- options.accountId ?? null,
113
- options.capabilities ?? [],
114
- options.applePayMerchantId
115
- )
116
- );
117
- }
118
123
  if (Platform.OS === 'ios') {
119
124
  return wrapPromise(
120
125
  FrameSDK.presentOnboarding(
121
126
  options.accountId ?? null,
122
- options.capabilities ?? []
127
+ options.capabilities ?? [],
128
+ options.applePayMerchantId ?? null
123
129
  )
124
130
  );
125
131
  }
@@ -166,18 +172,3 @@ export function presentGooglePay(options: PresentGooglePayOptions): Promise<Char
166
172
  );
167
173
  }
168
174
 
169
- /**
170
- * Configure colors, fonts, and corner radii for Frame's reusable iOS
171
- * components. Applied to every subsequent `present*` call; an in-flight modal
172
- * is not re-themed mid-flow.
173
- *
174
- * Pass `null` or `{}` to reset to SDK defaults. Android is a no-op until
175
- * frame-android ships a matching theme API.
176
- */
177
- export function setTheme(theme: FrameTheme | null): Promise<void> {
178
- if (theme !== null && (typeof theme !== 'object' || Array.isArray(theme))) {
179
- throw new Error('Frame.setTheme requires a theme object or null');
180
- }
181
- if (Platform.OS !== 'ios') return Promise.resolve();
182
- return wrapPromise(FrameSDK.setTheme(theme ?? {}));
183
- }