framepayments-react-native 3.0.0 → 3.0.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/Package.swift CHANGED
@@ -27,7 +27,7 @@ let package = Package(
27
27
  dependencies: [
28
28
  .package(
29
29
  url: "https://github.com/Frame-Payments/frame-ios",
30
- from: "2.2.2" // Keep in sync with package.json:frameNativeVersions.ios
30
+ from: "2.2.3" // Keep in sync with package.json:frameNativeVersions.ios
31
31
  )
32
32
  ],
33
33
  targets: [
package/README.md CHANGED
@@ -91,6 +91,8 @@ Initializes the native SDK. Must be called before any `present*` method. Call on
91
91
  await Frame.initialize({
92
92
  secretKey: 'sk_sandbox_...', // your Frame secret key
93
93
  publishableKey: 'pk_sandbox_...', // your Frame publishable key
94
+ applePayMerchantId: 'merchant.com.yourapp', // optional — see Apple Pay section
95
+ googlePayMerchantId: 'BCR2DN4T...', // optional — see Google Pay section
94
96
  debugMode: false, // set true in development to enable native debug logging
95
97
  });
96
98
  ```
@@ -99,6 +101,8 @@ await Frame.initialize({
99
101
  |---|---|---|---|
100
102
  | `secretKey` | `string` | Yes | Your Frame secret key (`sk_…`). Used for server-style operations. |
101
103
  | `publishableKey` | `string` | Yes | Your Frame publishable key (`pk_…`). Used for client-side operations like wallet payments. |
104
+ | `applePayMerchantId` | `string` | No | Apple Pay merchant identifier (`merchant.com.…`). Single source of truth for every Apple Pay surface — `presentApplePay`, the bundled checkout's wallet row, the onboarding wallet attach button. iOS-only; ignored on Android. |
105
+ | `googlePayMerchantId` | `string` | No | Google Pay merchant identifier from the Google Pay & Wallet Console. Single source of truth for every Google Pay surface — `presentGooglePay`, the bundled checkout's wallet row, the onboarding wallet attach button. Android-only; ignored on iOS. |
102
106
  | `debugMode` | `boolean` | No | Enables native debug logging and routes wallet flows through sandbox/test environments. Default: `false`. |
103
107
 
104
108
  ---
@@ -184,7 +188,8 @@ if (result.status === 'completed') {
184
188
  |---|---|---|---|
185
189
  | `accountId` | `string` | No | The Frame account to onboard |
186
190
  | `capabilities` | `OnboardingCapability[]` | No | Which onboarding steps to include (see below) |
187
- | `applePayMerchantId` | `string` | No | Apple Pay merchant ID. When set, the onboarding flow includes an Apple Pay setup step. iOS only — ignored on Android. Same prerequisites as `presentApplePay`. |
191
+
192
+ The Apple Pay / Google Pay wallet attach steps are rendered automatically when the corresponding merchant ID was passed to `Frame.initialize`. No per-call merchant params here.
188
193
 
189
194
  **`capabilities` values:**
190
195
 
@@ -217,6 +222,8 @@ if (result.status === 'completed') {
217
222
 
218
223
  Launches the native Apple Pay sheet, creates a Frame payment method from the authorized payment, and creates a charge against the owner. Resolves with the resulting resource's id string. Render your own button — Apple's `PKPaymentButton`, a community wrapper, or your own design-system component — and call this from its `onPress`.
219
224
 
225
+ The Apple Pay merchant ID is configured **once** at `Frame.initialize({ applePayMerchantId })`; there is no per-call merchant parameter.
226
+
220
227
  The `owner` determines which downstream resource is created:
221
228
 
222
229
  - `owner.type === 'customer'` → creates a `ChargeIntent` against the customer; resolves with the ChargeIntent's `id`.
@@ -232,7 +239,6 @@ const transferId = await Frame.presentApplePay({
232
239
  amount: 15000,
233
240
  currency: 'usd',
234
241
  owner: { type: 'account', id: 'acct_xxx' },
235
- merchantId: 'merchant.com.yourapp',
236
242
  });
237
243
 
238
244
  // Customer → ChargeIntent
@@ -240,7 +246,6 @@ const chargeIntentId = await Frame.presentApplePay({
240
246
  amount: 15000,
241
247
  currency: 'usd',
242
248
  owner: { type: 'customer', id: 'cus_xxx' },
243
- merchantId: 'merchant.com.yourapp',
244
249
  });
245
250
  ```
246
251
 
@@ -249,23 +254,42 @@ const chargeIntentId = await Frame.presentApplePay({
249
254
  | `amount` | `number` | Yes | Payment amount in cents |
250
255
  | `currency` | `string` | No | ISO 4217 currency code. Default `'usd'` |
251
256
  | `owner` | `{ type: 'customer' \| 'account', id: string }` | Yes | Customer or account that owns the resulting payment method and charge |
252
- | `merchantId` | `string` | Yes | Apple Pay merchant ID configured in your Apple Developer account |
253
257
 
254
258
  **Returns:** `Promise<string>` — the created `ChargeIntent`'s `id` (for customer owners) or the `Transfer`'s `id` (for account owners).
255
259
 
256
- The promise rejects with `code: 'USER_CANCELED'` when the user dismisses the sheet, `'INVALID_OWNER'` if the owner is missing or malformed, `'APPLE_PAY_UNAVAILABLE'` if the device cannot make Apple Pay payments, `'NOT_ATTESTED'` if device attestation has not completed yet (try again in a moment), `'PAYMENT_METHOD_FAILED'` when the wallet payment method could not be persisted, and `'PAYMENT_FAILED'` when the downstream ChargeIntent or Transfer could not be created.
260
+ The promise rejects with `code: 'USER_CANCELED'` when the user dismisses the sheet, `'INVALID_OWNER'` if the owner is missing or malformed, `'INVALID_MERCHANT_ID'` if `applePayMerchantId` was not configured at `Frame.initialize`, `'APPLE_PAY_UNAVAILABLE'` if the device cannot make Apple Pay payments, `'NOT_ATTESTED'` if device attestation has not completed yet (try again in a moment), `'PAYMENT_METHOD_FAILED'` when the wallet payment method could not be persisted, and `'PAYMENT_FAILED'` when the downstream ChargeIntent or Transfer could not be created.
257
261
 
258
- **Required iOS setup:**
262
+ #### Required iOS setup
259
263
 
260
- 1. **Apple Pay capability + merchant ID.** Add Apple Pay in your target's *Signing & Capabilities*, and register a merchant ID in your Apple Developer account.
261
- 2. **App Attest entitlement.** The Frame iOS SDK uses Apple's App Attest for device attestation on every Apple Pay payment. Add to your `.entitlements`:
264
+ Apple Pay setup is a four-part process: create a merchant ID with Apple, configure your Xcode project, pass the merchant ID to `Frame.initialize`, then **contact Frame** to enable the feature on your account.
265
+
266
+ 1. **Create a merchant identifier.** Sign in to [developer.apple.com → Certificates, Identifiers & Profiles → Identifiers → Merchant IDs](https://developer.apple.com/account/resources/identifiers/list/merchant), click **+**, choose **Merchant IDs**, and create one in reverse-DNS form (e.g. `merchant.com.yourapp`).
267
+ 2. **Add the Apple Pay capability in Xcode.** Open your project, select your **app target**, go to **Signing & Capabilities** → **+ Capability** → **Apple Pay**, and add the merchant ID you created in step 1. Xcode writes it into your entitlements file:
268
+ ```xml
269
+ <key>com.apple.developer.in-app-payments</key>
270
+ <array>
271
+ <string>merchant.com.yourapp</string>
272
+ </array>
273
+ ```
274
+ 3. **App Attest entitlement.** Frame uses Apple's App Attest for device attestation on every Apple Pay payment. Add to your `.entitlements`:
262
275
  ```xml
263
276
  <key>com.apple.developer.devicecheck.appattest-environment</key>
264
277
  <string>development</string>
265
278
  ```
266
- Use `production` for App Store builds.
267
- 3. **Real device required.** App Attest does not work in the simulator.
268
- 4. **Frame dashboard configuration.** In your Frame dashboard, go to **Settings → Device Attestation** and set your Apple Team ID and the Bundle ID of your iOS app. These must exactly match the app you're running the backend computes `SHA256("<TeamID>.<BundleID>")` and compares it to the hash signed by the device. If they don't match, payment-method creation fails with `App ID verification failed`.
279
+ Use `production` for App Store builds. App Attest does not work in the simulator — Apple Pay requires a real device.
280
+ 4. **Frame dashboard — device attestation.** In your Frame dashboard, **Settings Device Attestation**, set your Apple Team ID and the Bundle ID of your iOS app. These must exactly match the app you're running — the backend computes `SHA256("<TeamID>.<BundleID>")` and compares it to the hash signed by the device. If they don't match, payment-method creation fails with `App ID verification failed`.
281
+ 5. **Pass the merchant ID to `Frame.initialize`.** This is the single source of truthevery Frame Apple Pay surface reads it from here.
282
+ ```ts
283
+ await Frame.initialize({
284
+ secretKey: 'sk_...',
285
+ publishableKey: 'pk_...',
286
+ applePayMerchantId: 'merchant.com.yourapp',
287
+ });
288
+ ```
289
+
290
+ #### Enabling Apple Pay on your account
291
+
292
+ Once the steps above are complete, contact Frame at [support@framepayments.com](mailto:support@framepayments.com) (or via your [Frame dashboard](https://framepayments.com)) and we'll enable Apple Pay on your account. Apple Pay charges won't succeed until this is done on our side.
269
293
 
270
294
  On non-iOS platforms `Frame.presentApplePay` rejects synchronously with a not-supported error.
271
295
 
@@ -275,6 +299,8 @@ On non-iOS platforms `Frame.presentApplePay` rejects synchronously with a not-su
275
299
 
276
300
  Launches the native Google Pay sheet, creates a Frame payment method from the wallet token, and creates a charge against the owner. Resolves with the resulting resource's id string. Render your own button (Google's `PayButton` from `play-services-pay`, a community wrapper, or your own component) and call this from its `onPress`.
277
301
 
302
+ The Google Pay merchant ID is configured **once** at `Frame.initialize({ googlePayMerchantId })`; there is no per-call merchant parameter.
303
+
278
304
  The `owner` mirrors `presentApplePay` and determines which downstream resource is created:
279
305
 
280
306
  - `owner.type === 'customer'` → creates a `ChargeIntent` against the customer; resolves with the ChargeIntent's `id`.
@@ -302,21 +328,36 @@ const chargeIntentId = await Frame.presentGooglePay({
302
328
  | `amountCents` | `number` | Yes | Payment amount in cents |
303
329
  | `owner` | `{ type: 'customer' \| 'account', id: string }` | Yes | Customer or account that owns the resulting payment method and charge |
304
330
  | `currencyCode` | `string` | No | ISO 4217 currency code. Default `'USD'` |
305
- | `googlePayMerchantId` | `string` | No | Google Pay merchant ID override |
306
331
 
307
332
  **Returns:** `Promise<string>` — the created `ChargeIntent`'s `id` (for customer owners) or the `Transfer`'s `id` (for account owners).
308
333
 
309
- The promise rejects with `code: 'USER_CANCELED'` when the user dismisses the sheet, `'INVALID_OWNER'` if the owner is missing or malformed, `'GOOGLE_PAY_UNAVAILABLE'` if Google Pay is not ready on the device (no signed-in account, no test card, or Wallet API disabled), and `'PAYMENT_FAILED'` for backend failures.
334
+ The promise rejects with `code: 'USER_CANCELED'` when the user dismisses the sheet, `'INVALID_OWNER'` if the owner is missing or malformed, `'GOOGLE_PAY_UNAVAILABLE'` if Google Pay is not ready on the device (no signed-in account, no test card, no `googlePayMerchantId` configured at `Frame.initialize`, or Wallet API disabled in the manifest), and `'PAYMENT_FAILED'` for backend failures.
335
+
336
+ #### Required Android setup
310
337
 
311
- **Required Android setup:**
338
+ Google Pay setup is a four-part process: get a merchant ID from Google, declare the wallet capability in your manifest, pass the merchant ID to `Frame.initialize`, then **contact Frame** to enable the feature on your account.
312
339
 
313
- 1. **Google Pay metadata.** Add to your app's `AndroidManifest.xml` inside `<application>`:
340
+ 1. **Obtain a Google Pay merchant ID.** Sign up for a [Google Pay & Wallet Console](https://pay.google.com/business/console/) account, complete the business profile, and accept the Google Pay API Terms of Service. Your **Merchant ID** (looks like `BCR2DN4T…`) appears on the Business Console home page once approved.
341
+ 2. **Declare the wallet capability in `AndroidManifest.xml`.** Inside `<application>`:
314
342
  ```xml
315
343
  <meta-data
316
344
  android:name="com.google.android.gms.wallet.api.enabled"
317
345
  android:value="true" />
318
346
  ```
319
- 2. **Test environment.** When the SDK is initialized with `debugMode: true`, Google Pay runs in `ENVIRONMENT_TEST`; otherwise it uses `ENVIRONMENT_PRODUCTION`.
347
+ Without this entry the Google Pay button stays hidden the Wallet API is opted-out by default.
348
+ 3. **Test environment.** When the SDK is initialized with `debugMode: true`, Google Pay runs in `ENVIRONMENT_TEST`; otherwise it uses `ENVIRONMENT_PRODUCTION`.
349
+ 4. **Pass the merchant ID to `Frame.initialize`.** This is the single source of truth — every Frame Google Pay surface reads it from here.
350
+ ```ts
351
+ await Frame.initialize({
352
+ secretKey: 'sk_...',
353
+ publishableKey: 'pk_...',
354
+ googlePayMerchantId: 'BCR2DN4T...',
355
+ });
356
+ ```
357
+
358
+ #### Enabling Google Pay on your account
359
+
360
+ Once the steps above are complete, contact Frame at [support@framepayments.com](mailto:support@framepayments.com) (or via your [Frame dashboard](https://framepayments.com)) and we'll enable Google Pay on your account. Google Pay charges won't succeed until this is done on our side.
320
361
 
321
362
  On non-Android platforms `Frame.presentGooglePay` rejects synchronously with a not-supported error.
322
363
 
@@ -7,6 +7,7 @@ import android.os.Looper
7
7
  import android.widget.FrameLayout
8
8
  import androidx.appcompat.app.AppCompatActivity
9
9
  import com.framepayments.framesdk.FrameNetworking
10
+ import com.framepayments.framesdk.FrameResult
10
11
  import com.framepayments.framesdk_ui.FrameCheckoutView
11
12
 
12
13
  class FrameCheckoutActivity : AppCompatActivity() {
@@ -68,9 +69,23 @@ class FrameCheckoutActivity : AppCompatActivity() {
68
69
  private fun addCheckoutView(container: FrameLayout, accountId: String, amount: Int) {
69
70
  val checkoutView = FrameCheckoutView(this)
70
71
  FrameRNTheme.current?.let { checkoutView.setTheme(it) }
71
- checkoutView.configure(accountId, amount) { transferId ->
72
- setResult(RESULT_OK, Intent().putExtra(EXTRA_TRANSFER_ID, transferId))
73
- finish()
72
+ checkoutView.configure(accountId, amount) { result ->
73
+ when (result) {
74
+ is FrameResult.Completed -> {
75
+ setResult(RESULT_OK, Intent().putExtra(EXTRA_TRANSFER_ID, result.id))
76
+ finish()
77
+ }
78
+ FrameResult.Cancelled -> {
79
+ setResult(RESULT_CANCELED)
80
+ finish()
81
+ }
82
+ is FrameResult.Failed -> {
83
+ // Surfaced as USER_CANCELED at the JS layer (RESULT_CANCELED maps to it in FrameSDKModule).
84
+ // Per-error toast UI is already shown by the native FrameCheckoutView before this fires.
85
+ setResult(RESULT_CANCELED)
86
+ finish()
87
+ }
88
+ }
74
89
  }
75
90
  container.addView(checkoutView)
76
91
  }
@@ -4,6 +4,7 @@ import android.content.Intent
4
4
  import android.os.Bundle
5
5
  import android.widget.FrameLayout
6
6
  import androidx.appcompat.app.AppCompatActivity
7
+ import com.framepayments.framesdk.FrameResult
7
8
  import com.framepayments.framesdk_ui.FrameCartItem
8
9
  import com.framepayments.framesdk_ui.FrameCartView
9
10
  import com.framepayments.framesdk_ui.FrameCheckoutView
@@ -75,9 +76,21 @@ class FrameFlowActivity : AppCompatActivity() {
75
76
  container.removeAllViews()
76
77
  checkoutView = FrameCheckoutView(this).apply {
77
78
  FrameRNTheme.current?.let { setTheme(it) }
78
- configure(accountId, amount) { transferId ->
79
- setResult(RESULT_OK, Intent().putExtra(EXTRA_TRANSFER_ID, transferId))
80
- finish()
79
+ configure(accountId, amount) { result ->
80
+ when (result) {
81
+ is FrameResult.Completed -> {
82
+ setResult(RESULT_OK, Intent().putExtra(EXTRA_TRANSFER_ID, result.id))
83
+ finish()
84
+ }
85
+ FrameResult.Cancelled -> {
86
+ setResult(RESULT_CANCELED)
87
+ finish()
88
+ }
89
+ is FrameResult.Failed -> {
90
+ setResult(RESULT_CANCELED)
91
+ finish()
92
+ }
93
+ }
81
94
  }
82
95
  }
83
96
  container.addView(checkoutView)
@@ -40,7 +40,6 @@ class FrameGooglePayActivity : AppCompatActivity() {
40
40
  val ownerType = intent.getStringExtra(EXTRA_OWNER_TYPE)
41
41
  val ownerId = intent.getStringExtra(EXTRA_OWNER_ID)
42
42
  val currencyCode = intent.getStringExtra(EXTRA_CURRENCY) ?: "USD"
43
- val googlePayMerchantId = intent.getStringExtra(EXTRA_MERCHANT_ID)
44
43
 
45
44
  if (amountCents <= 0) {
46
45
  deliverFailure("Invalid amountCents")
@@ -68,7 +67,6 @@ class FrameGooglePayActivity : AppCompatActivity() {
68
67
  amountCents = amountCents,
69
68
  owner = owner,
70
69
  currencyCode = currencyCode,
71
- googlePayMerchantId = googlePayMerchantId,
72
70
  onResult = { result -> handleResult(result) },
73
71
  onReadinessChanged = { isReady -> handleReadiness(isReady) }
74
72
  )
@@ -153,7 +151,6 @@ class FrameGooglePayActivity : AppCompatActivity() {
153
151
  const val EXTRA_OWNER_TYPE = "owner_type"
154
152
  const val EXTRA_OWNER_ID = "owner_id"
155
153
  const val EXTRA_CURRENCY = "currency"
156
- const val EXTRA_MERCHANT_ID = "merchant_id"
157
154
  /**
158
155
  * The id of the resource created by the wallet flow. Holds a Transfer id when
159
156
  * the owner was an account, or a ChargeIntent id when the owner was a customer.
@@ -19,12 +19,10 @@ class FrameOnboardingActivity : ComponentActivity() {
19
19
  val accountId = intent.getStringExtra(EXTRA_ACCOUNT_ID)
20
20
  val capabilitiesJson = intent.getStringExtra(EXTRA_CAPABILITIES_JSON) ?: "[]"
21
21
  val capabilities = parseCapabilities(capabilitiesJson)
22
- val googlePayMerchantId = intent.getStringExtra(EXTRA_GOOGLE_PAY_MERCHANT_ID)
23
22
 
24
23
  val config = OnboardingConfig(
25
24
  accountId = accountId,
26
- requiredCapabilities = capabilities,
27
- googlePayMerchantId = googlePayMerchantId
25
+ requiredCapabilities = capabilities
28
26
  )
29
27
 
30
28
  val themeOverride = FrameRNTheme.current
@@ -44,7 +42,7 @@ class FrameOnboardingActivity : ComponentActivity() {
44
42
  setResult(RESULT_CANCELED)
45
43
  finish()
46
44
  }
47
- is OnboardingResult.Error -> {
45
+ is OnboardingResult.Failed -> {
48
46
  setResult(RESULT_CANCELED)
49
47
  finish()
50
48
  }
@@ -69,7 +67,6 @@ class FrameOnboardingActivity : ComponentActivity() {
69
67
  companion object {
70
68
  const val EXTRA_ACCOUNT_ID = "account_id"
71
69
  const val EXTRA_CAPABILITIES_JSON = "capabilities_json"
72
- const val EXTRA_GOOGLE_PAY_MERCHANT_ID = "google_pay_merchant_id"
73
70
  const val EXTRA_PAYMENT_METHOD_ID = "payment_method_id"
74
71
  const val REQUEST_CODE = 9003
75
72
  }
@@ -36,12 +36,17 @@ class FrameSDKModule(reactContext: ReactApplicationContext) :
36
36
  secretKey: String,
37
37
  publishableKey: String,
38
38
  debugMode: Boolean,
39
+ applePayMerchantId: String?,
40
+ googlePayMerchantId: String?,
39
41
  theme: ReadableMap?,
40
42
  promise: Promise
41
43
  ) {
42
44
  try {
43
45
  val ctx = reactApplicationContext.applicationContext
44
- FrameNetworking.initializeWithAPIKey(ctx, secretKey, publishableKey, debugMode)
46
+ // applePayMerchantId is iOS-only; accepted in the bridge signature so the JS Frame.initialize()
47
+ // API stays cross-platform, but ignored here. frame-android has no Apple Pay surface.
48
+ @Suppress("UNUSED_PARAMETER") val ignoredApplePayMerchantId = applePayMerchantId
49
+ FrameNetworking.initializeWithAPIKey(ctx, secretKey, publishableKey, googlePayMerchantId, debugMode)
45
50
  FrameRNTheme.current = theme?.takeIf { it.keySetIterator().hasNextKey() }?.let {
46
51
  FrameRNTheme.parse(ctx, it)
47
52
  }
@@ -99,7 +104,6 @@ class FrameSDKModule(reactContext: ReactApplicationContext) :
99
104
  ownerType: String?,
100
105
  ownerId: String?,
101
106
  currencyCode: String?,
102
- googlePayMerchantId: String?,
103
107
  promise: Promise
104
108
  ) {
105
109
  val activity = reactApplicationContext.currentActivity ?: run {
@@ -129,7 +133,6 @@ class FrameSDKModule(reactContext: ReactApplicationContext) :
129
133
  putExtra(FrameGooglePayActivity.EXTRA_OWNER_TYPE, ownerType)
130
134
  putExtra(FrameGooglePayActivity.EXTRA_OWNER_ID, ownerId)
131
135
  putExtra(FrameGooglePayActivity.EXTRA_CURRENCY, currencyCode ?: "USD")
132
- putExtra(FrameGooglePayActivity.EXTRA_MERCHANT_ID, googlePayMerchantId)
133
136
  }
134
137
  activity.startActivity(intent)
135
138
  }
@@ -139,7 +142,6 @@ class FrameSDKModule(reactContext: ReactApplicationContext) :
139
142
  fun presentOnboarding(
140
143
  accountId: String?,
141
144
  capabilities: ReadableArray,
142
- googlePayMerchantId: String?,
143
145
  promise: Promise
144
146
  ) {
145
147
  val activity = reactApplicationContext.currentActivity ?: run {
@@ -152,7 +154,6 @@ class FrameSDKModule(reactContext: ReactApplicationContext) :
152
154
  val intent = Intent(activity, FrameOnboardingActivity::class.java).apply {
153
155
  putExtra(FrameOnboardingActivity.EXTRA_ACCOUNT_ID, accountId)
154
156
  putExtra(FrameOnboardingActivity.EXTRA_CAPABILITIES_JSON, capabilitiesJson)
155
- putExtra(FrameOnboardingActivity.EXTRA_GOOGLE_PAY_MERCHANT_ID, googlePayMerchantId)
156
157
  }
157
158
  activity.startActivityForResult(intent, FrameOnboardingActivity.REQUEST_CODE)
158
159
  }
@@ -28,7 +28,6 @@ final class ApplePayPresenter: NSObject, PKPaymentAuthorizationControllerDelegat
28
28
  private let amount: Int
29
29
  private let currency: String
30
30
  private let owner: Owner
31
- private let merchantId: String
32
31
  private let resolve: (Any?) -> Void
33
32
  private let reject: (String, String, Error?) -> Void
34
33
 
@@ -46,13 +45,11 @@ final class ApplePayPresenter: NSObject, PKPaymentAuthorizationControllerDelegat
46
45
  init(amount: Int,
47
46
  currency: String,
48
47
  owner: Owner,
49
- merchantId: String,
50
48
  resolve: @escaping (Any?) -> Void,
51
49
  reject: @escaping (String, String, Error?) -> Void) {
52
50
  self.amount = amount
53
51
  self.currency = currency
54
52
  self.owner = owner
55
- self.merchantId = merchantId
56
53
  self.resolve = resolve
57
54
  self.reject = reject
58
55
  }
@@ -63,7 +60,7 @@ final class ApplePayPresenter: NSObject, PKPaymentAuthorizationControllerDelegat
63
60
 
64
61
  func present() {
65
62
  let request = PKPaymentRequest()
66
- request.merchantIdentifier = merchantId
63
+ request.merchantIdentifier = FrameNetworking.shared.applePayMerchantId ?? ""
67
64
  request.supportedNetworks = Self.supportedNetworks
68
65
  request.merchantCapabilities = .threeDSecure
69
66
  request.countryCode = "US"
@@ -28,6 +28,8 @@
28
28
  RCT_EXTERN_METHOD(initialize:(NSString *)secretKey
29
29
  publishableKey:(NSString *)publishableKey
30
30
  debugMode:(BOOL)debugMode
31
+ applePayMerchantId:(id)applePayMerchantId
32
+ googlePayMerchantId:(id)googlePayMerchantId
31
33
  theme:(NSDictionary *)theme
32
34
  resolver:(RCTPromiseResolveBlock)resolve
33
35
  rejecter:(RCTPromiseRejectBlock)reject)
@@ -45,7 +47,6 @@ RCT_EXTERN_METHOD(presentCart:(id)accountId
45
47
 
46
48
  RCT_EXTERN_METHOD(presentOnboarding:(id)accountId
47
49
  capabilities:(NSArray *)capabilities
48
- applePayMerchantId:(id)applePayMerchantId
49
50
  resolver:(RCTPromiseResolveBlock)resolve
50
51
  rejecter:(RCTPromiseRejectBlock)reject)
51
52
 
@@ -53,7 +54,6 @@ RCT_EXTERN_METHOD(presentApplePay:(NSString *)ownerType
53
54
  ownerId:(NSString *)ownerId
54
55
  amount:(nonnull NSNumber *)amount
55
56
  currency:(NSString *)currency
56
- merchantId:(NSString *)merchantId
57
57
  resolver:(RCTPromiseResolveBlock)resolve
58
58
  rejecter:(RCTPromiseRejectBlock)reject)
59
59
 
@@ -68,8 +68,8 @@ RCT_EXTERN_METHOD(presentApplePay:(NSString *)ownerType
68
68
  return YES;
69
69
  }
70
70
 
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];
71
+ - (void)initialize:(NSString *)secretKey publishableKey:(NSString *)publishableKey debugMode:(BOOL)debugMode applePayMerchantId:(id)applePayMerchantId googlePayMerchantId:(id)googlePayMerchantId theme:(NSDictionary *)theme resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
72
+ [[[ObjCFrameSDKBridge alloc] init] initialize:secretKey publishableKey:publishableKey debugMode:debugMode applePayMerchantId:applePayMerchantId googlePayMerchantId:googlePayMerchantId theme:theme resolver:resolve rejecter:reject];
73
73
  }
74
74
 
75
75
  - (void)presentCheckout:(id)accountId amount:(NSNumber *)amount resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
@@ -95,19 +95,19 @@ RCT_EXTERN_METHOD(presentApplePay:(NSString *)ownerType
95
95
  });
96
96
  }
97
97
 
98
- - (void)presentOnboarding:(id)accountId capabilities:(NSArray *)capabilities applePayMerchantId:(id)applePayMerchantId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
98
+ - (void)presentOnboarding:(id)accountId capabilities:(NSArray *)capabilities resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
99
99
  dispatch_async(dispatch_get_main_queue(), ^{
100
100
  UIViewController *topVC = FrameGetTopViewController();
101
101
  if (!topVC) {
102
102
  reject(@"NO_ROOT_VC", @"Could not find root view controller to present onboarding", nil);
103
103
  return;
104
104
  }
105
- [[[ObjCFrameSDKBridge alloc] init] presentOnboardingFrom:topVC accountId:accountId capabilities:capabilities applePayMerchantId:applePayMerchantId resolver:resolve rejecter:reject];
105
+ [[[ObjCFrameSDKBridge alloc] init] presentOnboardingFrom:topVC accountId:accountId capabilities:capabilities resolver:resolve rejecter:reject];
106
106
  });
107
107
  }
108
108
 
109
- - (void)presentApplePay:(NSString *)ownerType ownerId:(NSString *)ownerId amount:(NSNumber *)amount currency:(NSString *)currency merchantId:(NSString *)merchantId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
110
- [[[ObjCFrameSDKBridge alloc] init] presentApplePay:ownerType ownerId:ownerId amount:amount.intValue currency:currency merchantId:merchantId resolver:resolve rejecter:reject];
109
+ - (void)presentApplePay:(NSString *)ownerType ownerId:(NSString *)ownerId amount:(NSNumber *)amount currency:(NSString *)currency resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
110
+ [[[ObjCFrameSDKBridge alloc] init] presentApplePay:ownerType ownerId:ownerId amount:amount.intValue currency:currency resolver:resolve rejecter:reject];
111
111
  }
112
112
 
113
113
  @end
@@ -20,11 +20,21 @@ public class FrameSDKBridge: NSObject {
20
20
  }
21
21
 
22
22
  @objc public
23
- func initialize(_ secretKey: String, publishableKey: String, debugMode: Bool, theme: NSDictionary?, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
23
+ func initialize(_ secretKey: String, publishableKey: String, debugMode: Bool, applePayMerchantId: NSObject?, googlePayMerchantId: NSObject?, theme: NSDictionary?, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
24
24
  DispatchQueue.main.async {
25
25
  let themeDict = theme as? [String: Any] ?? [:]
26
26
  let resolvedTheme = themeDict.isEmpty ? FrameTheme.default : FrameRNTheme.parse(themeDict)
27
- FrameNetworking.shared.initializeWithAPIKey(secretKey, publishableKey: publishableKey, theme: resolvedTheme, debugMode: debugMode)
27
+ // googlePayMerchantId is iOS-side ignored frame-iOS has no Google Pay surface today.
28
+ // Accepted in the bridge signature so the JS Frame.initialize() API stays cross-platform.
29
+ _ = googlePayMerchantId
30
+ let applePayMerchantIdString = applePayMerchantId as? String
31
+ FrameNetworking.shared.initializeWithAPIKey(
32
+ secretKey,
33
+ publishableKey: publishableKey,
34
+ applePayMerchantId: applePayMerchantIdString,
35
+ theme: resolvedTheme,
36
+ debugMode: debugMode
37
+ )
28
38
  resolve(nil)
29
39
  }
30
40
  }
@@ -52,15 +62,14 @@ public class FrameSDKBridge: NSObject {
52
62
  }
53
63
 
54
64
  @objc public
55
- func presentOnboarding(from viewController: UIViewController, accountId: NSObject?, capabilities: NSArray, applePayMerchantId: NSObject?, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
65
+ func presentOnboarding(from viewController: UIViewController, accountId: NSObject?, capabilities: NSArray, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
56
66
  let parsedCapabilities = parseCapabilities(capabilities)
57
67
  let accountIdString = accountId as? String
58
- let merchantIdString = applePayMerchantId as? String
59
- presentOnboardingOnMain(from: viewController, accountId: accountIdString, capabilities: parsedCapabilities, applePayMerchantId: merchantIdString, resolve: resolve, reject: reject)
68
+ presentOnboardingOnMain(from: viewController, accountId: accountIdString, capabilities: parsedCapabilities, resolve: resolve, reject: reject)
60
69
  }
61
70
 
62
71
  @objc public
63
- func presentApplePay(_ ownerType: String, ownerId: String, amount: Int, currency: String, merchantId: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
72
+ func presentApplePay(_ ownerType: String, ownerId: String, amount: Int, currency: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
64
73
  DispatchQueue.main.async {
65
74
  Task { @MainActor in
66
75
  let owner: ApplePayPresenter.Owner
@@ -75,8 +84,9 @@ public class FrameSDKBridge: NSObject {
75
84
  reject("INVALID_OWNER", "owner.id must be non-empty", nil)
76
85
  return
77
86
  }
78
- guard !merchantId.isEmpty else {
79
- reject("INVALID_MERCHANT_ID", "merchantId must be non-empty", nil)
87
+ let configuredMerchantId = FrameNetworking.shared.applePayMerchantId ?? ""
88
+ guard !configuredMerchantId.isEmpty else {
89
+ reject("INVALID_MERCHANT_ID", "Apple Pay merchant ID is not configured. Pass `applePayMerchantId` to Frame.initialize().", nil)
80
90
  return
81
91
  }
82
92
  guard ApplePayPresenter.canMakePayments() else {
@@ -94,7 +104,6 @@ public class FrameSDKBridge: NSObject {
94
104
  amount: amount,
95
105
  currency: currency,
96
106
  owner: owner,
97
- merchantId: merchantId,
98
107
  resolve: { resolve($0) },
99
108
  reject: { code, message, error in reject(code, message, error) }
100
109
  )
@@ -106,17 +115,21 @@ public class FrameSDKBridge: NSObject {
106
115
  // MARK: - Private helpers
107
116
 
108
117
  private func presentCheckoutOnMain(from top: UIViewController, accountId: String, amount: Int, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
109
- // Single dismiss delegate guards against double-resolve: the inner checkout
110
- // calls `finish(.success)` with the transfer id; bare-dismiss (swipe-down
111
- // without completing checkout) calls `finish(.cancel)`.
118
+ // Single dismiss delegate guards against double-resolve. The native FrameCheckoutView now
119
+ // emits `.cancelled` on its own .onDisappear and the bridge's
120
+ // `presentationControllerDidDismiss` ALSO fires on swipe-down — the delegate's `didFinish`
121
+ // guard prevents the double-resolve.
112
122
  let delegate = CheckoutDismissDelegate(resolve: resolve, reject: reject)
113
123
  let checkoutView = FrameCheckoutView(
114
124
  accountId: accountId,
115
125
  paymentAmount: amount,
116
- checkoutCallback: { [weak top, delegate] success, transferId in
117
- if success, let transferId {
118
- delegate.finish(.success(transferId))
119
- } else {
126
+ onResult: { [weak top, delegate] result in
127
+ switch result {
128
+ case .completed(let id):
129
+ delegate.finish(.success(id))
130
+ case .cancelled:
131
+ delegate.finish(.cancel)
132
+ case .failed:
120
133
  delegate.finish(.failure)
121
134
  }
122
135
  top?.dismiss(animated: true)
@@ -164,19 +177,22 @@ public class FrameSDKBridge: NSObject {
164
177
  }
165
178
 
166
179
  private func presentCartOnMain(from top: UIViewController, accountId: String, cartItems: [RNFrameCartItem], shippingAmountInCents: Int, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
167
- // Single dismiss delegate guards against double-resolve: the inner checkout
168
- // calls `finish(.success)` with the transfer id; bare-dismiss (swipe-down
169
- // without completing checkout) calls `finish(.cancel)`.
180
+ // Single dismiss delegate guards against double-resolve. The native FrameCartView's own
181
+ // .onDisappear emits `.cancelled`, and the bridge's `presentationControllerDidDismiss`
182
+ // also fires on swipe-down — the delegate's `didFinish` guard prevents re-resolve.
170
183
  let delegate = CartDismissDelegate(resolve: resolve, reject: reject)
171
184
  let cartView = FrameCartView(
172
185
  accountId: accountId,
173
186
  cartItems: cartItems,
174
187
  shippingAmountInCents: shippingAmountInCents,
175
- checkoutCallback: { [weak top, delegate] success, transferId in
176
- if success, let transferId {
177
- delegate.finish(.success(transferId))
178
- } else {
188
+ onResult: { [weak top, delegate] result in
189
+ switch result {
190
+ case .completed(let id):
191
+ delegate.finish(.success(id))
192
+ case .cancelled:
179
193
  delegate.finish(.cancel)
194
+ case .failed:
195
+ delegate.finish(.failure)
180
196
  }
181
197
  top?.dismiss(animated: true)
182
198
  }
@@ -192,21 +208,23 @@ public class FrameSDKBridge: NSObject {
192
208
  }
193
209
  }
194
210
 
195
- private func presentOnboardingOnMain(from top: UIViewController, accountId: String?, capabilities: [FrameObjects.Capabilities], applePayMerchantId: String?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
196
- // Build the dismiss delegate up-front so onComplete captures a non-nil
197
- // instance directly. Earlier shape declared `var delegate: …!` and assigned
198
- // it AFTER constructing OnboardingContainerView; the onComplete closure
199
- // captured the local by reference, so any path that fired the callback
200
- // before assignment landed on a nil delegate and silently no-op'd via `?.`.
211
+ private func presentOnboardingOnMain(from top: UIViewController, accountId: String?, capabilities: [FrameObjects.Capabilities], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
212
+ // Build the dismiss delegate up-front so onResult captures a non-nil instance directly.
201
213
  let delegate = OnboardingDismissDelegate(resolve: resolve)
202
214
 
203
215
  let hosting = OnboardingHostingController(
204
216
  rootView: OnboardingContainerView(
205
217
  accountId: accountId,
206
218
  requiredCapabilities: capabilities,
207
- applePayMerchantId: applePayMerchantId,
208
- onComplete: { [delegate, weak top] in
209
- delegate.finish(completed: true)
219
+ onResult: { [delegate, weak top] result in
220
+ switch result {
221
+ case .completed(let id):
222
+ delegate.finish(.completed(paymentMethodId: id.isEmpty ? nil : id))
223
+ case .cancelled:
224
+ delegate.finish(.cancelled)
225
+ case .failed:
226
+ delegate.finish(.cancelled)
227
+ }
210
228
  top?.dismiss(animated: true)
211
229
  }
212
230
  )
@@ -276,6 +294,7 @@ private final class CheckoutDismissDelegate: NSObject, UIAdaptivePresentationCon
276
294
  private final class CartDismissDelegate: NSObject, UIAdaptivePresentationControllerDelegate {
277
295
  enum Outcome {
278
296
  case success(String)
297
+ case failure
279
298
  case cancel
280
299
  }
281
300
 
@@ -294,6 +313,7 @@ private final class CartDismissDelegate: NSObject, UIAdaptivePresentationControl
294
313
  DispatchQueue.main.async { [resolve, reject] in
295
314
  switch outcome {
296
315
  case .success(let transferId): resolve(transferId)
316
+ case .failure: reject("PAYMENT_FAILED", "Cart checkout did not produce a transfer id", nil)
297
317
  case .cancel: reject("USER_CANCELED", "User dismissed cart without completing checkout", nil)
298
318
  }
299
319
  }
@@ -305,6 +325,11 @@ private final class CartDismissDelegate: NSObject, UIAdaptivePresentationControl
305
325
  }
306
326
 
307
327
  private final class OnboardingDismissDelegate: NSObject, UIAdaptivePresentationControllerDelegate {
328
+ enum Outcome {
329
+ case completed(paymentMethodId: String?)
330
+ case cancelled
331
+ }
332
+
308
333
  let resolve: RCTPromiseResolveBlock
309
334
  weak var hostingController: UIViewController?
310
335
  var didFinish = false
@@ -313,11 +338,18 @@ private final class OnboardingDismissDelegate: NSObject, UIAdaptivePresentationC
313
338
  self.resolve = resolve
314
339
  }
315
340
 
316
- func finish(completed: Bool) {
341
+ func finish(_ outcome: Outcome) {
317
342
  guard !didFinish else { return }
318
343
  didFinish = true
319
- DispatchQueue.main.async { [resolve, completed] in
320
- resolve(["status": completed ? "completed" : "cancelled"])
344
+ DispatchQueue.main.async { [resolve] in
345
+ switch outcome {
346
+ case .completed(let paymentMethodId):
347
+ var payload: [String: Any] = ["status": "completed"]
348
+ if let paymentMethodId { payload["paymentMethodId"] = paymentMethodId }
349
+ resolve(payload)
350
+ case .cancelled:
351
+ resolve(["status": "cancelled"])
352
+ }
321
353
  }
322
354
  }
323
355
 
@@ -326,7 +358,7 @@ private final class OnboardingDismissDelegate: NSObject, UIAdaptivePresentationC
326
358
  // callback to the Onboarding host's delegate. Only treat dismissal of the
327
359
  // Onboarding hosting controller itself as a cancellation.
328
360
  guard presentationController.presentedViewController === hostingController else { return }
329
- finish(completed: false)
361
+ finish(.cancelled)
330
362
  }
331
363
  }
332
364
 
package/lib/native.d.ts CHANGED
@@ -6,6 +6,18 @@ export declare function initialize(options: {
6
6
  secretKey: string;
7
7
  publishableKey: string;
8
8
  debugMode?: boolean;
9
+ /**
10
+ * Apple Pay merchant ID configured in your Apple Developer account. Applied to every
11
+ * Apple Pay surface (presentApplePay, the bundled checkout's wallet row, the
12
+ * onboarding wallet attach button). iOS-only — ignored on Android.
13
+ */
14
+ applePayMerchantId?: string;
15
+ /**
16
+ * Google Pay merchant ID from the Google Pay & Wallet Console. Applied to every
17
+ * Google Pay surface (presentGooglePay, the bundled checkout's wallet row, the
18
+ * onboarding wallet attach button). Android-only — ignored on iOS.
19
+ */
20
+ googlePayMerchantId?: string;
9
21
  /**
10
22
  * Optional theme applied SDK-wide to Frame's reusable iOS components
11
23
  * (checkout, cart, onboarding). Pass any subset — unspecified tokens fall
@@ -41,8 +53,6 @@ export declare function presentCart(options: {
41
53
  export declare function presentOnboarding(options: {
42
54
  accountId?: string | null;
43
55
  capabilities?: OnboardingCapability[];
44
- applePayMerchantId?: string | null;
45
- googlePayMerchantId?: string | null;
46
56
  }): Promise<OnboardingResult>;
47
57
  /**
48
58
  * Presents the Apple Pay sheet and creates a charge from the resulting wallet
@@ -1 +1 @@
1
- {"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EACV,aAAa,EACb,UAAU,EACV,oBAAoB,EACpB,gBAAgB,EAChB,sBAAsB,EACtB,uBAAuB,EACxB,MAAM,SAAS,CAAC;AAiCjB,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;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAQlB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,qBAAqB,EAAE,MAAM,CAAC;CAC/B,GAAG,OAAO,CAAC,MAAM,CAAC,CAYlB;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;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAuBhF;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoBlF"}
1
+ {"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EACV,aAAa,EACb,UAAU,EACV,oBAAoB,EACpB,gBAAgB,EAChB,sBAAsB,EACtB,uBAAuB,EACxB,MAAM,SAAS,CAAC;AAiCjB,wBAAgB,UAAU,CAAC,OAAO,EAAE;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;OAKG;IACH,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBhB;AAwBD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAQlB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,qBAAqB,EAAE,MAAM,CAAC;CAC/B,GAAG,OAAO,CAAC,MAAM,CAAC,CAYlB;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IACzC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,oBAAoB,EAAE,CAAC;CACvC,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAQ5B;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmBhF;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmBlF"}
package/lib/native.js CHANGED
@@ -34,7 +34,7 @@ export function initialize(options) {
34
34
  if (options.theme !== undefined && (typeof options.theme !== 'object' || Array.isArray(options.theme))) {
35
35
  throwCoded(ErrorCodes.INIT_FAILED, 'Frame.initialize: theme must be an object');
36
36
  }
37
- return wrapPromise(FrameSDK.initialize(options.secretKey, options.publishableKey, options.debugMode ?? false, options.theme ?? null)).then(() => {
37
+ return wrapPromise(FrameSDK.initialize(options.secretKey, options.publishableKey, options.debugMode ?? false, options.applePayMerchantId ?? null, options.googlePayMerchantId ?? null, options.theme ?? null)).then(() => {
38
38
  isInitialized = true;
39
39
  });
40
40
  }
@@ -89,10 +89,7 @@ export function presentCart(options) {
89
89
  }
90
90
  export function presentOnboarding(options) {
91
91
  guardInitialized();
92
- if (Platform.OS === 'ios') {
93
- return wrapPromise(FrameSDK.presentOnboarding(options.accountId ?? null, options.capabilities ?? [], options.applePayMerchantId ?? null));
94
- }
95
- return wrapPromise(FrameSDK.presentOnboarding(options.accountId ?? null, options.capabilities ?? [], options.googlePayMerchantId ?? null));
92
+ return wrapPromise(FrameSDK.presentOnboarding(options.accountId ?? null, options.capabilities ?? []));
96
93
  }
97
94
  /**
98
95
  * Presents the Apple Pay sheet and creates a charge from the resulting wallet
@@ -113,10 +110,7 @@ export function presentApplePay(options) {
113
110
  if (!options.owner.id) {
114
111
  throwCoded(ErrorCodes.INVALID_OWNER, 'Frame.presentApplePay requires owner.id');
115
112
  }
116
- if (!options.merchantId) {
117
- throwCoded(ErrorCodes.INVALID_MERCHANT_ID, 'Frame.presentApplePay requires merchantId');
118
- }
119
- return wrapPromise(FrameSDK.presentApplePay(options.owner.type, options.owner.id, options.amount, options.currency ?? 'usd', options.merchantId));
113
+ return wrapPromise(FrameSDK.presentApplePay(options.owner.type, options.owner.id, options.amount, options.currency ?? 'usd'));
120
114
  }
121
115
  /**
122
116
  * Presents Google Pay and creates a charge from the resulting wallet payment method.
@@ -136,5 +130,5 @@ export function presentGooglePay(options) {
136
130
  if (!options.owner.id) {
137
131
  throwCoded(ErrorCodes.INVALID_OWNER, 'Frame.presentGooglePay requires owner.id');
138
132
  }
139
- return wrapPromise(FrameSDK.presentGooglePay(options.amountCents, options.owner.type, options.owner.id, options.currencyCode ?? 'USD', options.googlePayMerchantId ?? null));
133
+ return wrapPromise(FrameSDK.presentGooglePay(options.amountCents, options.owner.type, options.owner.id, options.currencyCode ?? 'USD'));
140
134
  }
package/lib/types.d.ts CHANGED
@@ -79,7 +79,12 @@ export type WalletOwner = {
79
79
  };
80
80
  /** @deprecated Use {@link WalletOwner}. Retained as an alias for source compatibility. */
81
81
  export type ApplePayOwner = WalletOwner;
82
- /** Options for Frame.presentApplePay. */
82
+ /**
83
+ * Options for Frame.presentApplePay.
84
+ *
85
+ * The Apple Pay merchant ID is set once at `Frame.initialize({ applePayMerchantId })`. There is
86
+ * no per-call merchant ID parameter — the SDK reads it from the singleton at present time.
87
+ */
83
88
  export interface PresentApplePayOptions {
84
89
  /** Payment amount in cents. */
85
90
  amount: number;
@@ -87,10 +92,13 @@ export interface PresentApplePayOptions {
87
92
  currency?: string;
88
93
  /** Customer or account that owns the resulting payment method and charge. */
89
94
  owner: WalletOwner;
90
- /** Apple Pay merchant ID configured in your Apple Developer account. */
91
- merchantId: string;
92
95
  }
93
- /** Options for Frame.presentGooglePay. */
96
+ /**
97
+ * Options for Frame.presentGooglePay.
98
+ *
99
+ * The Google Pay merchant ID is set once at `Frame.initialize({ googlePayMerchantId })`. There
100
+ * is no per-call merchant ID parameter — the SDK reads it from the singleton at present time.
101
+ */
94
102
  export interface PresentGooglePayOptions {
95
103
  /** Payment amount in cents. */
96
104
  amountCents: number;
@@ -98,8 +106,6 @@ export interface PresentGooglePayOptions {
98
106
  owner: WalletOwner;
99
107
  /** ISO 4217 currency code. Defaults to 'USD'. */
100
108
  currencyCode?: string;
101
- /** Optional override for the Google Pay merchant ID. */
102
- googlePayMerchantId?: string;
103
109
  }
104
110
  /**
105
111
  * Theming for Frame's reusable iOS components (checkout, cart, onboarding).
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "framepayments-react-native",
3
- "version": "3.0.0",
3
+ "version": "3.0.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",
@@ -34,8 +34,8 @@
34
34
  "react-native": ">=0.81.0"
35
35
  },
36
36
  "frameNativeVersions": {
37
- "ios": "2.2.2",
38
- "android": "2.0.8"
37
+ "ios": "2.2.3",
38
+ "android": "2.0.9"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/jest": "^29.5.0",
@@ -3,12 +3,12 @@
3
3
  * presentApplePay, presentGooglePay, presentOnboarding). NativeModules.FrameSDK is mocked.
4
4
  */
5
5
 
6
- const mockInitialize = jest.fn((_secretKey: string, _publishableKey: string, _debugMode: boolean) => Promise.resolve());
6
+ const mockInitialize = jest.fn((_secretKey: string, _publishableKey: string, _debugMode: boolean, _applePayMerchantId: string | null, _googlePayMerchantId: string | null, _theme: unknown) => Promise.resolve());
7
7
  const mockPresentCheckout = jest.fn((_accountId: unknown, _amount: number) => Promise.resolve('tr_1'));
8
8
  const mockPresentCart = jest.fn((_accountId: unknown, _items: unknown[], _shipping: number) => Promise.resolve('tr_2'));
9
- const mockPresentApplePay = jest.fn((_ownerType: string, _ownerId: string, _amount: number, _currency: string, _merchantId: string) => Promise.resolve('tr_3'));
10
- const mockPresentGooglePay = jest.fn((_amountCents: number, _ownerType: string, _ownerId: string, _currencyCode: string, _googlePayMerchantId: string | null) => Promise.resolve('tr_4'));
11
- const mockPresentOnboarding = jest.fn((_accountId: unknown, _capabilities: unknown[], _merchantId: string | null) => Promise.resolve({ status: 'completed', paymentMethodId: 'pm_1' }));
9
+ const mockPresentApplePay = jest.fn((_ownerType: string, _ownerId: string, _amount: number, _currency: string) => Promise.resolve('tr_3'));
10
+ const mockPresentGooglePay = jest.fn((_amountCents: number, _ownerType: string, _ownerId: string, _currencyCode: string) => Promise.resolve('tr_4'));
11
+ const mockPresentOnboarding = jest.fn((_accountId: unknown, _capabilities: unknown[]) => Promise.resolve({ status: 'completed', paymentMethodId: 'pm_1' }));
12
12
 
13
13
  const mockPlatform = { OS: 'ios' as 'ios' | 'android' };
14
14
 
@@ -27,16 +27,16 @@ jest.mock('react-native', () => ({
27
27
  }));
28
28
 
29
29
  // Re-import after mock so we get the mocked NativeModules
30
- let initialize: (opts: { secretKey: string; publishableKey: string; debugMode?: boolean }) => Promise<void>;
30
+ let initialize: (opts: { secretKey: string; publishableKey: string; debugMode?: boolean; applePayMerchantId?: string; googlePayMerchantId?: string }) => Promise<void>;
31
31
  let presentCheckout: (opts: { accountId: string; amount: number }) => Promise<string>;
32
32
  let presentCart: (opts: {
33
33
  accountId: string;
34
34
  items: Array<{ id: string; title: string; amountInCents: number; imageUrl: string }>;
35
35
  shippingAmountInCents: number;
36
36
  }) => Promise<string>;
37
- let presentApplePay: (opts: { amount: number; currency?: string; owner: { type: 'customer' | 'account'; id: string }; merchantId: string }) => Promise<string>;
38
- let presentGooglePay: (opts: { amountCents: number; owner: { type: 'customer' | 'account'; id: string }; currencyCode?: string; googlePayMerchantId?: string }) => Promise<string>;
39
- let presentOnboarding: (opts: { accountId?: string | null; capabilities?: string[]; applePayMerchantId?: string | null; googlePayMerchantId?: string | null }) => Promise<unknown>;
37
+ let presentApplePay: (opts: { amount: number; currency?: string; owner: { type: 'customer' | 'account'; id: string } }) => Promise<string>;
38
+ let presentGooglePay: (opts: { amountCents: number; owner: { type: 'customer' | 'account'; id: string }; currencyCode?: string }) => Promise<string>;
39
+ let presentOnboarding: (opts: { accountId?: string | null; capabilities?: string[] }) => Promise<unknown>;
40
40
 
41
41
  beforeEach(() => {
42
42
  jest.resetModules();
@@ -57,15 +57,33 @@ beforeEach(() => {
57
57
  });
58
58
 
59
59
  describe('initialize', () => {
60
- it('calls native FrameSDK.initialize with secretKey, publishableKey, and debugMode', () => {
60
+ it('calls native FrameSDK.initialize with all six positional args', () => {
61
61
  initialize({ secretKey: 'sk_test_xxx', publishableKey: 'pk_test_xxx', debugMode: true });
62
62
  expect(mockInitialize).toHaveBeenCalledTimes(1);
63
- expect(mockInitialize).toHaveBeenCalledWith('sk_test_xxx', 'pk_test_xxx', true, null);
63
+ expect(mockInitialize).toHaveBeenCalledWith('sk_test_xxx', 'pk_test_xxx', true, null, null, null);
64
64
  });
65
65
 
66
- it('defaults debugMode to false', () => {
66
+ it('defaults debugMode to false and both merchant IDs to null', () => {
67
67
  initialize({ secretKey: 'sk_test_yyy', publishableKey: 'pk_test_yyy' });
68
- expect(mockInitialize).toHaveBeenCalledWith('sk_test_yyy', 'pk_test_yyy', false, null);
68
+ expect(mockInitialize).toHaveBeenCalledWith('sk_test_yyy', 'pk_test_yyy', false, null, null, null);
69
+ });
70
+
71
+ it('forwards applePayMerchantId to native init', () => {
72
+ initialize({
73
+ secretKey: 'sk_test',
74
+ publishableKey: 'pk_test',
75
+ applePayMerchantId: 'merchant.com.example',
76
+ });
77
+ expect(mockInitialize).toHaveBeenCalledWith('sk_test', 'pk_test', false, 'merchant.com.example', null, null);
78
+ });
79
+
80
+ it('forwards googlePayMerchantId to native init', () => {
81
+ initialize({
82
+ secretKey: 'sk_test',
83
+ publishableKey: 'pk_test',
84
+ googlePayMerchantId: 'BCR2DN4T...',
85
+ });
86
+ expect(mockInitialize).toHaveBeenCalledWith('sk_test', 'pk_test', false, null, 'BCR2DN4T...', null);
69
87
  });
70
88
 
71
89
  it('throws if secretKey is missing', () => {
@@ -153,7 +171,7 @@ describe('presentCart', () => {
153
171
  describe('presentApplePay', () => {
154
172
  it('throws NOT_INITIALIZED if initialize was not called', async () => {
155
173
  try {
156
- await presentApplePay({ amount: 100, owner: { type: 'account', id: 'acct_1' }, merchantId: 'merchant.test' });
174
+ await presentApplePay({ amount: 100, owner: { type: 'account', id: 'acct_1' } });
157
175
  expect(true).toBe(false);
158
176
  } catch (e: any) {
159
177
  expect(e.code).toBe('NOT_INITIALIZED');
@@ -164,7 +182,7 @@ describe('presentApplePay', () => {
164
182
  it('throws INVALID_OWNER when owner is missing', async () => {
165
183
  await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
166
184
  try {
167
- await presentApplePay({ amount: 100, merchantId: 'merchant.test' } as any);
185
+ await presentApplePay({ amount: 100 } as any);
168
186
  expect(true).toBe(false);
169
187
  } catch (e: any) {
170
188
  expect(e.code).toBe('INVALID_OWNER');
@@ -175,7 +193,7 @@ describe('presentApplePay', () => {
175
193
  it('throws INVALID_OWNER when owner.id is empty', async () => {
176
194
  await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
177
195
  try {
178
- await presentApplePay({ amount: 100, owner: { type: 'account', id: '' }, merchantId: 'merchant.test' });
196
+ await presentApplePay({ amount: 100, owner: { type: 'account', id: '' } });
179
197
  expect(true).toBe(false);
180
198
  } catch (e: any) {
181
199
  expect(e.code).toBe('INVALID_OWNER');
@@ -183,43 +201,30 @@ describe('presentApplePay', () => {
183
201
  expect(mockPresentApplePay).not.toHaveBeenCalled();
184
202
  });
185
203
 
186
- it('throws INVALID_MERCHANT_ID when merchantId is missing', async () => {
187
- await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
188
- try {
189
- await presentApplePay({ amount: 100, owner: { type: 'account', id: 'acct_1' } } as any);
190
- expect(true).toBe(false);
191
- } catch (e: any) {
192
- expect(e.code).toBe('INVALID_MERCHANT_ID');
193
- }
194
- expect(mockPresentApplePay).not.toHaveBeenCalled();
195
- });
196
-
197
204
  it('forwards account owner; resolves with transfer id string', async () => {
198
- await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
205
+ await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx', applePayMerchantId: 'merchant.test' });
199
206
  const result = await presentApplePay({
200
207
  amount: 12345,
201
208
  currency: 'usd',
202
209
  owner: { type: 'account', id: 'acct_1' },
203
- merchantId: 'merchant.test',
204
210
  });
205
- expect(mockPresentApplePay).toHaveBeenCalledWith('account', 'acct_1', 12345, 'usd', 'merchant.test');
211
+ expect(mockPresentApplePay).toHaveBeenCalledWith('account', 'acct_1', 12345, 'usd');
206
212
  expect(result).toBe('tr_3');
207
213
  });
208
214
 
209
215
  it('forwards customer owner; resolves with charge intent id string', async () => {
210
- await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
216
+ await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx', applePayMerchantId: 'merchant.test' });
211
217
  await presentApplePay({
212
218
  amount: 9999,
213
219
  owner: { type: 'customer', id: 'cus_1' },
214
- merchantId: 'merchant.test',
215
220
  });
216
- expect(mockPresentApplePay).toHaveBeenCalledWith('customer', 'cus_1', 9999, 'usd', 'merchant.test');
221
+ expect(mockPresentApplePay).toHaveBeenCalledWith('customer', 'cus_1', 9999, 'usd');
217
222
  });
218
223
 
219
224
  it('defaults currency to usd', async () => {
220
- await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
221
- await presentApplePay({ amount: 100, owner: { type: 'account', id: 'acct_1' }, merchantId: 'merchant.test' });
222
- expect(mockPresentApplePay).toHaveBeenCalledWith('account', 'acct_1', 100, 'usd', 'merchant.test');
225
+ await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx', applePayMerchantId: 'merchant.test' });
226
+ await presentApplePay({ amount: 100, owner: { type: 'account', id: 'acct_1' } });
227
+ expect(mockPresentApplePay).toHaveBeenCalledWith('account', 'acct_1', 100, 'usd');
223
228
  });
224
229
  });
225
230
 
@@ -262,30 +267,29 @@ describe('presentGooglePay', () => {
262
267
  });
263
268
 
264
269
  it('forwards account owner; resolves with transfer id string', async () => {
265
- await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
270
+ await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx', googlePayMerchantId: 'BCR2DN4T...' });
266
271
  const result = await presentGooglePay({
267
272
  amountCents: 9999,
268
273
  owner: { type: 'account', id: 'acct_1' },
269
274
  currencyCode: 'EUR',
270
- googlePayMerchantId: 'BCR2DN4T...',
271
275
  });
272
- expect(mockPresentGooglePay).toHaveBeenCalledWith(9999, 'account', 'acct_1', 'EUR', 'BCR2DN4T...');
276
+ expect(mockPresentGooglePay).toHaveBeenCalledWith(9999, 'account', 'acct_1', 'EUR');
273
277
  expect(result).toBe('tr_4');
274
278
  });
275
279
 
276
280
  it('forwards customer owner; resolves with charge intent id string', async () => {
277
- await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
281
+ await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx', googlePayMerchantId: 'BCR2DN4T...' });
278
282
  await presentGooglePay({
279
283
  amountCents: 4242,
280
284
  owner: { type: 'customer', id: 'cus_1' },
281
285
  });
282
- expect(mockPresentGooglePay).toHaveBeenCalledWith(4242, 'customer', 'cus_1', 'USD', null);
286
+ expect(mockPresentGooglePay).toHaveBeenCalledWith(4242, 'customer', 'cus_1', 'USD');
283
287
  });
284
288
 
285
- it('defaults currencyCode to USD and merchantId to null', async () => {
286
- await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
289
+ it('defaults currencyCode to USD', async () => {
290
+ await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx', googlePayMerchantId: 'BCR2DN4T...' });
287
291
  await presentGooglePay({ amountCents: 100, owner: { type: 'account', id: 'acct_1' } });
288
- expect(mockPresentGooglePay).toHaveBeenCalledWith(100, 'account', 'acct_1', 'USD', null);
292
+ expect(mockPresentGooglePay).toHaveBeenCalledWith(100, 'account', 'acct_1', 'USD');
289
293
  });
290
294
  });
291
295
 
@@ -301,46 +305,23 @@ describe('presentOnboarding', () => {
301
305
  expect(mockPresentOnboarding).not.toHaveBeenCalled();
302
306
  });
303
307
 
304
- it('calls native presentOnboarding with accountId, capabilities, and null merchantId after initialize on iOS', async () => {
305
- mockPlatform.OS = 'ios';
308
+ it('calls native presentOnboarding with accountId and capabilities only no merchant params', async () => {
306
309
  await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
307
310
  const result = await presentOnboarding({ accountId: 'acct_1', capabilities: ['kyc', 'bank_account_verification'] });
308
- expect(mockPresentOnboarding).toHaveBeenCalledWith('acct_1', ['kyc', 'bank_account_verification'], null);
311
+ expect(mockPresentOnboarding).toHaveBeenCalledWith('acct_1', ['kyc', 'bank_account_verification']);
309
312
  expect(result).toEqual({ status: 'completed', paymentMethodId: 'pm_1' });
310
313
  });
311
314
 
312
- it('passes null for accountId and empty array for capabilities when not provided on iOS', async () => {
313
- mockPlatform.OS = 'ios';
315
+ it('passes null accountId and empty array for capabilities when not provided', async () => {
314
316
  await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
315
317
  await presentOnboarding({});
316
- expect(mockPresentOnboarding).toHaveBeenCalledWith(null, [], null);
317
- });
318
-
319
- it('forwards applePayMerchantId on iOS', async () => {
320
- mockPlatform.OS = 'ios';
321
- await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
322
- await presentOnboarding({ accountId: 'acct_1', capabilities: ['kyc'], applePayMerchantId: 'merchant.com.example' });
323
- expect(mockPresentOnboarding).toHaveBeenCalledWith('acct_1', ['kyc'], 'merchant.com.example');
318
+ expect(mockPresentOnboarding).toHaveBeenCalledWith(null, []);
324
319
  });
325
320
 
326
- it('ignores applePayMerchantId on Android', async () => {
321
+ it('behaves the same on Android — merchant IDs are init-only across both platforms', async () => {
327
322
  mockPlatform.OS = 'android';
328
- await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
329
- await presentOnboarding({ accountId: 'acct_1', capabilities: ['kyc'], applePayMerchantId: 'merchant.com.example' });
330
- expect(mockPresentOnboarding).toHaveBeenCalledWith('acct_1', ['kyc'], null);
331
- });
332
-
333
- it('forwards googlePayMerchantId to native presentOnboarding on Android', async () => {
334
- mockPlatform.OS = 'android';
335
- await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
336
- await presentOnboarding({ accountId: 'acct_1', capabilities: ['kyc'], googlePayMerchantId: 'BCR2DN4T...' });
337
- expect(mockPresentOnboarding).toHaveBeenCalledWith('acct_1', ['kyc'], 'BCR2DN4T...');
338
- });
339
-
340
- it('ignores googlePayMerchantId on iOS', async () => {
341
- mockPlatform.OS = 'ios';
342
- await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
343
- await presentOnboarding({ accountId: 'acct_1', capabilities: ['kyc'], googlePayMerchantId: 'BCR2DN4T...' });
344
- expect(mockPresentOnboarding).toHaveBeenCalledWith('acct_1', ['kyc'], null);
323
+ await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx', googlePayMerchantId: 'BCR2DN4T...' });
324
+ await presentOnboarding({ accountId: 'acct_1', capabilities: ['kyc'] });
325
+ expect(mockPresentOnboarding).toHaveBeenCalledWith('acct_1', ['kyc']);
345
326
  });
346
327
  });
package/src/native.ts CHANGED
@@ -47,6 +47,18 @@ export function initialize(options: {
47
47
  secretKey: string;
48
48
  publishableKey: string;
49
49
  debugMode?: boolean;
50
+ /**
51
+ * Apple Pay merchant ID configured in your Apple Developer account. Applied to every
52
+ * Apple Pay surface (presentApplePay, the bundled checkout's wallet row, the
53
+ * onboarding wallet attach button). iOS-only — ignored on Android.
54
+ */
55
+ applePayMerchantId?: string;
56
+ /**
57
+ * Google Pay merchant ID from the Google Pay & Wallet Console. Applied to every
58
+ * Google Pay surface (presentGooglePay, the bundled checkout's wallet row, the
59
+ * onboarding wallet attach button). Android-only — ignored on iOS.
60
+ */
61
+ googlePayMerchantId?: string;
50
62
  /**
51
63
  * Optional theme applied SDK-wide to Frame's reusable iOS components
52
64
  * (checkout, cart, onboarding). Pass any subset — unspecified tokens fall
@@ -69,6 +81,8 @@ export function initialize(options: {
69
81
  options.secretKey,
70
82
  options.publishableKey,
71
83
  options.debugMode ?? false,
84
+ options.applePayMerchantId ?? null,
85
+ options.googlePayMerchantId ?? null,
72
86
  options.theme ?? null
73
87
  )
74
88
  ).then(() => {
@@ -147,24 +161,12 @@ export function presentCart(options: {
147
161
  export function presentOnboarding(options: {
148
162
  accountId?: string | null;
149
163
  capabilities?: OnboardingCapability[];
150
- applePayMerchantId?: string | null;
151
- googlePayMerchantId?: string | null;
152
164
  }): Promise<OnboardingResult> {
153
165
  guardInitialized();
154
- if (Platform.OS === 'ios') {
155
- return wrapPromise(
156
- FrameSDK.presentOnboarding(
157
- options.accountId ?? null,
158
- options.capabilities ?? [],
159
- options.applePayMerchantId ?? null
160
- )
161
- );
162
- }
163
166
  return wrapPromise(
164
167
  FrameSDK.presentOnboarding(
165
168
  options.accountId ?? null,
166
- options.capabilities ?? [],
167
- options.googlePayMerchantId ?? null
169
+ options.capabilities ?? []
168
170
  )
169
171
  );
170
172
  }
@@ -188,16 +190,12 @@ export function presentApplePay(options: PresentApplePayOptions): Promise<string
188
190
  if (!options.owner.id) {
189
191
  throwCoded(ErrorCodes.INVALID_OWNER, 'Frame.presentApplePay requires owner.id');
190
192
  }
191
- if (!options.merchantId) {
192
- throwCoded(ErrorCodes.INVALID_MERCHANT_ID, 'Frame.presentApplePay requires merchantId');
193
- }
194
193
  return wrapPromise(
195
194
  FrameSDK.presentApplePay(
196
195
  options.owner.type,
197
196
  options.owner.id,
198
197
  options.amount,
199
- options.currency ?? 'usd',
200
- options.merchantId
198
+ options.currency ?? 'usd'
201
199
  )
202
200
  );
203
201
  }
@@ -225,8 +223,7 @@ export function presentGooglePay(options: PresentGooglePayOptions): Promise<stri
225
223
  options.amountCents,
226
224
  options.owner.type,
227
225
  options.owner.id,
228
- options.currencyCode ?? 'USD',
229
- options.googlePayMerchantId ?? null
226
+ options.currencyCode ?? 'USD'
230
227
  )
231
228
  );
232
229
  }
package/src/types.ts CHANGED
@@ -100,7 +100,12 @@ export type WalletOwner =
100
100
  /** @deprecated Use {@link WalletOwner}. Retained as an alias for source compatibility. */
101
101
  export type ApplePayOwner = WalletOwner;
102
102
 
103
- /** Options for Frame.presentApplePay. */
103
+ /**
104
+ * Options for Frame.presentApplePay.
105
+ *
106
+ * The Apple Pay merchant ID is set once at `Frame.initialize({ applePayMerchantId })`. There is
107
+ * no per-call merchant ID parameter — the SDK reads it from the singleton at present time.
108
+ */
104
109
  export interface PresentApplePayOptions {
105
110
  /** Payment amount in cents. */
106
111
  amount: number;
@@ -108,11 +113,14 @@ export interface PresentApplePayOptions {
108
113
  currency?: string;
109
114
  /** Customer or account that owns the resulting payment method and charge. */
110
115
  owner: WalletOwner;
111
- /** Apple Pay merchant ID configured in your Apple Developer account. */
112
- merchantId: string;
113
116
  }
114
117
 
115
- /** Options for Frame.presentGooglePay. */
118
+ /**
119
+ * Options for Frame.presentGooglePay.
120
+ *
121
+ * The Google Pay merchant ID is set once at `Frame.initialize({ googlePayMerchantId })`. There
122
+ * is no per-call merchant ID parameter — the SDK reads it from the singleton at present time.
123
+ */
116
124
  export interface PresentGooglePayOptions {
117
125
  /** Payment amount in cents. */
118
126
  amountCents: number;
@@ -120,8 +128,6 @@ export interface PresentGooglePayOptions {
120
128
  owner: WalletOwner;
121
129
  /** ISO 4217 currency code. Defaults to 'USD'. */
122
130
  currencyCode?: string;
123
- /** Optional override for the Google Pay merchant ID. */
124
- googlePayMerchantId?: string;
125
131
  }
126
132
 
127
133
  /**