@jimrising/easymerchantsdk-react-native 2.1.7 → 2.1.9
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 +184 -405
- package/ios/Classes/EasyMerchantSdk.m +3 -3
- package/ios/Classes/EasyMerchantSdk.swift +2 -2
- package/ios/easymerchantsdk.podspec +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# EasyMerchant SDK React Native Integration
|
|
2
2
|
|
|
3
|
-
This guide provides
|
|
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`
|
|
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.
|
|
22
|
+
"@jimrising/easymerchantsdk-react-native": "^2.1.9"
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
154
|
+
Create a unified `config` object with default values for all required and optional fields.
|
|
164
155
|
|
|
165
156
|
```javascript
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
242
|
-
-
|
|
243
|
-
-
|
|
244
|
-
-
|
|
245
|
-
-
|
|
246
|
-
-
|
|
247
|
-
-
|
|
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'` ).
|
|
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
|
-
|
|
238
|
+
For iOS, configure the environment before processing payments:
|
|
253
239
|
|
|
254
240
|
```javascript
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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**:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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 (!
|
|
340
|
-
|
|
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
|
-
|
|
344
|
-
|
|
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:
|
|
386
|
-
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
396
|
-
|
|
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
|
-
|
|
400
|
-
|
|
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
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
-
|
|
429
|
-
if (Platform.OS !== 'android')
|
|
430
|
-
|
|
431
|
-
|
|
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
|
-
|
|
324
|
+
const result = {
|
|
439
325
|
...parsed,
|
|
440
|
-
billingInfo:
|
|
441
|
-
additional_info:
|
|
442
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
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,87 @@ 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
|
-
|
|
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
|
-
|
|
360
|
+
Check the status of a payment on Android:
|
|
560
361
|
|
|
561
362
|
```javascript
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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
|
-
###
|
|
379
|
+
### 7. Payment Reference (iOS Only)
|
|
586
380
|
|
|
587
|
-
|
|
381
|
+
Retrieve payment reference details on iOS using a `ref_token`:
|
|
588
382
|
|
|
589
383
|
```javascript
|
|
590
|
-
const
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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
|
-
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
- **
|
|
621
|
-
- **
|
|
403
|
+
- Use `['card', 'ach']` (lowercase).
|
|
404
|
+
- **Recurring Payments**: Set `is_recurring: true` and include at least one interval in `recurringIntervals`.
|
|
405
|
+
- **Security**: Store `apiKey` and `secretKey` securely (e.g., in environment variables) and ensure they match the `environment`.
|
|
406
|
+
- **Optional Fields**: Set `fields.billing` and `fields.additional` to `[]` if not needed. Use `fields.visibility` to control display.
|
|
407
|
+
- **Theming**: Customize `appearanceSettings` for UI consistency. Ensure hex colors are valid.
|
|
408
|
+
- **GrailPay**: Configure `grailPayParams` for bank search UI (used with ACH payments).
|
|
622
409
|
- **Platform Differences**:
|
|
623
|
-
-
|
|
624
|
-
-
|
|
625
|
-
-
|
|
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.
|
|
410
|
+
- Android uses a direct object for `makePayment`; iOS requires a JSON string for `makePayment`.
|
|
411
|
+
- iOS requires environment initialization before payment.
|
|
412
|
+
- Android supports event listeners; iOS uses direct method responses.
|
|
632
413
|
|
|
633
414
|
## Troubleshooting
|
|
634
415
|
|
|
635
416
|
- **iOS Bridge Initialization**:
|
|
636
417
|
- **Error**: "Failed to retrieve EasyMerchantSdkPlugin instance."
|
|
637
|
-
- **Solution**: Verify
|
|
418
|
+
- **Solution**: Verify `easymerchantsdk` pod installation (`pod install --repo-update`) and check `AppDelegate.swift` for correct bridge setup.
|
|
638
419
|
- **Android Payment Issues**:
|
|
639
|
-
- Ensure `
|
|
640
|
-
- Validate
|
|
420
|
+
- Ensure `config.paymentMethods` includes valid methods (`['card', 'ach']`).
|
|
421
|
+
- Validate `apiKey` and `secretKey` in `config`.
|
|
641
422
|
- Check `gradle.properties` for correct `GITHUB_USERNAME` and `GITHUB_PASSWORD`.
|
|
642
|
-
- Log
|
|
423
|
+
- Log `config` for debugging: `console.log(JSON.stringify(config, null, 2))`.
|
|
643
424
|
- **Pod Installation**:
|
|
644
425
|
- **Error**: `pod install` fails.
|
|
645
426
|
- **Solution**: Ensure Ruby 3.2.8 or higher is installed and run `pod install --repo-update`.
|
|
646
427
|
- **Payment Failures**:
|
|
647
|
-
- Check console logs for
|
|
648
|
-
-
|
|
649
|
-
-
|
|
650
|
-
-
|
|
651
|
-
- **
|
|
652
|
-
-
|
|
653
|
-
|
|
654
|
-
-
|
|
655
|
-
-
|
|
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`.
|
|
428
|
+
- Check console logs for errors.
|
|
429
|
+
- Verify `amount` is positive and `paymentMethods` is not empty.
|
|
430
|
+
- Ensure `apiKey` and `secretKey` match the `environment`.
|
|
431
|
+
- For recurring payments, confirm `recurringIntervals` has at least one entry.
|
|
432
|
+
- **ACH Payments**:
|
|
433
|
+
- Ensure `authenticatedACH` and `secureAuthentication` are set appropriately in `config` when using `ach`.
|
|
434
|
+
- **Response Parsing**:
|
|
435
|
+
- Android responses may include JSON strings in `billingInfo` and `additional_info`; parse them as shown.
|
|
436
|
+
- 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
|
-
|
|
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
|
|
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
|
|
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: -
|
|
62
|
-
@objc public func
|
|
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
|