framepayments-react-native 2.2.1 → 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.
Files changed (35) hide show
  1. package/FrameReactNative.podspec +44 -0
  2. package/Package.swift +47 -0
  3. package/README.md +64 -33
  4. package/android/build.gradle +9 -3
  5. package/android/src/main/java/com/framepayments/reactnativeframe/FrameCheckoutActivity.kt +18 -3
  6. package/android/src/main/java/com/framepayments/reactnativeframe/FrameFlowActivity.kt +16 -3
  7. package/android/src/main/java/com/framepayments/reactnativeframe/FrameGooglePayActivity.kt +0 -3
  8. package/android/src/main/java/com/framepayments/reactnativeframe/FrameOnboardingActivity.kt +2 -5
  9. package/android/src/main/java/com/framepayments/reactnativeframe/FrameSDKModule.kt +13 -11
  10. package/ios/ApplePayPresenter.swift +1 -4
  11. package/ios/FramePreloader.h +23 -0
  12. package/ios/FrameSDKBridge.m +17 -17
  13. package/ios/FrameSDKBridge.swift +109 -54
  14. package/lib/native.d.ts +12 -2
  15. package/lib/native.d.ts.map +1 -1
  16. package/lib/native.js +18 -18
  17. package/lib/types.d.ts +12 -6
  18. package/lib/types.d.ts.map +1 -1
  19. package/package.json +8 -2
  20. package/src/__tests__/native.test.ts +60 -74
  21. package/src/native.ts +37 -34
  22. package/src/types.ts +12 -6
  23. package/ios/FrameReactNative.podspec +0 -32
  24. package/lib/__tests__/native.test.d.ts +0 -32
  25. package/lib/__tests__/native.test.d.ts.map +0 -1
  26. package/lib/__tests__/native.test.js +0 -101
  27. package/lib/components/FrameApplePayButton.d.ts +0 -30
  28. package/lib/components/FrameApplePayButton.d.ts.map +0 -1
  29. package/lib/components/FrameApplePayButton.js +0 -42
  30. package/lib/components/FrameGooglePayButton.d.ts +0 -28
  31. package/lib/components/FrameGooglePayButton.d.ts.map +0 -1
  32. package/lib/components/FrameGooglePayButton.js +0 -24
  33. package/lib/components/index.d.ts +0 -5
  34. package/lib/components/index.d.ts.map +0 -1
  35. package/lib/components/index.js +0 -2
@@ -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,47 +201,39 @@ 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
 
226
231
  describe('presentGooglePay', () => {
232
+ beforeEach(() => {
233
+ // presentGooglePay is Android-only; tests run on Android except where noted.
234
+ mockPlatform.OS = 'android';
235
+ });
236
+
227
237
  it('throws NOT_INITIALIZED if initialize was not called', async () => {
228
238
  try {
229
239
  await presentGooglePay({ amountCents: 100, owner: { type: 'account', id: 'acct_1' } });
@@ -257,30 +267,29 @@ describe('presentGooglePay', () => {
257
267
  });
258
268
 
259
269
  it('forwards account owner; resolves with transfer id string', async () => {
260
- await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
270
+ await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx', googlePayMerchantId: 'BCR2DN4T...' });
261
271
  const result = await presentGooglePay({
262
272
  amountCents: 9999,
263
273
  owner: { type: 'account', id: 'acct_1' },
264
274
  currencyCode: 'EUR',
265
- googlePayMerchantId: 'BCR2DN4T...',
266
275
  });
267
- expect(mockPresentGooglePay).toHaveBeenCalledWith(9999, 'account', 'acct_1', 'EUR', 'BCR2DN4T...');
276
+ expect(mockPresentGooglePay).toHaveBeenCalledWith(9999, 'account', 'acct_1', 'EUR');
268
277
  expect(result).toBe('tr_4');
269
278
  });
270
279
 
271
280
  it('forwards customer owner; resolves with charge intent id string', async () => {
272
- await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
281
+ await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx', googlePayMerchantId: 'BCR2DN4T...' });
273
282
  await presentGooglePay({
274
283
  amountCents: 4242,
275
284
  owner: { type: 'customer', id: 'cus_1' },
276
285
  });
277
- expect(mockPresentGooglePay).toHaveBeenCalledWith(4242, 'customer', 'cus_1', 'USD', null);
286
+ expect(mockPresentGooglePay).toHaveBeenCalledWith(4242, 'customer', 'cus_1', 'USD');
278
287
  });
279
288
 
280
- it('defaults currencyCode to USD and merchantId to null', async () => {
281
- 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...' });
282
291
  await presentGooglePay({ amountCents: 100, owner: { type: 'account', id: 'acct_1' } });
283
- expect(mockPresentGooglePay).toHaveBeenCalledWith(100, 'account', 'acct_1', 'USD', null);
292
+ expect(mockPresentGooglePay).toHaveBeenCalledWith(100, 'account', 'acct_1', 'USD');
284
293
  });
285
294
  });
286
295
 
@@ -296,46 +305,23 @@ describe('presentOnboarding', () => {
296
305
  expect(mockPresentOnboarding).not.toHaveBeenCalled();
297
306
  });
298
307
 
299
- it('calls native presentOnboarding with accountId, capabilities, and null merchantId after initialize on iOS', async () => {
300
- mockPlatform.OS = 'ios';
308
+ it('calls native presentOnboarding with accountId and capabilities only no merchant params', async () => {
301
309
  await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
302
310
  const result = await presentOnboarding({ accountId: 'acct_1', capabilities: ['kyc', 'bank_account_verification'] });
303
- expect(mockPresentOnboarding).toHaveBeenCalledWith('acct_1', ['kyc', 'bank_account_verification'], null);
311
+ expect(mockPresentOnboarding).toHaveBeenCalledWith('acct_1', ['kyc', 'bank_account_verification']);
304
312
  expect(result).toEqual({ status: 'completed', paymentMethodId: 'pm_1' });
305
313
  });
306
314
 
307
- it('passes null for accountId and empty array for capabilities when not provided on iOS', async () => {
308
- mockPlatform.OS = 'ios';
315
+ it('passes null accountId and empty array for capabilities when not provided', async () => {
309
316
  await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
310
317
  await presentOnboarding({});
311
- expect(mockPresentOnboarding).toHaveBeenCalledWith(null, [], null);
312
- });
313
-
314
- it('forwards applePayMerchantId on iOS', async () => {
315
- mockPlatform.OS = 'ios';
316
- await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
317
- await presentOnboarding({ accountId: 'acct_1', capabilities: ['kyc'], applePayMerchantId: 'merchant.com.example' });
318
- expect(mockPresentOnboarding).toHaveBeenCalledWith('acct_1', ['kyc'], 'merchant.com.example');
319
- });
320
-
321
- it('ignores applePayMerchantId on Android', async () => {
322
- mockPlatform.OS = 'android';
323
- await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
324
- await presentOnboarding({ accountId: 'acct_1', capabilities: ['kyc'], applePayMerchantId: 'merchant.com.example' });
325
- expect(mockPresentOnboarding).toHaveBeenCalledWith('acct_1', ['kyc'], null);
318
+ expect(mockPresentOnboarding).toHaveBeenCalledWith(null, []);
326
319
  });
327
320
 
328
- it('forwards googlePayMerchantId to native presentOnboarding on Android', async () => {
321
+ it('behaves the same on Android merchant IDs are init-only across both platforms', async () => {
329
322
  mockPlatform.OS = 'android';
330
- await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
331
- await presentOnboarding({ accountId: 'acct_1', capabilities: ['kyc'], googlePayMerchantId: 'BCR2DN4T...' });
332
- expect(mockPresentOnboarding).toHaveBeenCalledWith('acct_1', ['kyc'], 'BCR2DN4T...');
333
- });
334
-
335
- it('ignores googlePayMerchantId on iOS', async () => {
336
- mockPlatform.OS = 'ios';
337
- await initialize({ secretKey: 'sk_xxx', publishableKey: 'pk_xxx' });
338
- await presentOnboarding({ accountId: 'acct_1', capabilities: ['kyc'], googlePayMerchantId: 'BCR2DN4T...' });
339
- 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']);
340
326
  });
341
327
  });
package/src/native.ts CHANGED
@@ -13,20 +13,6 @@ import type {
13
13
  } from './types';
14
14
  import { ErrorCodes } from './errors';
15
15
 
16
- /**
17
- * Throw a coded error from synchronous JS validation. Mirrors the `code`/`message`
18
- * shape that native rejections produce so consumers can catch `e.code === 'INVALID_*'`
19
- * uniformly.
20
- */
21
- function throwCoded(code: string, message: string): never {
22
- const err = new Error(message) as Error & { code: string };
23
- err.code = code;
24
- throw err;
25
- }
26
-
27
- // theme is iOS-only today: frame-android does not yet have a matching theme API,
28
- // so the field is accepted on both platforms but ignored on Android until it does.
29
-
30
16
  const LINKING_ERROR =
31
17
  `The package 'framepayments-react-native' doesn't seem to be linked. Make sure you have run 'pod install' (iOS) or rebuilt the app (Android).`;
32
18
 
@@ -41,12 +27,38 @@ const FrameSDK = NativeModules.FrameSDK
41
27
  }
42
28
  );
43
29
 
30
+ /**
31
+ * Throw a coded error from synchronous JS validation. Mirrors the `code`/`message`
32
+ * shape that native rejections produce so consumers can catch `e.code === 'INVALID_*'`
33
+ * uniformly.
34
+ */
35
+ function throwCoded(code: string, message: string): never {
36
+ const err = new Error(message) as Error & { code: string };
37
+ err.code = code;
38
+ throw err;
39
+ }
40
+
41
+ // theme is iOS-only today: frame-android does not yet have a matching theme API,
42
+ // so the field is accepted on both platforms but ignored on Android until it does.
43
+
44
44
  let isInitialized = false;
45
45
 
46
46
  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
  }
@@ -179,22 +181,21 @@ export function presentOnboarding(options: {
179
181
  */
180
182
  export function presentApplePay(options: PresentApplePayOptions): Promise<string> {
181
183
  guardInitialized();
184
+ if (Platform.OS !== 'ios') {
185
+ throwCoded('PLATFORM_UNSUPPORTED', 'Frame.presentApplePay is iOS-only; use presentGooglePay on Android.');
186
+ }
182
187
  if (!options?.owner || (options.owner.type !== 'customer' && options.owner.type !== 'account')) {
183
188
  throwCoded(ErrorCodes.INVALID_OWNER, 'Frame.presentApplePay requires owner: { type: "customer" | "account", id: string }');
184
189
  }
185
190
  if (!options.owner.id) {
186
191
  throwCoded(ErrorCodes.INVALID_OWNER, 'Frame.presentApplePay requires owner.id');
187
192
  }
188
- if (!options.merchantId) {
189
- throwCoded(ErrorCodes.INVALID_MERCHANT_ID, 'Frame.presentApplePay requires merchantId');
190
- }
191
193
  return wrapPromise(
192
194
  FrameSDK.presentApplePay(
193
195
  options.owner.type,
194
196
  options.owner.id,
195
197
  options.amount,
196
- options.currency ?? 'usd',
197
- options.merchantId
198
+ options.currency ?? 'usd'
198
199
  )
199
200
  );
200
201
  }
@@ -208,6 +209,9 @@ export function presentApplePay(options: PresentApplePayOptions): Promise<string
208
209
  */
209
210
  export function presentGooglePay(options: PresentGooglePayOptions): Promise<string> {
210
211
  guardInitialized();
212
+ if (Platform.OS !== 'android') {
213
+ throwCoded('PLATFORM_UNSUPPORTED', 'Frame.presentGooglePay is Android-only; use presentApplePay on iOS.');
214
+ }
211
215
  if (!options?.owner || (options.owner.type !== 'customer' && options.owner.type !== 'account')) {
212
216
  throwCoded(ErrorCodes.INVALID_OWNER, 'Frame.presentGooglePay requires owner: { type: "customer" | "account", id: string }');
213
217
  }
@@ -219,8 +223,7 @@ export function presentGooglePay(options: PresentGooglePayOptions): Promise<stri
219
223
  options.amountCents,
220
224
  options.owner.type,
221
225
  options.owner.id,
222
- options.currencyCode ?? 'USD',
223
- options.googlePayMerchantId ?? null
226
+ options.currencyCode ?? 'USD'
224
227
  )
225
228
  );
226
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
  /**
@@ -1,32 +0,0 @@
1
- require 'json'
2
-
3
- package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
-
5
- Pod::Spec.new do |s|
6
- s.name = 'FrameReactNative'
7
- s.version = package['version']
8
- s.summary = package['description']
9
- s.homepage = package['homepage']
10
- s.license = package['license']
11
- s.authors = package['author']
12
- s.platforms = { :ios => '17.0' }
13
- s.source = { :git => package['repository']['url'], :tag => "#{s.version}" }
14
- s.source_files = '**/*.{h,m,mm,swift}'
15
- s.requires_arc = true
16
-
17
- s.dependency 'React-Core'
18
-
19
- # Frame-iOS SDK (Frame + FrameOnboarding) must be available to the app.
20
- #
21
- # SPM users (RN >= 0.73): add framepayments-react-native via Swift Package Manager.
22
- # Frame and FrameOnboarding are declared as dependencies in Package.swift and resolve
23
- # automatically — no manual step required.
24
- #
25
- # CocoaPods users: add frame-ios manually via Xcode:
26
- # File → Add Package Dependencies → https://github.com/Frame-Payments/frame-ios
27
- # Add both the "Frame-iOS" and "Frame-Onboarding" products to your app target.
28
- # If Frame-iOS is published as a pod in the future, replace the above with:
29
- # s.dependency 'Frame-iOS'
30
-
31
- install_modules_dependencies(s) if respond_to?(:install_modules_dependencies)
32
- end
@@ -1,32 +0,0 @@
1
- /**
2
- * Unit tests for the native module bridge (initialize, presentCheckout, presentCart).
3
- * NativeModules.FrameSDK is mocked.
4
- */
5
- declare const mockInitialize: jest.Mock<Promise<void>, [_apiKey: string, _debugMode: boolean], any>;
6
- declare const mockPresentCheckout: jest.Mock<Promise<{
7
- id: string;
8
- amount: number;
9
- }>, [_customerId: unknown, _amount: number], any>;
10
- declare const mockPresentCart: jest.Mock<Promise<{
11
- id: string;
12
- amount: number;
13
- }>, [_customerId: unknown, _items: unknown[], _shipping: number], any>;
14
- declare let initialize: (opts: {
15
- apiKey: string;
16
- debugMode?: boolean;
17
- }) => void;
18
- declare let presentCheckout: (opts: {
19
- customerId?: string | null;
20
- amount: number;
21
- }) => Promise<unknown>;
22
- declare let presentCart: (opts: {
23
- customerId?: string | null;
24
- items: Array<{
25
- id: string;
26
- title: string;
27
- amountInCents: number;
28
- imageUrl: string;
29
- }>;
30
- shippingAmountInCents: number;
31
- }) => Promise<unknown>;
32
- //# sourceMappingURL=native.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"native.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/native.test.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,QAAA,MAAM,cAAc,uEAAuE,CAAC;AAC5F,QAAA,MAAM,mBAAmB;;;iDAAqG,CAAC;AAC/H,QAAA,MAAM,eAAe;;;sEAA0H,CAAC;AAahJ,QAAA,IAAI,UAAU,EAAE,CAAC,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,KAAK,IAAI,CAAC;AACxE,QAAA,IAAI,eAAe,EAAE,CAAC,IAAI,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAChG,QAAA,IAAI,WAAW,EAAE,CAAC,IAAI,EAAE;IACtB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrF,qBAAqB,EAAE,MAAM,CAAC;CAC/B,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC"}
@@ -1,101 +0,0 @@
1
- "use strict";
2
- /**
3
- * Unit tests for the native module bridge (initialize, presentCheckout, presentCart).
4
- * NativeModules.FrameSDK is mocked.
5
- */
6
- const mockInitialize = jest.fn((_apiKey, _debugMode) => Promise.resolve());
7
- const mockPresentCheckout = jest.fn((_customerId, _amount) => Promise.resolve({ id: 'ci_1', amount: 10000 }));
8
- const mockPresentCart = jest.fn((_customerId, _items, _shipping) => Promise.resolve({ id: 'ci_2', amount: 15000 }));
9
- jest.mock('react-native', () => ({
10
- NativeModules: {
11
- FrameSDK: {
12
- initialize: mockInitialize,
13
- presentCheckout: mockPresentCheckout,
14
- presentCart: mockPresentCart,
15
- },
16
- },
17
- }));
18
- // Re-import after mock so we get the mocked NativeModules
19
- let initialize;
20
- let presentCheckout;
21
- let presentCart;
22
- beforeEach(() => {
23
- jest.resetModules();
24
- mockInitialize.mockClear();
25
- mockPresentCheckout.mockClear();
26
- mockPresentCart.mockClear();
27
- const native = require('../native');
28
- initialize = native.initialize;
29
- presentCheckout = native.presentCheckout;
30
- presentCart = native.presentCart;
31
- });
32
- describe('initialize', () => {
33
- it('calls native FrameSDK.initialize with apiKey and debugMode', () => {
34
- initialize({ apiKey: 'sk_test_xxx', debugMode: true });
35
- expect(mockInitialize).toHaveBeenCalledTimes(1);
36
- expect(mockInitialize).toHaveBeenCalledWith('sk_test_xxx', true);
37
- });
38
- it('defaults debugMode to false', () => {
39
- initialize({ apiKey: 'sk_test_yyy' });
40
- expect(mockInitialize).toHaveBeenCalledWith('sk_test_yyy', false);
41
- });
42
- it('throws if apiKey is missing', () => {
43
- expect(() => initialize({ apiKey: '' })).toThrow();
44
- expect(() => initialize({})).toThrow();
45
- expect(mockInitialize).not.toHaveBeenCalled();
46
- });
47
- });
48
- describe('presentCheckout', () => {
49
- it('throws NOT_INITIALIZED if initialize was not called', async () => {
50
- try {
51
- await presentCheckout({ amount: 10000 });
52
- expect(true).toBe(false);
53
- }
54
- catch (e) {
55
- expect(e.code).toBe('NOT_INITIALIZED');
56
- expect(e.message).toContain('initialized');
57
- }
58
- expect(mockPresentCheckout).not.toHaveBeenCalled();
59
- });
60
- it('calls native presentCheckout with customerId and amount after initialize', async () => {
61
- initialize({ apiKey: 'sk_xxx' });
62
- const result = await presentCheckout({ customerId: 'cus_1', amount: 10000 });
63
- expect(mockPresentCheckout).toHaveBeenCalledWith('cus_1', 10000);
64
- expect(result).toEqual({ id: 'ci_1', amount: 10000 });
65
- });
66
- it('passes null for customerId when not provided', async () => {
67
- initialize({ apiKey: 'sk_xxx' });
68
- await presentCheckout({ amount: 5000 });
69
- expect(mockPresentCheckout).toHaveBeenCalledWith(null, 5000);
70
- });
71
- });
72
- describe('presentCart', () => {
73
- const items = [
74
- { id: '1', title: 'Item A', amountInCents: 1000, imageUrl: 'https://example.com/a.jpg' },
75
- ];
76
- it('throws NOT_INITIALIZED if initialize was not called', async () => {
77
- try {
78
- await presentCart({ items, shippingAmountInCents: 500 });
79
- expect(true).toBe(false);
80
- }
81
- catch (e) {
82
- expect(e.code).toBe('NOT_INITIALIZED');
83
- }
84
- expect(mockPresentCart).not.toHaveBeenCalled();
85
- });
86
- it('calls native presentCart with customerId, items, shipping after initialize', async () => {
87
- initialize({ apiKey: 'sk_xxx' });
88
- const result = await presentCart({
89
- customerId: 'cus_2',
90
- items,
91
- shippingAmountInCents: 500,
92
- });
93
- expect(mockPresentCart).toHaveBeenCalledWith('cus_2', items, 500);
94
- expect(result).toEqual({ id: 'ci_2', amount: 15000 });
95
- });
96
- it('passes null for customerId when not provided', async () => {
97
- initialize({ apiKey: 'sk_xxx' });
98
- await presentCart({ items, shippingAmountInCents: 0 });
99
- expect(mockPresentCart).toHaveBeenCalledWith(null, items, 0);
100
- });
101
- });
@@ -1,30 +0,0 @@
1
- /**
2
- * <FrameApplePayButton /> — iOS-only React Native view that wraps Frame iOS SDK's
3
- * native FrameApplePayButton (PassKit + automatic device attestation).
4
- *
5
- * On non-iOS platforms this component renders nothing, so consumers can place it
6
- * unconditionally in their JSX.
7
- */
8
- import * as React from 'react';
9
- import { type NativeSyntheticEvent, type ViewProps } from 'react-native';
10
- import type { ApplePayButtonStyle, ApplePayButtonType, ApplePayOwner, FrameApplePayResultEvent } from '../types';
11
- export interface FrameApplePayButtonProps extends ViewProps {
12
- /** Payment amount in cents. */
13
- amount: number;
14
- /** ISO 4217 currency code. Defaults to 'usd'. */
15
- currency?: string;
16
- /** Customer or account that owns the resulting payment method. */
17
- owner: ApplePayOwner;
18
- /** Apple Pay merchant ID configured in your Apple Developer account. */
19
- merchantId: string;
20
- /** When true, the button renders a "Or" divider beneath itself for inline checkout layouts. */
21
- addCheckoutDivider?: boolean;
22
- /** PassKit button type. Defaults to 'buy'. */
23
- buttonType?: ApplePayButtonType;
24
- /** PassKit button style. Defaults to 'black'. */
25
- buttonStyle?: ApplePayButtonStyle;
26
- /** Fired when payment completes, fails, or the button reports an error. */
27
- onResult: (event: NativeSyntheticEvent<FrameApplePayResultEvent>) => void;
28
- }
29
- export declare const FrameApplePayButton: React.FC<FrameApplePayButtonProps>;
30
- //# sourceMappingURL=FrameApplePayButton.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"FrameApplePayButton.d.ts","sourceRoot":"","sources":["../../src/components/FrameApplePayButton.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAIL,KAAK,oBAAoB,EACzB,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EACV,mBAAmB,EACnB,kBAAkB,EAClB,aAAa,EACb,wBAAwB,EACzB,MAAM,UAAU,CAAC;AAElB,MAAM,WAAW,wBAAyB,SAAQ,SAAS;IACzD,+BAA+B;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,KAAK,EAAE,aAAa,CAAC;IACrB,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAC;IACnB,+FAA+F;IAC/F,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,8CAA8C;IAC9C,UAAU,CAAC,EAAE,kBAAkB,CAAC;IAChC,iDAAiD;IACjD,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,2EAA2E;IAC3E,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,CAAC,wBAAwB,CAAC,KAAK,IAAI,CAAC;CAC3E;AA+BD,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CAclE,CAAC"}