@jimrising/easymerchantsdk-react-native 1.4.3 → 1.4.4

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
@@ -7,7 +7,7 @@ To add the path of sdk in your project. Open your `package.json` file and inside
7
7
 
8
8
  ```json
9
9
  "dependencies": {
10
- "@jimrising/easymerchantsdk-react-native": "^1.4.3"
10
+ "@jimrising/easymerchantsdk-react-native": "^1.4.4"
11
11
  },
12
12
  ```
13
13
 
@@ -165,7 +165,7 @@ const App = () => {
165
165
 
166
166
  const handleBilling = async () => {
167
167
  const billingInfo = {
168
- visibility: { billing: true, additional: true },
168
+ visibility: { billing: false, additional: false },
169
169
  billing: {
170
170
  address: 'Mohali, Punjab',
171
171
  country: 'India',
@@ -211,6 +211,16 @@ const App = () => {
211
211
  fontFamily: '"Inter", sans-serif',
212
212
  };
213
213
 
214
+ const authConfig = {
215
+ accessToken: '251|uTijpDGfrS88UR2V1cZNMQ8S4hUJA0sVzsnsoUZF',
216
+ vendorId: '251',
217
+ role: 'business',
218
+ timeout: 10,
219
+ isSandbox: true,
220
+ brandingName: 'Lyfecycle Payments',
221
+ finderSubtitle: 'Search for your bank',
222
+ searchPlaceholder: 'Enter bank name',
223
+ };
214
224
  try {
215
225
  if (Platform.OS === 'android') {
216
226
  const result = await RNEasymerchantsdk.billing('72', null);
@@ -225,10 +235,11 @@ const App = () => {
225
235
  false, // tokenOnly
226
236
  true, // saveCard
227
237
  true, // saveAccount
228
- false, // authenticatedACH
229
- null, // grailPayParams
238
+ true, // authenticatedACH
239
+ authConfig, // grailPayParams
230
240
  'Submit',
231
241
  false, // isRecurring
242
+ 2, // if isRecurring == true then numOfCycle required
232
243
  ['weekly', 'monthly'],
233
244
  'custom',
234
245
  '07/07/2025',
@@ -2,8 +2,8 @@
2
2
  #import <React/RCTLog.h>
3
3
  #import <React/RCTBridgeModule.h>
4
4
 
5
- #import <easymerchantsdk-Swift.h>
6
- //#import <easymerchantsdk/easymerchantsdk-Swift.h>
5
+ //#import <easymerchantsdk-Swift.h>
6
+ #import <easymerchantsdk/easymerchantsdk-Swift.h>
7
7
 
8
8
  @interface EasyMerchantSdk ()
9
9
  @property (nonatomic, strong) EasyMerchantSdkPlugin *sdkPluginInstance;
@@ -40,6 +40,7 @@ RCT_EXPORT_METHOD(
40
40
  grailPayParams:(NSDictionary *)grailPayParams
41
41
  submitButtonText:(NSString *)submitButtonText
42
42
  isRecurring:(BOOL)isRecurring
43
+ numOfCycle:(int)numOfCycle
43
44
  recurringIntervals:(NSArray *)recurringIntervals
44
45
  recurringStartDateType:(NSString *)recurringStartDateType
45
46
  recurringStartDate:(NSString *)recurringStartDate
@@ -67,6 +68,7 @@ RCT_EXPORT_METHOD(
67
68
  grailPayParams:grailPayParams
68
69
  submitButtonText:submitButtonText
69
70
  isRecurring:isRecurring
71
+ numOfCycle:numOfCycle
70
72
  recurringIntervals:recurringIntervals
71
73
  recurringStartDateType:recurringStartDateType
72
74
  recurringStartDate:recurringStartDate
@@ -69,6 +69,7 @@ public class EasyMerchantSdkPlugin: NSObject, RCTBridgeModule {
69
69
  grailPayParams: [String: Any]?,
70
70
  submitButtonText: String?,
71
71
  isRecurring: Bool,
72
+ numOfCycle: Int,
72
73
  recurringIntervals: [String]?,
73
74
  recurringStartDateType: String?,
74
75
  recurringStartDate: String?,
@@ -215,6 +216,7 @@ public class EasyMerchantSdkPlugin: NSObject, RCTBridgeModule {
215
216
  authenticatedACH: authenticatedACH,
216
217
  grailPayParams: grailParams,
217
218
  is_recurring: isRecurring,
219
+ numOfCycle: numOfCycle,
218
220
  recurringIntervals: intervals,
219
221
  recurringStartDateType: startType,
220
222
  recurringStartDate: recurringStartDate,
@@ -106,14 +106,14 @@ public struct FieldItem: Codable {
106
106
  public let name: String
107
107
  public let required: Bool
108
108
  public var value: String
109
-
109
+
110
110
  // Overloaded initializer for BillingFieldName
111
111
  public init(name: BillingFieldName, required: Bool, value: String) {
112
112
  self.name = name.rawValue
113
113
  self.required = required
114
114
  self.value = value
115
115
  }
116
-
116
+
117
117
  // Overloaded initializer for AdditionalFieldName
118
118
  public init(name: AdditionalFieldName, required: Bool, value: String) {
119
119
  self.name = name.rawValue
@@ -125,7 +125,7 @@ public struct FieldItem: Codable {
125
125
  public struct FieldsVisibility: Codable {
126
126
  public let billing: Bool
127
127
  public let additional: Bool
128
-
128
+
129
129
  public init(billing: Bool, additional: Bool) {
130
130
  self.billing = billing
131
131
  self.additional = additional
@@ -136,7 +136,7 @@ public struct FieldSection: Codable {
136
136
  public let visibility: FieldsVisibility
137
137
  public var billing: [FieldItem]
138
138
  public var additional: [FieldItem]
139
-
139
+
140
140
  public init(visibility: FieldsVisibility, billing: [FieldItem], additional: [FieldItem]) {
141
141
  self.visibility = visibility
142
142
  self.billing = billing
@@ -158,6 +158,7 @@ public final class Request: NSObject {
158
158
  public let authenticatedACH: Bool?
159
159
  public let grailPayParams: GrailPayRequest?
160
160
  public var is_recurring: Bool?
161
+ public var numOfCycle: Int?
161
162
  public var recurringIntervals: [RecurringIntervals]?
162
163
  public var recurringStartDateType: RecurringStartDateType?
163
164
  public var recurringStartDate: String?
@@ -167,7 +168,7 @@ public final class Request: NSObject {
167
168
  public let showSubmitButton: Bool?
168
169
  public let referenceID: Bool?
169
170
  public let referenceToken: String?
170
-
171
+
171
172
  public init(
172
173
  amount: Double? = nil,
173
174
  currency: String? = nil,
@@ -181,6 +182,7 @@ public final class Request: NSObject {
181
182
  authenticatedACH: Bool = false,
182
183
  grailPayParams: GrailPayRequest? = nil,
183
184
  is_recurring: Bool = false,
185
+ numOfCycle: Int? = nil,
184
186
  recurringIntervals: [RecurringIntervals]? = nil,
185
187
  recurringStartDateType: RecurringStartDateType? = nil,
186
188
  recurringStartDate: String? = nil,
@@ -191,7 +193,6 @@ public final class Request: NSObject {
191
193
  referenceID: Bool = false,
192
194
  referenceToken: String? = nil
193
195
  ) {
194
-
195
196
  // Validate if amount is provided, must be ≥ 0.50
196
197
  if let amt = amount, amt < 0.50 {
197
198
  DispatchQueue.main.async {
@@ -223,6 +224,7 @@ public final class Request: NSObject {
223
224
  self.authenticatedACH = authenticatedACH
224
225
  self.grailPayParams = authenticatedACH ? grailPayParams : nil
225
226
  self.is_recurring = is_recurring
227
+ self.numOfCycle = numOfCycle
226
228
  self.recurringIntervals = recurringIntervals
227
229
  self.recurringStartDateType = recurringStartDateType
228
230
  self.recurringStartDate = recurringStartDate
@@ -236,11 +238,13 @@ public final class Request: NSObject {
236
238
  // Conditionally assign recurring fields only if is_recurring == true
237
239
  if is_recurring {
238
240
  self.is_recurring = true
241
+ self.numOfCycle = numOfCycle
239
242
  self.recurringIntervals = recurringIntervals
240
243
  self.recurringStartDateType = recurringStartDateType
241
244
  self.recurringStartDate = recurringStartDate
242
245
  } else {
243
246
  self.is_recurring = false
247
+ self.numOfCycle = nil
244
248
  self.recurringIntervals = nil
245
249
  self.recurringStartDateType = nil
246
250
  self.recurringStartDate = nil
@@ -304,7 +308,7 @@ public final class Request: NSObject {
304
308
  //MARK: - Payment Intent Api
305
309
  func paymentIntentApi(completion: @escaping (Bool) -> Void) {
306
310
  guard let serviceURL = URL(string: EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.paymentIntent.path()) else {
307
- print("Invalid URL")
311
+ showErrorAndDismiss(message: "Invalid payment URL.")
308
312
  completion(false)
309
313
  return
310
314
  }
@@ -315,31 +319,14 @@ public final class Request: NSObject {
315
319
  request.addValue(EnvironmentConfig.apiKey ?? "", forHTTPHeaderField: "X-Api-Key")
316
320
  request.addValue(EnvironmentConfig.apiSecret ?? "", forHTTPHeaderField: "X-Api-Secret")
317
321
 
318
- // Check if recurringStartDate is not in the past
322
+ // Recurring date validation
319
323
  if let startDateString = recurringStartDate,
320
324
  let startDate = DateFormatter.recurringDateFormatter.date(from: startDateString) {
321
-
322
325
  let today = Calendar.current.startOfDay(for: Date())
323
326
  let startDay = Calendar.current.startOfDay(for: startDate)
324
327
 
325
328
  if startDay < today {
326
- DispatchQueue.main.async {
327
- if let topVC = UIApplication.topViewController() {
328
- let alert = UIAlertController(title: "Invalid Date",
329
- message: "The recurring start date cannot be in the past. Please select today or a future date.",
330
- preferredStyle: .alert)
331
- alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
332
- if let easyPayVC = UIApplication.findEasyPayViewController(from: topVC) {
333
- easyPayVC.dismiss(animated: true)
334
- } else {
335
- // fallback: dismiss topVC itself if it's presented modally
336
- topVC.dismiss(animated: true)
337
- }
338
- }))
339
- topVC.present(alert, animated: true)
340
- }
341
- }
342
-
329
+ showErrorAndDismiss(message: "The recurring start date cannot be in the past. Please select today or a future date.")
343
330
  completion(false)
344
331
  return
345
332
  }
@@ -347,7 +334,8 @@ public final class Request: NSObject {
347
334
 
348
335
  let params: [String: Any] = [
349
336
  "amount": amount ?? 0,
350
- "allowed_cycles": String(recurringIntervals?.count ?? 0),
337
+ // "allowed_cycles": String(recurringIntervals?.count ?? 0),
338
+ "allowed_cycles": numOfCycle ?? 0,
351
339
  "intervals": recurringIntervals?.map { $0.rawValue } ?? [],
352
340
  "is_recurring": self.is_recurring ?? false,
353
341
  "recurring_start_date": recurringStartDate ?? "",
@@ -355,21 +343,16 @@ public final class Request: NSObject {
355
343
  ]
356
344
 
357
345
  do {
358
-
359
346
  request.httpBody = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
360
347
  } catch {
361
- print("Error creating JSON data: \(error)")
348
+ showErrorAndDismiss(message: "Failed to encode payment data.")
362
349
  completion(false)
363
350
  return
364
351
  }
365
352
 
366
353
  let task = URLSession.shared.dataTask(with: request) { data, response, error in
367
- DispatchQueue.main.async {
368
- // Stop loader when response is received
369
- }
370
-
371
354
  guard let httpResponse = response as? HTTPURLResponse, error == nil else {
372
- print("Error: \(error?.localizedDescription ?? "Unknown error")")
355
+ self.showErrorAndDismiss(message: error?.localizedDescription ?? "An unknown error occurred.")
373
356
  completion(false)
374
357
  return
375
358
  }
@@ -378,51 +361,60 @@ public final class Request: NSObject {
378
361
  if let data = data {
379
362
  do {
380
363
  if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
381
- print("Response Data: \(responseObject)")
382
-
383
- // Save tokens
384
364
  if let clientToken = responseObject["client_token"] as? String {
385
365
  UserStoreSingleton.shared.clientToken = clientToken
386
- print("Client Token successfully saved: \(UserStoreSingleton.shared.clientToken ?? "None")")
387
366
  }
388
-
389
367
  if let paymentIntent = responseObject["payment_intent"] as? String {
390
368
  UserStoreSingleton.shared.paymentIntent = paymentIntent
391
369
  }
392
-
393
370
  self.hostedCheckoutsApi { success in
394
371
  completion(success)
395
372
  }
373
+ return
396
374
  } else {
397
- print("Invalid JSON format")
398
- completion(false)
375
+ self.showErrorAndDismiss(message: "Invalid response format.")
399
376
  }
400
377
  } catch {
401
- print("Error parsing JSON: \(error)")
402
- completion(false)
378
+ self.showErrorAndDismiss(message: "Failed to parse response.")
403
379
  }
404
380
  } else {
405
- print("No data received")
406
- completion(false)
381
+ self.showErrorAndDismiss(message: "No response data received.")
407
382
  }
408
383
  } else {
384
+ var message = "Payment request failed with status code: \(httpResponse.statusCode)"
409
385
  if let data = data,
410
386
  let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
411
- let message = responseObj["message"] as? String {
412
- print(message)
413
- } else {
414
- print("HTTP Status Code: \(httpResponse.statusCode)")
387
+ let msg = responseObj["message"] as? String {
388
+ message = msg
415
389
  }
416
-
417
- completion(false)
390
+ self.showErrorAndDismiss(message: message)
418
391
  }
392
+ completion(false)
419
393
  }
394
+
420
395
  task.resume()
421
396
  }
422
397
 
398
+ private func showErrorAndDismiss(message: String) {
399
+ DispatchQueue.main.async {
400
+ if let topVC = UIApplication.topViewController() {
401
+ let alert = UIAlertController(title: "Payment Error",
402
+ message: message,
403
+ preferredStyle: .alert)
404
+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
405
+ if let easyPayVC = UIApplication.findEasyPayViewController(from: topVC) {
406
+ easyPayVC.dismiss(animated: true)
407
+ } else {
408
+ topVC.dismiss(animated: true)
409
+ }
410
+ }))
411
+ topVC.present(alert, animated: true)
412
+ }
413
+ }
414
+ }
415
+
423
416
  // MARK: - Hosted Checkout API
424
417
  func hostedCheckoutsApi(completion: @escaping (Bool) -> Void) {
425
-
426
418
  // Build the URL using EnvironmentConfig
427
419
  guard let baseURL = URL(string: EnvironmentConfig.baseURL) else {
428
420
  print("Invalid base URL")
@@ -645,4 +637,3 @@ extension UIApplication {
645
637
  }
646
638
  }
647
639
 
648
-
@@ -47,3 +47,4 @@ public final class Result: NSObject {
47
47
  super.init()
48
48
  }
49
49
  }
50
+
@@ -319,44 +319,51 @@ class AdditionalInfoVC: BaseVC {
319
319
 
320
320
  // MARK: - Flow Based on Conditions
321
321
  if isSavedForFuture {
322
- if let emailVerificationVC = self.storyboard?.instantiateViewController(withIdentifier: "EmailVerificationVC") as? EmailVerificationVC {
323
- // Pass data to EmailVerificationVC
324
- emailVerificationVC.billingInfoData = billingInfoData
325
- emailVerificationVC.selectedPaymentMethod = selectedPaymentMethod
326
- emailVerificationVC.easyPayDelegate = easyPayDelegate
327
- emailVerificationVC.request = request
328
- emailVerificationVC.chosenPlan = chosenPlan
329
- emailVerificationVC.startDate = startDate
330
- emailVerificationVC.userEmail = userEmail
331
- emailVerificationVC.billingInfo = fieldSection?.billing
332
- emailVerificationVC.additionalInfo = fieldSection?.additional
333
- emailVerificationVC.visibility = fieldSection?.visibility
334
- emailVerificationVC.amount = amount
335
-
336
- // Extra fields per payment method
337
- if selectedPaymentMethod == "Card" {
338
- emailVerificationVC.cardNumber = cardNumber
339
- emailVerificationVC.expiryDate = expiryDate
340
- emailVerificationVC.cvv = cvv
341
- emailVerificationVC.nameOnCard = nameOnCard
342
- } else if selectedPaymentMethod == "Bank" {
343
- emailVerificationVC.accountName = accountName
344
- emailVerificationVC.routingNumber = routingNumber
345
- emailVerificationVC.accountType = accountType
346
- emailVerificationVC.accountNumber = accountNumber
347
- } else if selectedPaymentMethod == "GrailPay" {
348
- emailVerificationVC.grailPayAccountID = grailPayAccountID
349
- emailVerificationVC.selectedGrailPayAccountType = selectedGrailPayAccountType
350
- emailVerificationVC.selectedGrailPayAccountName = selectedGrailPayAccountName
351
- emailVerificationVC.isSavedForFuture = true
352
- } else if selectedPaymentMethod == "NewGrailPayAccount" {
353
- emailVerificationVC.grailPayAccountID = grailPayAccountID
354
- emailVerificationVC.selectedGrailPayAccountType = selectedGrailPayAccountType
355
- emailVerificationVC.selectedGrailPayAccountName = selectedGrailPayAccountName
356
- emailVerificationVC.isSavedForFuture = true
322
+ if selectedPaymentMethod == "NewGrailPayAccount" {
323
+ // Only call API, do NOT navigate
324
+ grailPayAccountChargeApi(customerId: UserStoreSingleton.shared.customerId)
325
+ } else {
326
+ // Navigate to EmailVerificationVC for all other payment methods
327
+ if let emailVerificationVC = self.storyboard?.instantiateViewController(withIdentifier: "EmailVerificationVC") as? EmailVerificationVC {
328
+
329
+ emailVerificationVC.billingInfoData = billingInfoData
330
+ emailVerificationVC.selectedPaymentMethod = selectedPaymentMethod
331
+ emailVerificationVC.easyPayDelegate = easyPayDelegate
332
+ emailVerificationVC.request = request
333
+ emailVerificationVC.chosenPlan = chosenPlan
334
+ emailVerificationVC.startDate = startDate
335
+ emailVerificationVC.userEmail = userEmail
336
+ emailVerificationVC.billingInfo = fieldSection?.billing
337
+ emailVerificationVC.additionalInfo = fieldSection?.additional
338
+ emailVerificationVC.visibility = fieldSection?.visibility
339
+ emailVerificationVC.amount = amount
340
+
341
+ // Payment method-specific data
342
+ switch selectedPaymentMethod {
343
+ case "Card":
344
+ emailVerificationVC.cardNumber = cardNumber
345
+ emailVerificationVC.expiryDate = expiryDate
346
+ emailVerificationVC.cvv = cvv
347
+ emailVerificationVC.nameOnCard = nameOnCard
348
+
349
+ case "Bank":
350
+ emailVerificationVC.accountName = accountName
351
+ emailVerificationVC.routingNumber = routingNumber
352
+ emailVerificationVC.accountType = accountType
353
+ emailVerificationVC.accountNumber = accountNumber
354
+
355
+ case "GrailPay":
356
+ emailVerificationVC.grailPayAccountID = grailPayAccountID
357
+ emailVerificationVC.selectedGrailPayAccountType = selectedGrailPayAccountType
358
+ emailVerificationVC.selectedGrailPayAccountName = selectedGrailPayAccountName
359
+ emailVerificationVC.isSavedForFuture = true
360
+
361
+ default:
362
+ break
363
+ }
364
+
365
+ navigationController?.pushViewController(emailVerificationVC, animated: true)
357
366
  }
358
-
359
- navigationController?.pushViewController(emailVerificationVC, animated: true)
360
367
  }
361
368
  }
362
369
  else {
@@ -2134,6 +2141,224 @@ class AdditionalInfoVC: BaseVC {
2134
2141
  task.resume()
2135
2142
  }
2136
2143
 
2144
+ //MARK: - GrailPay Account Charge Api if user saved account
2145
+ func grailPayAccountChargeApi(customerId: String?) {
2146
+ showLoadingIndicator()
2147
+
2148
+ let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
2149
+
2150
+ guard let serviceURL = URL(string: fullURL) else {
2151
+ print("Invalid URL")
2152
+ hideLoadingIndicator()
2153
+ return
2154
+ }
2155
+
2156
+ var uRLRequest = URLRequest(url: serviceURL)
2157
+ uRLRequest.httpMethod = "POST"
2158
+ uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
2159
+
2160
+ let token = UserStoreSingleton.shared.clientToken
2161
+ print("Setting clientToken header: \(token ?? "None")")
2162
+ uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2163
+
2164
+ if let apiKey = EnvironmentConfig.apiKey {
2165
+ uRLRequest.addValue(apiKey, forHTTPHeaderField: "X-Api-Key")
2166
+ }
2167
+ if let apiSecret = EnvironmentConfig.apiSecret {
2168
+ uRLRequest.addValue(apiSecret, forHTTPHeaderField: "X-Api-Secret")
2169
+ }
2170
+
2171
+ let emailPrefix = userEmail?.components(separatedBy: "@").first ?? ""
2172
+
2173
+ var params: [String: Any] = [
2174
+ "account_id": self.grailPayAccountID ?? "",
2175
+ "account_type": self.selectedGrailPayAccountType ?? "",
2176
+ "name": self.selectedGrailPayAccountName ?? "",
2177
+ "save_account": 1,
2178
+ "is_default": 1,
2179
+ "customer_id": customerId ?? "",
2180
+ "email": userEmail ?? "",
2181
+ "create_customer": "1",
2182
+ ]
2183
+
2184
+ if let customerId = customerId {
2185
+ params["customer"] = customerId
2186
+ } else {
2187
+ params["username"] = emailPrefix
2188
+ }
2189
+
2190
+ // // Billing Info
2191
+ // if let visibility = visibility, visibility.billing == true,
2192
+ // let billing = billingInfo, !billing.isEmpty {
2193
+ // var billingDict: [String: Any] = [:]
2194
+ // billing.forEach { billingDict[$0.name] = $0.value }
2195
+ //
2196
+ // params["address"] = billingDict["address"] as? String ?? ""
2197
+ // params["country"] = billingDict["country"] as? String ?? ""
2198
+ // params["state"] = billingDict["state"] as? String ?? ""
2199
+ // params["city"] = billingDict["city"] as? String ?? ""
2200
+ // params["zip"] = billingDict["postal_code"] as? String ?? ""
2201
+ // }
2202
+
2203
+ // Always include Billing Info if available
2204
+ if let billing = billingInfo, !billing.isEmpty {
2205
+ var billingDict: [String: Any] = [:]
2206
+ billing.forEach { billingDict[$0.name] = $0.value }
2207
+
2208
+ params["address"] = billingDict["address"] as? String ?? ""
2209
+ params["country"] = billingDict["country"] as? String ?? ""
2210
+ params["state"] = billingDict["state"] as? String ?? ""
2211
+ params["city"] = billingDict["city"] as? String ?? ""
2212
+ params["zip"] = billingDict["postal_code"] as? String ?? ""
2213
+ }
2214
+
2215
+ // // Additional Info or default description
2216
+ // var descriptionValue: String = "Hosted payment checkout" // default
2217
+ // if let visibility = visibility, visibility.additional == true,
2218
+ // let additional = additionalInfo, !additional.isEmpty {
2219
+ //
2220
+ // var additionalDict: [String: Any] = [:]
2221
+ // additional.forEach { additionalDict[$0.name] = $0.value }
2222
+ //
2223
+ // if let desc = additionalDict["description"] as? String, !desc.isEmpty {
2224
+ // descriptionValue = desc
2225
+ // }
2226
+ //
2227
+ // if let phone = additionalDict["phone_number"] as? String, !phone.isEmpty {
2228
+ // params["phone_number"] = phone
2229
+ // }
2230
+ // }
2231
+ // params["description"] = descriptionValue
2232
+
2233
+ // Always include Additional Info if available
2234
+ var descriptionValue: String = "Hosted payment checkout"
2235
+ if let additional = additionalInfo, !additional.isEmpty {
2236
+ var additionalDict: [String: Any] = [:]
2237
+ additional.forEach { additionalDict[$0.name] = $0.value }
2238
+
2239
+ if let desc = additionalDict["description"] as? String, !desc.isEmpty {
2240
+ descriptionValue = desc
2241
+ }
2242
+
2243
+ if let phone = additionalDict["phone_number"] as? String, !phone.isEmpty {
2244
+ params["phone_number"] = phone
2245
+ }
2246
+ }
2247
+ params["description"] = descriptionValue
2248
+
2249
+ // Add these if recurring is enabled
2250
+ if let req = request, req.is_recurring == true {
2251
+ if let recurringType = req.recurringStartDateType, recurringType == .custom {
2252
+ // Only send start_date if type is .custom and field is not empty
2253
+ if let startDateText = startDate, !startDateText.isEmpty {
2254
+ let inputFormatter = DateFormatter()
2255
+ inputFormatter.dateFormat = "dd/MM/yyyy"
2256
+
2257
+ let outputFormatter = DateFormatter()
2258
+ outputFormatter.dateFormat = "MM/dd/yyyy"
2259
+
2260
+ if let date = inputFormatter.date(from: startDateText) {
2261
+ let apiFormattedDate = outputFormatter.string(from: date)
2262
+ params["start_date"] = apiFormattedDate
2263
+ } else {
2264
+ print("Invalid date format in startDateText")
2265
+ }
2266
+ }
2267
+ }
2268
+
2269
+ params["interval"] = chosenPlan?.lowercased()
2270
+ }
2271
+
2272
+ print(params)
2273
+
2274
+ do {
2275
+ let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2276
+ uRLRequest.httpBody = jsonData
2277
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
2278
+ print("JSON Payload: \(jsonString)")
2279
+ }
2280
+ } catch let error {
2281
+ print("Error creating JSON data: \(error)")
2282
+ hideLoadingIndicator()
2283
+ return
2284
+ }
2285
+
2286
+ let session = URLSession.shared
2287
+ let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
2288
+
2289
+ DispatchQueue.main.async {
2290
+ self.hideLoadingIndicator() // Stop loader when response is received
2291
+ }
2292
+
2293
+ if let error = error {
2294
+ self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
2295
+ return
2296
+ }
2297
+
2298
+ guard let httpResponse = serviceResponse as? HTTPURLResponse else {
2299
+ self.presentPaymentErrorVC(errorMessage: "Invalid response")
2300
+ return
2301
+ }
2302
+
2303
+ if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
2304
+ if let data = serviceData {
2305
+ do {
2306
+ if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
2307
+ print("Response Data: \(responseObject)")
2308
+
2309
+ // Check if status is 0 and handle the error
2310
+ if let status = responseObject["status"] as? Int, status == 0 {
2311
+ let errorMessage = responseObject["message"] as? String ?? "Unknown error"
2312
+ self.presentPaymentErrorVC(errorMessage: errorMessage)
2313
+ } else {
2314
+ DispatchQueue.main.async {
2315
+ if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
2316
+ paymentDoneVC.chargeData = responseObject
2317
+ paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
2318
+ paymentDoneVC.easyPayDelegate = self.easyPayDelegate
2319
+ // Pass billing and additional info
2320
+ // Conditionally pass raw FieldItem array
2321
+ paymentDoneVC.visibility = self.visibility
2322
+
2323
+ // if self.visibility?.billing == true {
2324
+ paymentDoneVC.billingInfoData = self.billingInfo
2325
+ var billingDict: [String: Any] = [:]
2326
+ self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
2327
+ paymentDoneVC.billingInfo = billingDict
2328
+ // }
2329
+
2330
+ // if self.visibility?.additional == true {
2331
+ paymentDoneVC.additionalInfoData = self.additionalInfo
2332
+ var additionalDict: [String: Any] = [:]
2333
+ self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
2334
+ paymentDoneVC.additionalInfo = additionalDict
2335
+ // }
2336
+ self.navigationController?.pushViewController(paymentDoneVC, animated: true)
2337
+ }
2338
+ }
2339
+ }
2340
+ } else {
2341
+ self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
2342
+ }
2343
+ } catch let jsonError {
2344
+ self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
2345
+ }
2346
+ } else {
2347
+ self.presentPaymentErrorVC(errorMessage: "No data received")
2348
+ }
2349
+ } else {
2350
+ if let data = serviceData,
2351
+ let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
2352
+ let message = responseObj["message"] as? String {
2353
+ self.presentPaymentErrorVC(errorMessage: message)
2354
+ } else {
2355
+ self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
2356
+ }
2357
+ }
2358
+ }
2359
+ task.resume()
2360
+ }
2361
+
2137
2362
  }
2138
2363
 
2139
2364
  extension AdditionalInfoVC: UITextFieldDelegate {