@jimrising/easymerchantsdk-react-native 2.1.7 → 2.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # EasyMerchant SDK React Native Integration
2
2
 
3
- This guide provides step-by-step instructions for integrating the EasyMerchant SDK into a React Native application for both Android and iOS platforms. The SDK supports a unified configuration structure with minor platform-specific differences in method calls for initiating payments and handling responses. The provided `App.js` demonstrates a fully configurable payment interface supporting card payments, ACH transfers, recurring payments, 3D Secure authentication, and customizable billing/additional information fields with a user-friendly UI.
4
-
3
+ This guide provides instructions for integrating the EasyMerchant SDK into a React Native application for Android and iOS platforms. It focuses on creating a unified configuration object and using it to process payments with `RNEasymerchantsdk.makePayment` (Android) and `EasyMerchantSdk.makePayment` (iOS). The configuration supports card payments, ACH transfers, recurring payments, 3D Secure authentication, and customizable billing/additional fields.
5
4
 
6
5
  ## Prerequisites
7
6
 
@@ -10,7 +9,7 @@ This guide provides step-by-step instructions for integrating the EasyMerchant S
10
9
  - **Ruby**: Version 3.2.8 or higher for iOS setup (required for CocoaPods).
11
10
  - **Xcode**: Version 14 or higher for iOS development.
12
11
  - **Android Studio**: For Android development, with Gradle configured.
13
- - **EasyMerchant SDK Credentials**: Obtain API keys and secret keys for `sandbox` and `staging` environments from your EasyMerchant account.
12
+ - **EasyMerchant SDK Credentials**: Obtain API keys and secret keys for `sandbox` or `production` environments.
14
13
 
15
14
  ## Installation
16
15
 
@@ -20,7 +19,7 @@ Add the EasyMerchant SDK to your `package.json` under `dependencies`:
20
19
 
21
20
  ```json
22
21
  "dependencies": {
23
- "@jimrising/easymerchantsdk-react-native": "^2.1.7"
22
+ "@jimrising/easymerchantsdk-react-native": "^2.1.8"
24
23
  }
25
24
  ```
26
25
 
@@ -51,7 +50,7 @@ allprojects {
51
50
  }
52
51
  ```
53
52
 
54
- 2. Define `GITHUB_USERNAME` and `GITHUB_PASSWORD` in `android/gradle.properties` or your CI/CD system. For example:
53
+ 2. Define `GITHUB_USERNAME` and `GITHUB_PASSWORD` in `android/gradle.properties`:
55
54
 
56
55
  ```properties
57
56
  GITHUB_USERNAME=your-github-username
@@ -141,318 +140,209 @@ pod install --repo-update
141
140
 
142
141
  ## Usage
143
142
 
144
- The provided `App.js` is a complete implementation showcasing the EasyMerchant SDK's capabilities with a configurable UI. Below is a guide to integrating and using the SDK in your React Native application.
143
+ This section explains how to configure and process payments using a unified configuration object, passed to `RNEasymerchantsdk.makePayment` (Android) or `EasyMerchantSdk.makePayment` (iOS). The configuration includes all necessary fields with default values, supporting card payments, ACH transfers, recurring payments, and 3D Secure authentication.
145
144
 
146
145
  ### 1. Import Required Modules
147
146
 
148
- Import necessary React Native components and the EasyMerchant SDK NativeModules:
149
-
150
147
  ```javascript
151
- import React, { useState, useEffect } from 'react';
152
- import {
153
- StyleSheet, Text, View, TextInput, Switch, TouchableOpacity, NativeModules,
154
- NativeEventEmitter, Platform, Alert, ScrollView, KeyboardAvoidingView, Modal,
155
- TouchableWithoutFeedback
156
- } from 'react-native';
157
-
148
+ import { NativeModules, Platform, Alert } from 'react-native';
158
149
  const { RNEasymerchantsdk, EasyMerchantSdk } = NativeModules;
159
150
  ```
160
151
 
161
152
  ### 2. Define Configuration
162
153
 
163
- Define a unified payment configuration object for both platforms, as shown in `App.js`:
154
+ Create a unified `config` object with default values for all required and optional fields.
164
155
 
165
156
  ```javascript
166
- const [amount, setAmount] = useState('5.0');
167
- const [email, setEmail] = useState('john.doe@example.com');
168
- const [name, setName] = useState('John Doe');
169
- const [environment, setEnvironment] = useState('sandbox');
170
- const [isRecurring, setIsRecurring] = useState(false);
171
- const [isAuthenticatedACH, setAuthenticatedACH] = useState(true);
172
- const [isSecureAuthentication, setSecureAuthentication] = useState(true);
173
- const [isEmail, setIsEmail] = useState(true);
174
- const [themeConfiguration, setThemeConfiguration] = useState({
175
- theme: 'light',
176
- bodyBackgroundColor: '#121212',
177
- containerBackgroundColor: '#1E1E1E',
178
- primaryFontColor: '#FFFFFF',
179
- secondaryFontColor: '#B0B0B0',
180
- primaryButtonBackgroundColor: '#2563EB',
181
- primaryButtonHoverColor: '#1D4ED8',
182
- primaryButtonFontColor: '#FFFFFF',
183
- secondaryButtonBackgroundColor: '#374151',
184
- secondaryButtonHoverColor: '#4B5563',
185
- secondaryButtonFontColor: '#E5E7EB',
186
- borderRadius: '8',
187
- fontSize: '16',
188
- fontWeight: 500,
189
- fontFamily: '"Inter", sans-serif'
190
- });
191
- const [grailPayParams, setGrailPayParams] = useState({
192
- role: 'business',
193
- timeout: 10,
194
- isSandbox: true,
195
- brandingName: 'Lyfecycle Payments',
196
- finderSubtitle: 'Search for your bank',
197
- searchPlaceholder: 'Enter bank name',
198
- });
199
- const [recurringData, setRecurringData] = useState({
200
- allowCycles: 2,
201
- intervals: ['daily', 'weekly', 'monthly'],
202
- recurringStartType: 'fixed',
203
- recurringStartDate: new Date().toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' }),
204
- });
205
- const [paymentConfig, setPaymentConfig] = useState({
206
- currency: 'usd',
207
- saveCard: true,
208
- saveAccount: true,
209
- showReceipt: true,
210
- showDonate: false,
211
- showTotal: true,
212
- showSubmitButton: true,
213
- paymentMethods: ['card', 'ach'],
157
+ const config = {
158
+ environment: 'sandbox', // String, e.g., 'sandbox' or 'production'
159
+ amount: '5.0', // String or number, must be a positive value
160
+ currency: 'usd', // String, currency code, e.g., 'usd'
161
+ tokenOnly: false, // Boolean, set to true for tokenization only, false for full payment
162
+ saveCard: true, // Boolean, set to true to save card details, false otherwise
163
+ saveAccount: true, // Boolean, set to true to save account details for ACH, false otherwise
164
+ submitButtonText: 'Submit', // String, text for the payment button
165
+ authenticatedACH: true, // Boolean, set to true to enable authenticated ACH, false otherwise
166
+ secureAuthentication: true, // Boolean, set to true to enable 3D Secure, false otherwise
167
+ showReceipt: true, // Boolean, set to true to show receipt after payment, false otherwise
168
+ showDonate: false, // Boolean, set to true to show donation option, false otherwise
169
+ showTotal: true, // Boolean, set to true to display total amount, false otherwise
170
+ showSubmitButton: true, // Boolean, set to true to show submit button, false otherwise
171
+ paymentMethods: ['card', 'ach'], // Array of strings, must include at least one method, e.g., ['card', 'ach']
172
+ apiKey: 'replace-with-api-key', // String, environment-specific API key
173
+ secretKey: 'replace-with-secret-key', // String, environment-specific secret key
214
174
  fields: {
215
- visibility: { billing: false, additional: false },
175
+ visibility: { billing: false, additional: false }, // Object, visibility for fields
216
176
  billing: [
217
177
  { name: 'address', required: true, value: '123 Main Street, Suite 100' },
218
178
  { name: 'country', required: true, value: 'United States' },
219
179
  { name: 'state', required: true, value: 'California' },
220
180
  { name: 'city', required: false, value: 'San Francisco' },
221
- { name: 'postal_code', required: true, value: '94105' },
181
+ { name: 'postal_code', required: true, value: '94105' }
222
182
  ],
223
183
  additional: [
224
184
  { name: 'phone_number', required: false, value: '+1-555-123-4567' },
225
- { name: 'description', required: true, value: 'Test payment for development purposes' },
226
- ],
185
+ { name: 'description', required: true, value: 'Test payment for development purposes' }
186
+ ]
227
187
  },
228
- });
229
- const [apiKeys, setApiKeys] = useState({
230
- sandbox: {
231
- apiKey: 'apiKey',
232
- secretKey: 'secretKey',
188
+ appearanceSettings: {
189
+ theme: 'light', // String, UI theme, e.g., 'light' or 'dark'
190
+ bodyBackgroundColor: '#121212', // String, hex color for body background
191
+ containerBackgroundColor: '#1E1E1E', // String, hex color for container background
192
+ primaryFontColor: '#FFFFFF', // String, hex color for primary font
193
+ secondaryFontColor: '#B0B0B0', // String, hex color for secondary font
194
+ primaryButtonBackgroundColor: '#2563EB', // String, hex color for primary button
195
+ primaryButtonHoverColor: '#1D4ED8', // String, hex color for primary button hover
196
+ primaryButtonFontColor: '#FFFFFF', // String, hex color for primary button font
197
+ secondaryButtonBackgroundColor: '#374151', // String, hex color for secondary button
198
+ secondaryButtonHoverColor: '#4B5563', // String, hex color for secondary button hover
199
+ secondaryButtonFontColor: '#E5E7EB', // String, hex color for secondary button font
200
+ borderRadius: '8', // String, border radius in pixels
201
+ fontSize: '16', // String, font size in pixels
202
+ fontWeight: 500, // Number, font weight, e.g., 400, 500, 700
203
+ fontFamily: '"Inter", sans-serif' // String, font family
233
204
  },
234
- staging: {
235
- apiKey: 'apiKey',
236
- secretKey: 'secretKey',
205
+ email: 'john.doe@example.com', // String, customer email
206
+ name: 'John Doe', // String, customer name
207
+ isEmail: true, // Boolean, set to true to allow email editing, false otherwise
208
+ metadata: {
209
+ metadataOne: 'metadataOne', // String, custom metadata key-value
210
+ metadataTwo: 'metadataTwo' // String, custom metadata key-value
237
211
  },
238
- });
212
+ grailPayParams: {
213
+ role: 'business', // String, GrailPay role
214
+ timeout: 10, // Number, timeout in seconds
215
+ isSandbox: true, // Boolean, set to true for GrailPay sandbox mode
216
+ brandingName: 'Lyfecycle Payments', // String, branding name
217
+ finderSubtitle: 'Search for your bank', // String, bank finder subtitle
218
+ searchPlaceholder: 'Enter bank name' // String, search placeholder
219
+ },
220
+ is_recurring: false, // Boolean, set to true to enable recurring payments
221
+ numOfCycle: 2, // Number, minimum 2 cycles for recurring payments
222
+ recurringIntervals: ['daily', 'weekly', 'monthly'], // Array of strings, intervals for recurring payments
223
+ recurringStartDateType: 'fixed', // String, recurring start type, e.g., 'fixed' or 'custom'
224
+ recurringStartDate: '28/08/2025' // String, recurring start date in DD/MM/YYYY format
225
+ };
239
226
  ```
240
227
 
241
- This configuration includes:
242
- - **Basic Info**: `amount`, `email`, `name`.
243
- - **Environment**: `sandbox` or `staging` with corresponding API keys.
244
- - **Payment Options**: Toggles for recurring payments, authenticated ACH, 3D Secure, and email editability.
245
- - **Billing and Additional Fields**: Configurable fields with visibility and required flags.
246
- - **Theming**: Unified `themeConfiguration` for both platforms.
247
- - **GrailPay Params**: Settings for bank search UI.
248
- - **Recurring Data**: Options for recurring payment cycles, intervals, and start date.
228
+ **Notes**:
229
+ - Replace `apiKey` and `secretKey` with your actual credentials.
230
+ - Ensure `paymentMethods` includes at least one valid method (`'card'` or `'ach'` for Android; mapped to `'Card'` or `'Bank'` for iOS).
231
+ - For recurring payments, set `is_recurring: true` and ensure `recurringIntervals` has at least one interval (`'daily'`, `'weekly'`, `'monthly'`).
232
+ - Set `fields.billing` and `fields.additional` to empty arrays (`[]`) if not needed.
233
+ - Adjust `appearanceSettings` colors to match your app’s design.
234
+ - Store sensitive data like `apiKey` and `secretKey` securely (e.g., in environment variables).
249
235
 
250
- ### 3. Initialize Environment
236
+ ### 3. Initialize Environment (iOS Only)
251
237
 
252
- Configure the SDK environment (`sandbox` or `staging`) with API keys and secret keys:
238
+ For iOS, configure the environment before processing payments:
253
239
 
254
240
  ```javascript
255
- useEffect(() => {
256
- const updateEnvironment = async () => {
257
- const { apiKey, secretKey } = apiKeys[environment];
258
- if (Platform.OS === 'ios') {
259
- setIsEnvironmentLoading(true);
260
- try {
261
- await EasyMerchantSdk.setViewController();
262
- await EasyMerchantSdk.configureEnvironment(environment, apiKey, secretKey);
263
- console.log(`iOS Environment configured: ${environment} with key ${apiKey}`);
264
- } catch (err) {
265
- console.error('iOS Initialization Error:', err);
266
- Alert.alert('Error', `Failed to configure iOS environment: ${err.message}`);
267
- } finally {
268
- setIsEnvironmentLoading(false);
269
- }
270
- }
271
- };
272
- updateEnvironment();
273
- }, [environment, apiKeys]);
241
+ const initializeEnvironment = async () => {
242
+ try {
243
+ await EasyMerchantSdk.setViewController();
244
+ await EasyMerchantSdk.configureEnvironment(config.environment, config.apiKey, config.secretKey);
245
+ console.log(`iOS Environment configured: ${config.environment}`);
246
+ } catch (error) {
247
+ console.error('iOS Initialization Error:', error);
248
+ Alert.alert('Error', `Failed to configure iOS environment: ${error.message}`);
249
+ }
250
+ };
274
251
  ```
275
252
 
276
- **Note**: Replace placeholder API keys with your actual credentials. Store them securely (e.g., in environment variables) rather than hardcoding.
277
-
278
- ### 4. Handle Payments
279
-
280
- The `buildPaymentConfiguration` function creates a unified configuration object, and the `handlePayment` function validates inputs and delegates to platform-specific handlers. At least one payment method (`card` or `ach`) and a valid amount are required. For recurring payments, at least one interval must be selected.
281
-
282
- #### Unified Configuration Builder
253
+ **Note**: Call `initializeEnvironment()` before processing payments on iOS. Android does not require this step.
283
254
 
284
- ```javascript
285
- const buildPaymentConfiguration = ({
286
- amount, environment, paymentConfig, themeConfiguration, grailPayParams,
287
- recurringData, isRecurring, isAuthenticatedACH, isSecureAuthentication,
288
- email, name, isEmail, metadata, apiKey, secretKey
289
- }) => {
290
- const selectedPaymentMethods = [...paymentConfig.paymentMethods];
291
- const config = {
292
- environment,
293
- amount,
294
- currency: 'usd',
295
- tokenOnly: false,
296
- saveCard: paymentConfig.saveCard,
297
- saveAccount: paymentConfig.saveAccount,
298
- submitButtonText: 'Submit',
299
- authenticatedACH: isAuthenticatedACH,
300
- secureAuthentication: isSecureAuthentication,
301
- showReceipt: paymentConfig.showReceipt,
302
- showDonate: paymentConfig.showDonate,
303
- showTotal: paymentConfig.showTotal,
304
- showSubmitButton: paymentConfig.showSubmitButton,
305
- paymentMethods: selectedPaymentMethods,
306
- apiKey,
307
- secretKey,
308
- fields: {
309
- visibility: paymentConfig.fields.visibility,
310
- billing: paymentConfig.fields.billing,
311
- additional: paymentConfig.fields.additional
312
- },
313
- appearanceSettings: themeConfiguration,
314
- email,
315
- name,
316
- isEmail,
317
- metadata,
318
- grailPayParams,
319
- is_recurring: isRecurring,
320
- numOfCycle: recurringData.allowCycles,
321
- recurringIntervals: recurringData.intervals,
322
- recurringStartDateType: recurringData.recurringStartType,
323
- recurringStartDate: recurringData.recurringStartDate
324
- };
325
- return config;
326
- };
327
- ```
255
+ ### 4. Process Payments
328
256
 
329
- #### Main Payment Handler
257
+ Use the `config` object to initiate payments. The process differs slightly by platform:
330
258
 
331
259
  ```javascript
332
260
  const handlePayment = async () => {
333
- if (!amount || isNaN(parseFloat(amount)) || parseFloat(amount) <= 0) {
334
- return Alert.alert('Error', 'Please enter a valid amount');
335
- }
336
- if (isRecurring && (!recurringData.intervals || recurringData.intervals.length === 0)) {
337
- return Alert.alert('Error', 'Please select at least one interval for recurring payment');
261
+ // Validate inputs
262
+ if (!config.amount || isNaN(parseFloat(config.amount)) || parseFloat(config.amount) <= 0) {
263
+ Alert.alert('Error', 'Please set a valid amount');
264
+ return;
338
265
  }
339
- if (!paymentConfig.paymentMethods || paymentConfig.paymentMethods.length === 0) {
340
- return Alert.alert('Error', 'Please select at least one payment method');
266
+ if (!config.paymentMethods || config.paymentMethods.length === 0) {
267
+ Alert.alert('Error', 'Please select at least one payment method');
268
+ return;
341
269
  }
342
-
343
- setLoading(true);
344
- const timeoutId = setTimeout(() => setLoading(false), 3000);
345
- try {
346
- const config = buildPaymentConfiguration({
347
- amount,
348
- environment,
349
- paymentConfig,
350
- themeConfiguration,
351
- grailPayParams,
352
- recurringData,
353
- isRecurring,
354
- isAuthenticatedACH,
355
- isSecureAuthentication,
356
- email,
357
- name,
358
- isEmail,
359
- metadata,
360
- apiKey: apiKeys[environment].apiKey,
361
- secretKey: apiKeys[environment].secretKey
362
- });
363
- await handlePlatformPayment(config);
364
- } catch (error) {
365
- console.error('Payment Error:', error);
366
- setResult(`Error: ${error.message ?? JSON.stringify(error)}`);
367
- Alert.alert('Payment Error', error.message ?? 'Unknown error');
368
- } finally {
369
- clearTimeout(timeoutId);
370
- setLoading(false);
270
+ if (config.is_recurring && (!config.recurringIntervals || config.recurringIntervals.length === 0)) {
271
+ Alert.alert('Error', 'Please select at least one interval for recurring payment');
272
+ return;
371
273
  }
372
- };
373
- ```
374
-
375
- #### Platform-Specific Payment Handler
376
274
 
377
- ```javascript
378
- const handlePlatformPayment = async (config) => {
379
275
  try {
380
276
  if (Platform.OS === 'android') {
277
+ // Android: Pass config object directly
381
278
  const response = await RNEasymerchantsdk.makePayment(config);
382
279
  const raw = response?.response ? JSON.parse(response.response) : {};
383
280
  const parsedResponse = {
384
281
  ...raw,
385
- billingInfo: safeParseMaybeJSON(raw.billingInfo),
386
- additional_info: safeParseMaybeJSON(raw.additional_info),
282
+ billingInfo: raw.billingInfo ? JSON.parse(raw.billingInfo) : raw.billingInfo,
283
+ additional_info: raw.additional_info ? JSON.parse(raw.additional_info) : raw.additional_info
387
284
  };
388
285
  console.log('Android Payment Response:', parsedResponse);
389
- setResult(JSON.stringify(parsedResponse, null, 2));
286
+ Alert.alert('Success', 'Payment processed!');
390
287
  } else {
288
+ // iOS: Initialize environment and pass config as JSON string
289
+ await initializeEnvironment();
391
290
  const configString = JSON.stringify(config);
392
- console.log('iOS Config as String:', configString);
393
- const result = await EasyMerchantSdk.billingWithConfig(configString);
291
+ const result = await EasyMerchantSdk.makePayment(configString);
394
292
  const refToken = result?.additionalInfo?.threeDSecureStatus?.data?.ref_token;
395
- if (refToken) setReferenceToken(refToken);
396
- setResult(JSON.stringify(result, null, 2));
293
+ console.log('iOS Payment Response:', result);
294
+ if (refToken) console.log('Reference Token:', refToken);
295
+ Alert.alert('Success', 'Payment processed!');
397
296
  }
398
297
  } catch (error) {
399
- const platform = Platform.OS === 'android' ? 'Android' : 'iOS';
400
- console.error(`${platform} Payment Error:`, error);
401
- setResult(`Error: ${error.message ?? JSON.stringify(error)}`);
402
- Alert.alert('Payment Error', error.message ?? 'Unknown error');
298
+ console.error(`${Platform.OS === 'android' ? 'Android' : 'iOS'} Payment Error:`, error);
299
+ Alert.alert('Payment Error', error.message || 'Unknown error');
403
300
  }
404
301
  };
405
302
  ```
406
303
 
407
- #### Helper Function for JSON Parsing
408
-
409
- ```javascript
410
- const safeParseMaybeJSON = (value) => {
411
- if (typeof value === 'string') {
412
- try {
413
- return JSON.parse(value);
414
- } catch (e) {
415
- console.warn('Failed to parse JSON string:', value);
416
- return value;
417
- }
418
- }
419
- return value;
420
- };
421
- ```
304
+ **How It Works**:
305
+ - **Android**: Pass the `config` object to `RNEasymerchantsdk.makePayment(config)`. The response may include `billingInfo` and `additional_info`, which are parsed if they are JSON strings.
306
+ - **iOS**: Stringify the `config` (`JSON.stringify(config)`) and pass to `EasyMerchantSdk.makePayment(configString)`. Call `initializeEnvironment()` first. The response may include a `ref_token` for 3D Secure transactions.
307
+ - **Validation**: Ensures `amount` is positive, at least one payment method is selected, and recurring payments have at least one interval.
308
+ - **Error Handling**: Displays user-friendly alerts for errors.
422
309
 
423
310
  ### 5. Android Event Listeners
424
311
 
425
312
  For Android, set up event listeners to handle payment success, status, and errors:
426
313
 
427
314
  ```javascript
428
- useEffect(() => {
429
- if (Platform.OS !== 'android') return;
430
- if (!RNEasymerchantsdk) {
431
- console.warn('RNEasymerchantsdk native module is not available.');
432
- return;
315
+ const setupAndroidEventListeners = () => {
316
+ if (Platform.OS !== 'android' || !RNEasymerchantsdk) {
317
+ console.warn('RNEasymerchantsdk not available or not Android.');
318
+ return () => {};
433
319
  }
434
320
 
435
321
  const easyMerchantEvents = new NativeEventEmitter(RNEasymerchantsdk);
436
322
  const successSub = easyMerchantEvents.addListener('PaymentSuccess', (data) => {
437
323
  const parsed = JSON.parse(data.response || '{}');
438
- setResult(JSON.stringify({
324
+ const result = {
439
325
  ...parsed,
440
- billingInfo: safeParseMaybeJSON(parsed.billingInfo),
441
- additional_info: safeParseMaybeJSON(parsed.additional_info),
442
- }, null, 2));
326
+ billingInfo: parsed.billingInfo ? JSON.parse(parsed.billingInfo) : parsed.billingInfo,
327
+ additional_info: parsed.additional_info ? JSON.parse(parsed.additional_info) : parsed.additional_info
328
+ };
329
+ console.log('Payment Success:', result);
330
+ Alert.alert('Success', 'Payment processed!');
443
331
  });
332
+
444
333
  const statusSub = easyMerchantEvents.addListener('PaymentStatus', (data) => {
445
334
  const parsed = JSON.parse(data.statusResponse || '{}');
446
- setResult(JSON.stringify(parsed, null, 2));
335
+ console.log('Payment Status:', parsed);
336
+ Alert.alert('Status', JSON.stringify(parsed));
447
337
  });
338
+
448
339
  const statusErrorSub = easyMerchantEvents.addListener('PaymentStatusError', (data) => {
449
340
  let parsedError = data.error;
450
341
  try {
451
- if (typeof parsedError === 'string') {
452
- parsedError = JSON.parse(parsedError);
453
- }
342
+ if (typeof parsedError === 'string') parsedError = JSON.parse(parsedError);
454
343
  } catch (e) {}
455
- setResult(`Status Error: ${JSON.stringify(parsedError, null, 2)}`);
344
+ console.error('Payment Status Error:', parsedError);
345
+ Alert.alert('Status Error', JSON.stringify(parsedError));
456
346
  });
457
347
 
458
348
  return () => {
@@ -460,198 +350,89 @@ useEffect(() => {
460
350
  statusSub.remove();
461
351
  statusErrorSub.remove();
462
352
  };
463
- }, []);
464
- ```
465
-
466
- ## UI Components
467
-
468
- The `App.js` includes custom UI components to enhance user interaction:
469
-
470
- ### Dropdown Component
471
-
472
- A custom dropdown for selecting options like country or recurring start type, with platform-specific handling for Android (Modal) and iOS (View):
473
-
474
- ```javascript
475
- const Dropdown = ({ value, onValueChange, options, placeholder }) => {
476
- const [isOpen, setIsOpen] = useState(false);
477
- const handleClose = () => setIsOpen(false);
478
-
479
- return (
480
- <View style={styles.dropdownContainer}>
481
- <TouchableOpacity
482
- style={styles.dropdownButton}
483
- onPress={() => setIsOpen(!isOpen)}
484
- >
485
- <Text style={styles.dropdownButtonText}>
486
- {value || placeholder}
487
- </Text>
488
- <Text style={styles.dropdownArrow}>
489
- {isOpen ? '▲' : '▼'}
490
- </Text>
491
- </TouchableOpacity>
492
- {Platform.OS === 'android' ? (
493
- <Modal
494
- visible={isOpen}
495
- transparent={true}
496
- animationType="fade"
497
- onRequestClose={handleClose}
498
- >
499
- <TouchableWithoutFeedback onPress={handleClose}>
500
- <View style={{ flex: 1, backgroundColor: 'rgba(0,0,0,0.2)' }}>
501
- <View style={[styles.dropdownOptions, { position: 'absolute', top: '30%', left: 20, right: 20, maxHeight: 300, elevation: 20 }]}>
502
- <ScrollView
503
- style={[styles.dropdownScrollView, { maxHeight: 280 }]}
504
- showsVerticalScrollIndicator={true}
505
- persistentScrollbar={true}
506
- nestedScrollEnabled={true}
507
- keyboardShouldPersistTaps="handled"
508
- >
509
- {options.map((option, index) => (
510
- <TouchableOpacity
511
- key={index}
512
- style={styles.dropdownOption}
513
- onPress={() => {
514
- onValueChange(option);
515
- setIsOpen(false);
516
- }}
517
- >
518
- <Text style={styles.dropdownOptionText}>{option}</Text>
519
- </TouchableOpacity>
520
- ))}
521
- </ScrollView>
522
- </View>
523
- </View>
524
- </TouchableWithoutFeedback>
525
- </Modal>
526
- ) : (
527
- isOpen && (
528
- <View style={styles.dropdownOptions}>
529
- <ScrollView
530
- style={styles.dropdownScrollView}
531
- showsVerticalScrollIndicator={true}
532
- persistentScrollbar={true}
533
- nestedScrollEnabled={true}
534
- keyboardShouldPersistTaps="handled"
535
- >
536
- {options.map((option, index) => (
537
- <TouchableOpacity
538
- key={index}
539
- style={styles.dropdownOption}
540
- onPress={() => {
541
- onValueChange(option);
542
- setIsOpen(false);
543
- }}
544
- >
545
- <Text style={styles.dropdownOptionText}>{option}</Text>
546
- </TouchableOpacity>
547
- ))}
548
- </ScrollView>
549
- </View>
550
- )
551
- )}
552
- </View>
553
- );
554
353
  };
555
354
  ```
556
355
 
557
- ### Expandable Section Component
356
+ **Note**: Call `setupAndroidEventListeners()` during app initialization and clean up listeners when done (returned function).
357
+
358
+ ### 6. Check Payment Status (Android Only)
558
359
 
559
- A collapsible section for organizing configuration settings:
360
+ Check the status of a payment on Android:
560
361
 
561
362
  ```javascript
562
- const ExpandableSection = ({ title, children, isExpanded, onToggle }) => {
563
- return (
564
- <View style={styles.expandableSection}>
565
- <TouchableOpacity
566
- style={styles.sectionHeader}
567
- onPress={onToggle}
568
- activeOpacity={0.7}
569
- >
570
- <Text style={styles.sectionHeaderTitle}>{title}</Text>
571
- <Text style={styles.expandIcon}>
572
- {isExpanded ? '▼' : '▶'}
573
- </Text>
574
- </TouchableOpacity>
575
- {isExpanded && (
576
- <View style={styles.sectionContent}>
577
- {children}
578
- </View>
579
- )}
580
- </View>
581
- );
363
+ const checkPaymentStatus = async () => {
364
+ if (Platform.OS !== 'android') {
365
+ Alert.alert('Error', 'Payment status check is Android-only');
366
+ return;
367
+ }
368
+ try {
369
+ const response = await RNEasymerchantsdk.checkPaymentStatus();
370
+ console.log('Payment Status:', response);
371
+ Alert.alert('Status', JSON.stringify(response));
372
+ } catch (error) {
373
+ console.error('Status Check Error:', error);
374
+ Alert.alert('Status Check Error', error.message || 'Unknown error');
375
+ }
582
376
  };
583
377
  ```
584
378
 
585
- ### Filled Button Component
379
+ ### 7. Payment Reference (iOS Only)
586
380
 
587
- A reusable button component for actions like toggling payment methods or initiating payments:
381
+ Retrieve payment reference details on iOS using a `ref_token`:
588
382
 
589
383
  ```javascript
590
- const FilledButton = ({ title, onPress, disabled = false, style = {}, textStyle = {} }) => {
591
- return (
592
- <TouchableOpacity
593
- style={[
594
- styles.filledButton,
595
- disabled && styles.filledButtonDisabled,
596
- style,
597
- ]}
598
- onPress={onPress}
599
- disabled={disabled}
600
- >
601
- <Text style={[
602
- styles.filledButtonText,
603
- disabled && styles.filledButtonTextDisabled,
604
- textStyle,
605
- ]}>
606
- {title}
607
- </Text>
608
- </TouchableOpacity>
609
- );
384
+ const checkPaymentReference = async (refToken) => {
385
+ if (Platform.OS !== 'ios') {
386
+ Alert.alert('Error', 'Payment reference is iOS-only');
387
+ return;
388
+ }
389
+ try {
390
+ const response = await EasyMerchantSdk.paymentReference(refToken);
391
+ console.log('Payment Reference:', response);
392
+ Alert.alert('Payment Reference', JSON.stringify(response));
393
+ } catch (error) {
394
+ console.error('Payment Reference Error:', error);
395
+ Alert.alert('Payment Reference Error', error.message || 'Unknown error');
396
+ }
610
397
  };
611
398
  ```
612
399
 
613
400
  ## Notes
614
401
 
615
- - **Billing Info**: Set `paymentConfig.fields.billing` or `additional` to an empty array if not needed. Use `paymentConfig.fields.visibility` to control visibility and `required` flags for field validation.
616
402
  - **Payment Methods**:
617
- - Android: `['card', 'ach']` (lowercase).
618
- - iOS: `['Card', 'Bank']` (capitalized, mapped internally).
619
- - Ensure at least one payment method is selected in `paymentConfig.paymentMethods`.
620
- - **Recurring Payments**: Requires at least one interval (`daily`, `weekly`, `monthly`) when enabled.
621
- - **Security**: Store API keys and secret keys securely (e.g., in environment variables or secure storage).
403
+ - Android: Use `['card', 'ach']` (lowercase).
404
+ - iOS: Methods are mapped to `['Card', 'Bank']` internally.
405
+ - Ensure at least one method is included in `config.paymentMethods`.
406
+ - **Recurring Payments**: Set `is_recurring: true` and include at least one interval in `recurringIntervals`.
407
+ - **Security**: Store `apiKey` and `secretKey` securely (e.g., in environment variables) and ensure they match the `environment`.
408
+ - **Optional Fields**: Set `fields.billing` and `fields.additional` to `[]` if not needed. Use `fields.visibility` to control display.
409
+ - **Theming**: Customize `appearanceSettings` for UI consistency. Ensure hex colors are valid.
410
+ - **GrailPay**: Configure `grailPayParams` for bank search UI (used with ACH payments).
622
411
  - **Platform Differences**:
623
- - **Android**: Uses `RNEasymerchantsdk.makePayment(config)` with a JSON config object.
624
- - **iOS**: Uses `EasyMerchantSdk.billingWithConfig(configString)` with a JSON string.
625
- - iOS supports `paymentReference` for status checks; Android uses `checkPaymentStatus`.
626
- - **UI Features**:
627
- - Expandable sections for configuration management.
628
- - Dropdown for selecting countries and recurring start types.
629
- - Modal popup to view and confirm configuration before payment.
630
- - Color preview for theme configuration inputs.
631
- - Secure text entry toggle for secret key input.
412
+ - Android uses a direct object for `makePayment`; iOS requires a JSON string for `makePayment`.
413
+ - iOS requires environment initialization before payment.
414
+ - Android supports event listeners; iOS uses direct method responses.
632
415
 
633
416
  ## Troubleshooting
634
417
 
635
418
  - **iOS Bridge Initialization**:
636
419
  - **Error**: "Failed to retrieve EasyMerchantSdkPlugin instance."
637
- - **Solution**: Verify the `easymerchantsdk` pod is installed correctly. Run `pod install --repo-update` and check `AppDelegate.swift` for correct bridge setup.
420
+ - **Solution**: Verify `easymerchantsdk` pod installation (`pod install --repo-update`) and check `AppDelegate.swift` for correct bridge setup.
638
421
  - **Android Payment Issues**:
639
- - Ensure `paymentConfig.paymentMethods` includes at least one valid method (`['card', 'ach']`).
640
- - Validate API keys and secret keys in `apiKeys[environment]`.
422
+ - Ensure `config.paymentMethods` includes valid methods (`['card', 'ach']`).
423
+ - Validate `apiKey` and `secretKey` in `config`.
641
424
  - Check `gradle.properties` for correct `GITHUB_USERNAME` and `GITHUB_PASSWORD`.
642
- - Log the `config` object to verify payment method inclusion (e.g., `console.log(JSON.stringify(config, null, 2))`).
425
+ - Log `config` for debugging: `console.log(JSON.stringify(config, null, 2))`.
643
426
  - **Pod Installation**:
644
427
  - **Error**: `pod install` fails.
645
428
  - **Solution**: Ensure Ruby 3.2.8 or higher is installed and run `pod install --repo-update`.
646
429
  - **Payment Failures**:
647
- - Check console logs for detailed errors.
648
- - Ensure `amount` is a positive number.
649
- - Verify at least one payment method is selected.
650
- - Confirm API keys match the selected environment (`sandbox` or `staging`).
651
- - **Payment Method Errors**:
652
- - **Issue**: Payment fails due to invalid methods.
653
- - **Solution**: Ensure `paymentConfig.paymentMethods` contains valid entries (`['card', 'ach']` for Android, mapped to `['Card', 'Bank']` for iOS) and that ACH-specific settings (`isAuthenticatedACH`, `isSecureAuthentication`) are correctly configured when `ach` is selected.
654
- - **UI Issues**:
655
- - **Dropdown Not Displaying**: Ensure `zIndex` is set correctly for the dropdown container (e.g., `zIndex: 9998`).
656
- - **Modal Overlap**: Verify `elevation` and `shadow` properties for Android modals to prevent overlap.
657
- - **Keyboard Overlap**: Ensure `KeyboardAvoidingView` is configured with the correct `behavior` and `keyboardVerticalOffset`.
430
+ - Check console logs for errors.
431
+ - Verify `amount` is positive and `paymentMethods` is not empty.
432
+ - Ensure `apiKey` and `secretKey` match the `environment`.
433
+ - For recurring payments, confirm `recurringIntervals` has at least one entry.
434
+ - **ACH Payments**:
435
+ - Ensure `authenticatedACH` and `secureAuthentication` are set appropriately in `config` when using `ach`.
436
+ - **Response Parsing**:
437
+ - Android responses may include JSON strings in `billingInfo` and `additional_info`; parse them as shown.
438
+ - iOS responses may include a `ref_token` for 3D Secure; store it for reference checks.
@@ -29,7 +29,7 @@ RCT_EXPORT_METHOD(configureEnvironment:(NSString *)env
29
29
  }
30
30
 
31
31
  RCT_EXPORT_METHOD(
32
- billingWithConfig:(NSString *)jsonConfig
32
+ makePayment:(NSString *)jsonConfig
33
33
  resolver:(RCTPromiseResolveBlock)resolve
34
34
  rejecter:(RCTPromiseRejectBlock)reject
35
35
  )
@@ -38,7 +38,7 @@ RCT_EXPORT_METHOD(
38
38
  self.sdkPluginInstance = [[EasyMerchantSdkPlugin alloc] init];
39
39
  }
40
40
  @try {
41
- [self.sdkPluginInstance billingWithConfig:jsonConfig
41
+ [self.sdkPluginInstance makePayment:jsonConfig
42
42
  resolver:^(id result) {
43
43
  resolve(result);
44
44
  self.sdkPluginInstance = nil;
@@ -48,7 +48,7 @@ RCT_EXPORT_METHOD(
48
48
  self.sdkPluginInstance = nil;
49
49
  }];
50
50
  } @catch (NSException *exception) {
51
- reject(@"BRIDGE_ERROR", [NSString stringWithFormat:@"Failed to call billingWithConfig: %@", exception.reason], nil);
51
+ reject(@"BRIDGE_ERROR", [NSString stringWithFormat:@"Failed to call makePayment: %@", exception.reason], nil);
52
52
  }
53
53
  }
54
54
 
@@ -58,8 +58,8 @@ public class EasyMerchantSdkPlugin: NSObject, RCTBridgeModule {
58
58
  EnvironmentConfig.configure(apiKey: apiKey, apiSecret: apiSecret)
59
59
  }
60
60
 
61
- // MARK: - billingWithConfig(...) Exposed to RN - Unified Config Method
62
- @objc public func billingWithConfig(
61
+ // MARK: - makePayment(...) Exposed to RN - Unified Config Method
62
+ @objc public func makePayment(
63
63
  _ jsonConfig: String,
64
64
  resolver: @escaping RCTPromiseResolveBlock,
65
65
  rejecter: @escaping RCTPromiseRejectBlock
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'easymerchantsdk'
3
- s.version = '2.1.7'
3
+ s.version = '2.1.8'
4
4
  s.summary = 'A React Native SDK for Easy Merchant.'
5
5
  s.description = <<-DESC
6
6
  A React Native SDK to enable Easy Merchant functionality in mobile applications.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jimrising/easymerchantsdk-react-native",
3
- "version": "2.1.7",
3
+ "version": "2.1.8",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {