@jimrising/easymerchantsdk-react-native 2.4.2 → 2.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.
Files changed (71) hide show
  1. package/README.md +42 -113
  2. package/android/build.gradle +1 -1
  3. package/ios/Classes/EasyMerchantSdk.m +2 -2
  4. package/ios/easymerchantsdk.podspec +1 -1
  5. package/package.json +1 -1
  6. package/.idea/caches/deviceStreaming.xml +0 -340
  7. package/.idea/em-MobileCheckoutSDK-ReactNative.iml +0 -9
  8. package/.idea/misc.xml +0 -5
  9. package/.idea/modules.xml +0 -8
  10. package/.idea/vcs.xml +0 -6
  11. package/android/build/generated/source/buildConfig/debug/com/reactlibrary/BuildConfig.java +0 -10
  12. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +0 -7
  13. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +0 -18
  14. package/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +0 -6
  15. package/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +0 -1
  16. package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
  17. package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
  18. package/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +0 -0
  19. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +0 -1
  20. package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +0 -2
  21. package/android/build/intermediates/incremental/mergeDebugAssets/merger.xml +0 -2
  22. package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +0 -2
  23. package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +0 -2
  24. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/BuildConfig.class +0 -0
  25. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$1$1.class +0 -0
  26. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
  27. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
  28. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$3.class +0 -0
  29. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
  30. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkPackage.class +0 -0
  31. package/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +0 -2
  32. package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +0 -7
  33. package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +0 -7
  34. package/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +0 -1
  35. package/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +0 -1
  36. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/BuildConfig.class +0 -0
  37. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$1$1.class +0 -0
  38. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
  39. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
  40. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$3.class +0 -0
  41. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
  42. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkPackage.class +0 -0
  43. package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
  44. package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +0 -1
  45. package/android/build/outputs/logs/manifest-merger-debug-report.txt +0 -16
  46. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  47. package/ios/Pods/UserDefaults/UserStoreSingleton.swift +0 -425
  48. package/ios/Pods/ViewControllers/AdditionalInfoVC.swift +0 -2996
  49. package/ios/Pods/ViewControllers/BaseVC.swift +0 -142
  50. package/ios/Pods/ViewControllers/BillingInfoVC/BillingInfoVC.swift +0 -3807
  51. package/ios/Pods/ViewControllers/BillingInfoVC/Cells/CityListTVC.swift +0 -46
  52. package/ios/Pods/ViewControllers/BillingInfoVC/Cells/CountryListTVC.swift +0 -47
  53. package/ios/Pods/ViewControllers/BillingInfoVC/Cells/StateListTVC.swift +0 -46
  54. package/ios/Pods/ViewControllers/Clean Runner_2025-07-23T14-58-05.txt +0 -13
  55. package/ios/Pods/ViewControllers/CountryListVC.swift +0 -435
  56. package/ios/Pods/ViewControllers/EmailVerificationVC.swift +0 -300
  57. package/ios/Pods/ViewControllers/GrailPayVC.swift +0 -492
  58. package/ios/Pods/ViewControllers/OTPVerificationVC.swift +0 -2278
  59. package/ios/Pods/ViewControllers/PaymentDoneVC.swift +0 -287
  60. package/ios/Pods/ViewControllers/PaymentErrorVC.swift +0 -85
  61. package/ios/Pods/ViewControllers/PaymentInformation/AccountTypeTVC.swift +0 -41
  62. package/ios/Pods/ViewControllers/PaymentInformation/PaymentInfoVC.swift +0 -13115
  63. package/ios/Pods/ViewControllers/PaymentInformation/PaymentInformationCVC.swift +0 -35
  64. package/ios/Pods/ViewControllers/PaymentInformation/RecurringTVC.swift +0 -40
  65. package/ios/Pods/ViewControllers/PaymentInformation/SavedAccountsTVC/SavedAccountTVC.swift +0 -80
  66. package/ios/Pods/ViewControllers/PaymentInformation/SavedAccountsTVC/SavedAccountTVC.xib +0 -163
  67. package/ios/Pods/ViewControllers/PaymentInformation/SavedCardsTVC/SavedCardsTVC.swift +0 -81
  68. package/ios/Pods/ViewControllers/PaymentInformation/SavedCardsTVC/SavedCardsTVC.xib +0 -188
  69. package/ios/Pods/ViewControllers/PaymentStatusWebViewVC.swift +0 -167
  70. package/ios/Pods/ViewControllers/TermAndConditionsVC.swift +0 -63
  71. package/ios/Pods/ViewControllers/ThreeDSecurePaymentDoneVC.swift +0 -1254
@@ -1,2996 +0,0 @@
1
- //
2
- // AdditionalInfoVC.swift
3
- // EasyPay
4
- //
5
- // Created by Mony's Mac on 13/08/24.
6
- //
7
-
8
- import UIKit
9
-
10
- @available(iOS 16.0, *)
11
- class AdditionalInfoVC: BaseVC {
12
-
13
- // @IBOutlet weak var viewAdditionalInfo: UIView!
14
- @IBOutlet weak var txtFieldName: UITextField!
15
- @IBOutlet weak var txtFieldEmail: UITextField!
16
- @IBOutlet weak var txtFieldPhoneNumber: UITextField!
17
- @IBOutlet weak var txtFieldDescription: UITextField!
18
- @IBOutlet weak var lblCountryCode: UILabel!
19
- @IBOutlet weak var btnPrevious: UIButton!
20
- @IBOutlet weak var imgViewCountryFlag: UIImageView!
21
-
22
- @IBOutlet weak var btnPayNow: UIButton!
23
-
24
- @IBOutlet weak var lblAdditionalInfo: UILabel!
25
- @IBOutlet weak var lblEasyMerchant: UILabel!
26
- @IBOutlet weak var btnCountryCode: UIButton!
27
-
28
- @IBOutlet weak var lblStarNameField: UILabel!
29
- @IBOutlet weak var lblStarEmailField: UILabel!
30
- @IBOutlet weak var lblStarPhoneField: UILabel!
31
- @IBOutlet weak var lblStarDescriptionField: UILabel!
32
-
33
- var cardNumber: String?
34
- var expiryDate: String?
35
- var cvv: String?
36
- var nameOnCard: String?
37
- var userEmail: String?
38
-
39
- var selectedPaymentMethod: String?
40
-
41
- //Banking Params
42
- var accountName: String?
43
- var routingNumber: String?
44
- var accountType: String?
45
- var accountNumber: String?
46
-
47
- var easyPayDelegate: EasyPayViewControllerDelegate?
48
-
49
- var isSavedForFuture: Bool = false
50
-
51
- var selectedCard: CardModel?
52
- // var amount: Int?
53
- var amount: Double?
54
- var cvvText: String?
55
- var isFrom = String()
56
- var isFromm = String()
57
-
58
- var isSavedNewCard: Bool = false
59
-
60
- //From Regular Saved Bank Accounts
61
- var customerID: String?
62
- var accountID: String?
63
-
64
- var isSavedNewAccount: Bool?
65
-
66
- var request: Request!
67
-
68
- var chosenPlan: String?
69
- var startDate: String?
70
-
71
- //GrailPay Params
72
- var grailPayAccountID: String?
73
- var selectedGrailPayAccountType: String?
74
- var selectedGrailPayAccountName: String?
75
-
76
- var billingInfoData: Data?
77
- var fieldSection: FieldSection?
78
-
79
- var additionalInfo: [FieldItem]?
80
- var billingInfo: [FieldItem]?
81
- var visibility: FieldsVisibility?
82
-
83
- override func viewDidLoad() {
84
- super.viewDidLoad()
85
- self.lblEasyMerchant.text = "POWERED BY \(UserStoreSingleton.shared.companyName?.uppercased() ?? "")"
86
-
87
- let amountValue = Double(amount ?? 0)
88
- let amountText = String(format: "$%.2f", amountValue)
89
- let submitText = request?.submitButtonText ?? ""
90
-
91
- let defaultTitle = !submitText.isEmpty
92
- ? "\(submitText) (\(amountText))"
93
- : "Pay Now (\(amountText))"
94
-
95
- DispatchQueue.main.async {
96
- self.btnPayNow.setTitle(defaultTitle, for: .normal)
97
- }
98
-
99
- uiFinishingTouchElements()
100
-
101
- txtFieldName.delegate = self
102
- txtFieldEmail.delegate = self
103
- txtFieldDescription.delegate = self
104
- txtFieldPhoneNumber.delegate = self
105
-
106
- // Add tap gesture to hide the views and dismiss the keyboard
107
- let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapOutside))
108
- tapGesture.cancelsTouchesInView = false
109
- self.view.addGestureRecognizer(tapGesture)
110
-
111
- // Decode fieldSection if it's not already set
112
- if fieldSection == nil, let billingInfoData = billingInfoData {
113
- do {
114
- let decodedFieldSection = try JSONDecoder().decode(FieldSection.self, from: billingInfoData)
115
- self.fieldSection = decodedFieldSection
116
- self.additionalInfo = decodedFieldSection.additional
117
- self.billingInfo = decodedFieldSection.billing
118
- self.visibility = decodedFieldSection.visibility
119
- print("fieldSection decoded successfully")
120
- } catch {
121
- print("Failed to decode billing info data: \(error)")
122
- }
123
- }
124
-
125
- if let additionalInfo = additionalInfo {
126
- for item in additionalInfo {
127
- switch item.name {
128
- case "name":
129
- txtFieldName.text = item.value
130
- case "email":
131
- txtFieldEmail.text = item.value
132
- case "phone_number":
133
- txtFieldPhoneNumber.text = item.value
134
- case "description":
135
- txtFieldDescription.text = item.value
136
- default:
137
- break
138
- }
139
- }
140
- configureFieldVisibility()
141
- } else if let billingInfoData = billingInfoData {
142
- do {
143
- let decodedFieldSection = try JSONDecoder().decode(FieldSection.self, from: billingInfoData)
144
- self.fieldSection = decodedFieldSection
145
-
146
- for item in decodedFieldSection.additional {
147
- switch item.name {
148
- case "name":
149
- txtFieldName.text = item.value
150
- case "email":
151
- txtFieldEmail.text = item.value
152
- case "phone_number":
153
- txtFieldPhoneNumber.text = item.value
154
- case "description":
155
- txtFieldDescription.text = item.value
156
- default:
157
- break
158
- }
159
- }
160
- configureFieldVisibility()
161
- } catch {
162
- print("Failed to decode billing info data: \(error)")
163
- }
164
- }
165
-
166
- if let flag = String.flag(for: "US") {
167
- lblCountryCode.text = "\(flag) +1"
168
- }
169
-
170
- //Show Default USA code in starting.
171
- let usaCountry = Country()
172
- usaCountry.name = "United States"
173
- usaCountry.countryCode = "US"
174
- usaCountry.extensionCode = "1"
175
- usaCountry.flag = String.flag(for: "US")
176
- lblCountryCode.text = "\(usaCountry.flag ?? "") +\(usaCountry.extensionCode ?? "")"
177
- }
178
-
179
- override func viewWillAppear(_ animated: Bool) {
180
- uiFinishingTouchElements()
181
- configureFieldVisibility()
182
- }
183
-
184
- func uiFinishingTouchElements() {
185
- // Set background color for the main view
186
- if let containerBGcolor = UserStoreSingleton.shared.container_bg_col,
187
- let uiColor = UIColor(hex: containerBGcolor) {
188
- self.view.backgroundColor = uiColor
189
- }
190
-
191
- if let primaryBtnBackGroundColor = UserStoreSingleton.shared.primary_btn_bg_col,
192
- let uiColor = UIColor(hex: primaryBtnBackGroundColor) {
193
- btnPayNow.backgroundColor = uiColor
194
- btnPrevious.setTitleColor(uiColor, for: .normal)
195
- btnPrevious.layer.borderColor = uiColor.cgColor
196
- }
197
-
198
- if let primaryBtnFontColor = UserStoreSingleton.shared.primary_btn_font_col,
199
- let secondaryUIColor = UIColor(hex: primaryBtnFontColor) {
200
- btnPayNow.setTitleColor(secondaryUIColor, for: .normal)
201
- }
202
-
203
- if let secondaryFontColor = UserStoreSingleton.shared.secondary_font_col,
204
- let placeholderColor = UIColor(hex: secondaryFontColor) {
205
- lblEasyMerchant.textColor = placeholderColor
206
- }
207
-
208
- if let borderRadiusString = UserStoreSingleton.shared.border_radious,
209
- let borderRadius = Double(borderRadiusString) { // Convert String to Double
210
- btnPayNow.layer.cornerRadius = CGFloat(borderRadius) // Set corner radius
211
- btnPrevious.layer.cornerRadius = CGFloat(borderRadius)
212
- btnPrevious.layer.borderWidth = 1
213
- } else {
214
- btnPayNow.layer.cornerRadius = 8 // Default value
215
- btnPrevious.layer.cornerRadius = 8
216
- }
217
- btnPayNow.layer.masksToBounds = true // Ensure the corners are clipped properly
218
- btnPrevious.layer.masksToBounds = true
219
-
220
- if let primaryFontColor = UserStoreSingleton.shared.primary_font_col,
221
- let uiColor = UIColor(hex: primaryFontColor) {
222
- lblAdditionalInfo.textColor = uiColor
223
- lblCountryCode.textColor = uiColor
224
- }
225
-
226
- if let fontSizeString = UserStoreSingleton.shared.fontSize,
227
- let fontSizeDouble = Double(fontSizeString) { // Convert String to Double
228
- let fontSize = CGFloat(fontSizeDouble) // Convert Double to CGFloat
229
- lblEasyMerchant.font = UIFont.systemFont(ofSize: fontSize)
230
- lblCountryCode.font = UIFont.systemFont(ofSize: fontSize)
231
- btnPayNow.titleLabel?.font = UIFont.systemFont(ofSize: fontSize)
232
- btnPrevious.titleLabel?.font = UIFont.systemFont(ofSize: fontSize)
233
- }
234
-
235
- }
236
-
237
- private func getFieldValue(for name: String) -> String? {
238
- return fieldSection?.additional.first(where: { $0.name == name })?.value
239
- }
240
-
241
- private func setFieldValue(_ name: String, to value: String?) {
242
- guard let index = fieldSection?.additional.firstIndex(where: { $0.name == name }) else { return }
243
- fieldSection?.additional[index].value = value ?? ""
244
- }
245
-
246
- private func configureFieldVisibility() {
247
- guard let additionalFields = fieldSection?.additional else {
248
- print("No additional fields found")
249
- return
250
- }
251
-
252
- func isFieldRequired(_ name: String) -> Bool {
253
- let required = additionalFields.first(where: { $0.name == name })?.required == true
254
- print("Field \(name) required: \(required)")
255
- return required
256
- }
257
-
258
- DispatchQueue.main.async {
259
- self.lblStarNameField.isHidden = !isFieldRequired("name")
260
- self.lblStarEmailField.isHidden = !isFieldRequired("email_address")
261
- self.lblStarPhoneField.isHidden = !isFieldRequired("phone_number")
262
- self.lblStarDescriptionField.isHidden = !isFieldRequired("description")
263
- print("Star visibility set")
264
- self.view.layoutIfNeeded()
265
- }
266
- }
267
-
268
- @objc func didTapOutside() {
269
- // Dismiss the keyboard
270
- self.view.endEditing(true)
271
- }
272
-
273
- @IBAction func actionBtnSelectCountryCode(_ sender: UIButton) {
274
- let vc = easymerchantsdk.instantiateViewController(withIdentifier: "CountryListVC") as! CountryListVC
275
- vc.delegate = self
276
- self.navigationController?.pushViewController(vc, animated: true)
277
- }
278
-
279
- @IBAction func actionBtnPrevious(_ sender: UIButton) {
280
- navigationController?.popViewController(animated: true)
281
- }
282
-
283
- func updateAdditionalInfoData() {
284
- setAdditionalFieldValue("name", to: txtFieldName.text)
285
- setAdditionalFieldValue("email", to: txtFieldEmail.text)
286
- setAdditionalFieldValue("phone_number", to: txtFieldPhoneNumber.text)
287
- setAdditionalFieldValue("description", to: txtFieldDescription.text)
288
- additionalInfo = fieldSection?.additional
289
- }
290
-
291
- // Helper method
292
- func setAdditionalFieldValue(_ name: String, to value: String?) {
293
- if let index = fieldSection?.additional.firstIndex(where: { $0.name == name }) {
294
- fieldSection?.additional[index].value = value ?? ""
295
- }
296
- }
297
-
298
- @IBAction func actionBtnPayNow(_ sender: UIButton) {
299
- // MARK: - Validation
300
- if !lblStarPhoneField.isHidden &&
301
- txtFieldPhoneNumber.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
302
- showAlert(title: "Missing Information", message: "Please enter your phone number.")
303
- return
304
- } else if !lblStarDescriptionField.isHidden &&
305
- txtFieldDescription.text?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
306
- showAlert(title: "Missing Information", message: "Please enter description text.")
307
- return
308
- }
309
-
310
- // MARK: - Update Additional Info
311
- updateAdditionalInfoData()
312
-
313
- // MARK: - Encode Updated fieldSection to billingInfoData
314
- if let updatedFieldSection = fieldSection,
315
- let updatedBillingData = try? JSONEncoder().encode(updatedFieldSection) {
316
- billingInfoData = updatedBillingData
317
- } else {
318
- print("❌ Failed to encode updated additional info")
319
- return
320
- }
321
-
322
- // MARK: - Flow Based on Conditions
323
- if isSavedForFuture {
324
- // ✅ If logged in → Call API directly, no navigation
325
- if UserStoreSingleton.shared.isLoggedIn == true {
326
- switch selectedPaymentMethod {
327
- case "Card":
328
- if request.secureAuthentication == true {
329
- threeDSecurePaymentApi()
330
- } else {
331
- paymentIntentApi()
332
- }
333
-
334
- case "Bank":
335
- accountChargeApi()
336
-
337
- case "GrailPay":
338
- grailPayAccountChargeApi()
339
-
340
- case "NewGrailPayAccount":
341
- grailPayAccountChargeApi(customerId: UserStoreSingleton.shared.customerId)
342
-
343
- default:
344
- print("Unknown payment method for logged-in user")
345
- }
346
- return
347
- }
348
-
349
- // ❌ Not logged in → Always navigate to OTPVerificationVC
350
- if let emailVerificationVC = self.storyboard?.instantiateViewController(withIdentifier: "OTPVerificationVC") as? OTPVerificationVC {
351
-
352
- emailVerificationVC.billingInfoData = billingInfoData
353
- emailVerificationVC.selectedPaymentMethod = selectedPaymentMethod
354
- emailVerificationVC.easyPayDelegate = easyPayDelegate
355
- emailVerificationVC.request = request
356
- emailVerificationVC.chosenPlan = chosenPlan
357
- emailVerificationVC.startDate = startDate
358
- emailVerificationVC.userEmail = userEmail
359
- emailVerificationVC.billingInfo = fieldSection?.billing
360
- emailVerificationVC.additionalInfo = fieldSection?.additional
361
- emailVerificationVC.visibility = fieldSection?.visibility
362
- emailVerificationVC.amount = amount
363
- emailVerificationVC.email = userEmail
364
-
365
- // Payment method-specific data
366
- switch selectedPaymentMethod {
367
- case "Card":
368
- emailVerificationVC.cardNumber = cardNumber
369
- emailVerificationVC.expiryDate = expiryDate
370
- emailVerificationVC.cvv = cvv
371
- emailVerificationVC.nameOnCard = nameOnCard
372
-
373
- case "Bank":
374
- emailVerificationVC.accountName = accountName
375
- emailVerificationVC.routingNumber = routingNumber
376
- emailVerificationVC.accountType = accountType
377
- emailVerificationVC.accountNumber = accountNumber
378
-
379
- case "GrailPay":
380
- emailVerificationVC.grailPayAccountID = grailPayAccountID
381
- emailVerificationVC.selectedGrailPayAccountType = selectedGrailPayAccountType
382
- emailVerificationVC.selectedGrailPayAccountName = selectedGrailPayAccountName
383
- emailVerificationVC.isSavedForFuture = true
384
-
385
- default:
386
- break
387
- }
388
-
389
- navigationController?.pushViewController(emailVerificationVC, animated: true)
390
- }
391
- }
392
-
393
- else {
394
- // Direct Payment Flow
395
- if selectedPaymentMethod == "Card" {
396
- if isFrom == "SavedCards" {
397
- paymentIntentFromShowCardApi()
398
- }
399
- else if isSavedNewCard {
400
- if isFrom == "AddNewCard" {
401
- if request.secureAuthentication == true {
402
- threeDSecurePaymentAddNewCardApi(customerId: UserStoreSingleton.shared.customerId)
403
- } else {
404
- paymentIntentAddNewCardApi(customerId: UserStoreSingleton.shared.customerId)
405
- }
406
- }
407
- }
408
- else {
409
- if request.secureAuthentication == true {
410
- threeDSecurePaymentApi()
411
- } else {
412
- paymentIntentApi()
413
- }
414
- }
415
- }
416
- else if selectedPaymentMethod == "Bank" {
417
- if isFrom == "SavedBank" {
418
- accountChargeSavedBankAccountApi()
419
- }
420
- else if isFrom == "NormalBankPayWithoutSave" {
421
- accountChargeApi()
422
- }
423
- else if isFrom == "AddNewAccountWithoutSave" {
424
- accountChargeApi()
425
- }
426
- else if isFrom == "AddNewAccountWithSave" {
427
- accountChargeApi(customerId: UserStoreSingleton.shared.customerId)
428
- }
429
- }
430
- else if selectedPaymentMethod == "GrailPay" {
431
- grailPayAccountChargeApi()
432
- }
433
- else if selectedPaymentMethod == "NewGrailPayAccount" {
434
- grailPayAccountChargeApi()
435
- }
436
- }
437
- }
438
-
439
- func presentPaymentErrorVC(errorMessage: String) {
440
- DispatchQueue.main.async {
441
- if let paymentErrorVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentErrorVC") as? PaymentErrorVC {
442
- paymentErrorVC.errorMessage = errorMessage
443
- paymentErrorVC.easyPayDelegate = self.easyPayDelegate // Pass the reference here
444
- self.navigationController?.pushViewController(paymentErrorVC, animated: true)
445
- }
446
- }
447
- }
448
-
449
- // MARK: - Credit Card Charge Api
450
- func paymentIntentApi() {
451
- showLoadingIndicator()
452
-
453
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.charges.path()
454
-
455
- guard let serviceURL = URL(string: fullURL) else {
456
- print("Invalid URL")
457
- hideLoadingIndicator()
458
- return
459
- }
460
-
461
- var urlRequest = URLRequest(url: serviceURL)
462
- urlRequest.httpMethod = "POST"
463
- urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
464
-
465
- let token = UserStoreSingleton.shared.clientToken
466
- print("Setting clientToken header: \(token ?? "None")")
467
- urlRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
468
-
469
- // Extract only the digits from the phone number (local only, no country code)
470
- let localPhone = txtFieldPhoneNumber.text?.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() ?? ""
471
-
472
- var params: [String: Any] = [
473
- "name": nameOnCard ?? "",
474
- "email": userEmail ?? "",
475
- "card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
476
- "cardholder_name": nameOnCard ?? "",
477
- "exp_month": expiryDate?.components(separatedBy: "/").first ?? "",
478
- "exp_year": expiryDate?.components(separatedBy: "/").last ?? "",
479
- "cvc": cvv ?? "",
480
- "currency": "usd"
481
- ]
482
-
483
- // ✅ Only for logged-in users
484
- if UserStoreSingleton.shared.isLoggedIn == true {
485
- let emailText = userEmail
486
- let emailPrefix = emailText?.components(separatedBy: "@").first ?? ""
487
-
488
- params["save_card"] = isSavedNewCard ? 1 : 0
489
- if isSavedNewCard {
490
- params["is_default"] = "1"
491
- }
492
- params["tokenize"] = request.tokenOnly ?? ""
493
- params["username"] = emailPrefix
494
-
495
- if let customerId = UserStoreSingleton.shared.customerId {
496
- params["customer"] = customerId
497
- params["customer_id"] = customerId
498
- } else {
499
- params["create_customer"] = "1"
500
- }
501
-
502
- if UserStoreSingleton.shared.customerId == nil {
503
- params["create_customer"] = "1"
504
- }
505
- }
506
-
507
- // Conditionally add billing info
508
- if let visibility = visibility, visibility.billing == true,
509
- let billing = billingInfo, !billing.isEmpty {
510
-
511
- var billingInfoDict: [String: Any] = [:]
512
- for item in billing {
513
- billingInfoDict[item.name] = item.value
514
- }
515
-
516
- params["address"] = billingInfoDict["address"] as? String ?? ""
517
- params["country"] = billingInfoDict["country"] as? String ?? ""
518
- params["state"] = billingInfoDict["state"] as? String ?? ""
519
- params["city"] = billingInfoDict["city"] as? String ?? ""
520
- params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
521
- }
522
-
523
- // Conditionally add additional info
524
- if let visibility = visibility, visibility.additional == true,
525
- let additional = additionalInfo, !additional.isEmpty {
526
- params["description"] = txtFieldDescription.text ?? ""
527
- params["phone_number"] = localPhone
528
- }
529
-
530
- // Add these if recurring is enabled
531
- // if let req = request, req.is_recurring == true {
532
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
533
- // // Only send start_date if type is .custom and field is not empty
534
- // if let startDateText = startDate, !startDateText.isEmpty {
535
- // let inputFormatter = DateFormatter()
536
- // inputFormatter.dateFormat = "dd/MM/yyyy"
537
- //
538
- // let outputFormatter = DateFormatter()
539
- // outputFormatter.dateFormat = "MM/dd/yyyy"
540
- //
541
- // if let date = inputFormatter.date(from: startDateText) {
542
- // let apiFormattedDate = outputFormatter.string(from: date)
543
- // params["start_date"] = apiFormattedDate
544
- // } else {
545
- // print("Invalid date format in startDateText")
546
- // }
547
- // }
548
- // }
549
- //
550
- // params["interval"] = chosenPlan?.lowercased()
551
- // }
552
-
553
- // Add these if recurring is enabled
554
- if let req = request, req.is_recurring == true {
555
- if let startDateText = startDate, !startDateText.isEmpty {
556
- let inputFormatter = DateFormatter()
557
- inputFormatter.dateFormat = "dd/MM/yyyy"
558
-
559
- let outputFormatter = DateFormatter()
560
- outputFormatter.dateFormat = "MM/dd/yyyy"
561
-
562
- if let date = inputFormatter.date(from: startDateText) {
563
- let apiFormattedDate = outputFormatter.string(from: date)
564
- params["start_date"] = apiFormattedDate
565
- } else {
566
- print("Invalid date format in startDateText")
567
- }
568
- }
569
-
570
- // interval is still required
571
- params["interval"] = chosenPlan?.lowercased()
572
- }
573
-
574
- // ✅ Include metadata only if it has at least 1 key-value pair
575
- if let metadata = request?.metadata, !metadata.isEmpty {
576
- params["metadata"] = metadata
577
- }
578
-
579
- print(params)
580
-
581
- do {
582
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
583
- urlRequest.httpBody = jsonData
584
- if let jsonString = String(data: jsonData, encoding: .utf8) {
585
- print("JSON Payload: \(jsonString)")
586
- }
587
- } catch let error {
588
- print("Error creating JSON data: \(error)")
589
- hideLoadingIndicator()
590
- return
591
- }
592
-
593
- let session = URLSession.shared
594
- let task = session.dataTask(with: urlRequest) { (serviceData, serviceResponse, error) in
595
-
596
- DispatchQueue.main.async {
597
- self.hideLoadingIndicator()
598
- }
599
-
600
- if let error = error {
601
- print("Error: \(error.localizedDescription)")
602
- self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
603
- return
604
- }
605
-
606
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
607
- print("Invalid response")
608
- self.presentPaymentErrorVC(errorMessage: "Invalid response from server.")
609
- return
610
- }
611
-
612
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
613
- if let data = serviceData {
614
- do {
615
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
616
- print("Response Data: \(responseObject)")
617
-
618
- // ✅ Handle duplicate transaction case
619
- if let status = responseObject["status"] as? Bool, status == false,
620
- let message = responseObject["message"] as? String,
621
- message.lowercased().contains("duplicate transaction") {
622
- self.presentPaymentErrorVC(errorMessage: message)
623
- return
624
- }
625
-
626
- if let status = responseObject["status"] as? Int, status == 0,
627
- let message = responseObject["message"] as? String,
628
- message.lowercased().contains("duplicate transaction") {
629
- self.presentPaymentErrorVC(errorMessage: message)
630
- return
631
- }
632
-
633
- // ✅ Handle generic "status == 0" error case
634
- if let status = responseObject["status"] as? Int, status == 0 {
635
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
636
- self.presentPaymentErrorVC(errorMessage: errorMessage)
637
- return
638
- }
639
- else {
640
- DispatchQueue.main.async {
641
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
642
- paymentDoneVC.chargeData = responseObject
643
- paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
644
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate
645
- // Pass billing and additional info
646
- // Conditionally pass raw FieldItem array
647
- paymentDoneVC.visibility = self.visibility
648
- paymentDoneVC.request = self.request
649
-
650
- // if self.visibility?.billing == true {
651
- paymentDoneVC.billingInfoData = self.billingInfo
652
- var billingDict: [String: Any] = [:]
653
- self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
654
- paymentDoneVC.billingInfo = billingDict
655
- // }
656
-
657
- // if self.visibility?.additional == true {
658
- // Update additionalInfo values before sending
659
- if let index = self.additionalInfo?.firstIndex(where: { $0.name == "description" }) {
660
- self.additionalInfo?[index].value = self.txtFieldDescription.text ?? ""
661
- }
662
- if let index = self.additionalInfo?.firstIndex(where: { $0.name == "phone_number" }) {
663
- self.additionalInfo?[index].value = localPhone
664
- }
665
-
666
- paymentDoneVC.additionalInfoData = self.additionalInfo
667
-
668
- var additionalDict: [String: Any] = [:]
669
- self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
670
- paymentDoneVC.additionalInfo = additionalDict
671
- // }
672
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
673
- }
674
- }
675
- }
676
- } else {
677
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
678
- }
679
- } catch let jsonError {
680
- self.presentPaymentErrorVC(errorMessage: "Error parsing response: \(jsonError.localizedDescription)")
681
- }
682
- } else {
683
- self.presentPaymentErrorVC(errorMessage: "No data received from server.")
684
- }
685
- } else {
686
- if let data = serviceData,
687
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
688
- let message = responseObj["message"] as? String {
689
- self.presentPaymentErrorVC(errorMessage: message)
690
- } else {
691
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
692
- }
693
- }
694
- }
695
- task.resume()
696
- }
697
-
698
- //MARK: - Credit Card Charge Api from Saved cards
699
- func paymentIntentFromShowCardApi() {
700
-
701
- print(">>> paymentIntentFromShowCardApi() called")
702
- Thread.callStackSymbols.forEach { print($0) }
703
-
704
- showLoadingIndicator()
705
-
706
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.charges.path()
707
-
708
- guard let serviceURL = URL(string: fullURL) else {
709
- print("Invalid URL")
710
- hideLoadingIndicator()
711
- return
712
- }
713
-
714
- var uRLRequest = URLRequest(url: serviceURL)
715
- uRLRequest.httpMethod = "POST"
716
- uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
717
-
718
- let token = UserStoreSingleton.shared.clientToken
719
- print("Setting clientToken header: \(token ?? "None")")
720
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
721
-
722
- // Extract only the digits from the phone number (local only, no country code)
723
- let localPhone = txtFieldPhoneNumber.text?.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() ?? ""
724
-
725
- // let emailText = UserStoreSingleton.shared.verificationEmail ?? ""
726
- // let emailPrefix = emailText.components(separatedBy: "@").first ?? ""
727
-
728
- let finalEmail: String
729
- if let verificationEmail = UserStoreSingleton.shared.verificationEmail, !verificationEmail.isEmpty {
730
- finalEmail = verificationEmail
731
- } else {
732
- finalEmail = request.email ?? ""
733
- }
734
-
735
- let emailPrefix: String
736
- if !finalEmail.isEmpty {
737
- emailPrefix = finalEmail.components(separatedBy: "@").first ?? ""
738
- } else {
739
- emailPrefix = ""
740
- }
741
-
742
- // Determine name: use request.name if available, otherwise fallback to email prefix
743
- let nameParam: String
744
- if let requestName = request.name, !requestName.trimmingCharacters(in: .whitespaces).isEmpty {
745
- nameParam = requestName
746
- } else {
747
- nameParam = emailPrefix
748
- }
749
-
750
- var params: [String: Any] = [
751
- "description": txtFieldDescription.text ?? "",
752
- "currency": "usd",
753
- "payment_method": "card",
754
- "save_card": 0,
755
- "customer" : selectedCard?.customerId ?? "",
756
- "customer_id" : selectedCard?.customerId ?? "",
757
- "card_id" : selectedCard?.cardId ?? "",
758
- "cvc" : cvvText ?? "",
759
- "name": nameParam,
760
- "email": finalEmail
761
- ]
762
-
763
- // Conditionally add billing info
764
- if let visibility = visibility, visibility.billing == true,
765
- let billing = billingInfo, !billing.isEmpty {
766
-
767
- var billingInfoDict: [String: Any] = [:]
768
- for item in billing {
769
- billingInfoDict[item.name] = item.value
770
- }
771
-
772
- params["address"] = billingInfoDict["address"] as? String ?? ""
773
- params["country"] = billingInfoDict["country"] as? String ?? ""
774
- params["state"] = billingInfoDict["state"] as? String ?? ""
775
- params["city"] = billingInfoDict["city"] as? String ?? ""
776
- params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
777
- }
778
-
779
- // Default values
780
- let defaultDescription = "Hosted payment checkout"
781
- // Additional Info
782
- if let visibility = visibility, visibility.additional == true,
783
- let additional = additionalInfo, !additional.isEmpty {
784
-
785
- var additionalInfoDict: [String: Any] = [:]
786
- for item in additional {
787
- additionalInfoDict[item.name] = item.value
788
- }
789
-
790
- // Description
791
- let description = (additionalInfoDict["description"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
792
- params["description"] = (description?.isEmpty == false) ? description! : defaultDescription
793
-
794
- // Phone
795
- params["phone_number"] = localPhone
796
- } else {
797
- // Fallback if additional section not visible
798
- params["description"] = defaultDescription
799
- }
800
-
801
- // Add these if recurring is enabled
802
- // if let req = request, req.is_recurring == true {
803
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
804
- // // Only send start_date if type is .custom and field is not empty
805
- // if let startDateText = startDate, !startDateText.isEmpty {
806
- // let inputFormatter = DateFormatter()
807
- // inputFormatter.dateFormat = "dd/MM/yyyy"
808
- //
809
- // let outputFormatter = DateFormatter()
810
- // outputFormatter.dateFormat = "MM/dd/yyyy"
811
- //
812
- // if let date = inputFormatter.date(from: startDateText) {
813
- // let apiFormattedDate = outputFormatter.string(from: date)
814
- // params["start_date"] = apiFormattedDate
815
- // } else {
816
- // print("Invalid date format in startDateText")
817
- // }
818
- // }
819
- // }
820
- //
821
- // params["interval"] = chosenPlan?.lowercased()
822
- // }
823
-
824
- // Add these if recurring is enabled
825
- if let req = request, req.is_recurring == true {
826
- if let startDateText = startDate, !startDateText.isEmpty {
827
- let inputFormatter = DateFormatter()
828
- inputFormatter.dateFormat = "dd/MM/yyyy"
829
-
830
- let outputFormatter = DateFormatter()
831
- outputFormatter.dateFormat = "MM/dd/yyyy"
832
-
833
- if let date = inputFormatter.date(from: startDateText) {
834
- let apiFormattedDate = outputFormatter.string(from: date)
835
- params["start_date"] = apiFormattedDate
836
- } else {
837
- print("Invalid date format in startDateText")
838
- }
839
- }
840
-
841
- // interval is still required
842
- params["interval"] = chosenPlan?.lowercased()
843
- }
844
-
845
- // ✅ Include metadata only if it has at least 1 key-value pair
846
- if let metadata = request?.metadata, !metadata.isEmpty {
847
- params["metadata"] = metadata
848
- }
849
-
850
- print(params)
851
-
852
- do {
853
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
854
- uRLRequest.httpBody = jsonData
855
- if let jsonString = String(data: jsonData, encoding: .utf8) {
856
- print("JSON Payload: \(jsonString)")
857
- }
858
- } catch let error {
859
- print("Error creating JSON data: \(error)")
860
- hideLoadingIndicator()
861
- return
862
- }
863
-
864
- let session = URLSession.shared
865
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
866
-
867
- DispatchQueue.main.async {
868
- self.hideLoadingIndicator() // Stop loader when response is received
869
- }
870
-
871
- if let error = error {
872
- print("Error: \(error.localizedDescription)")
873
- return
874
- }
875
-
876
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
877
- print("Invalid response")
878
- return
879
- }
880
-
881
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
882
- if let data = serviceData {
883
- do {
884
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
885
- print("Response Data: \(responseObject)")
886
-
887
- // ✅ Handle duplicate transaction case
888
- if let status = responseObject["status"] as? Bool, status == false,
889
- let message = responseObject["message"] as? String,
890
- message.lowercased().contains("duplicate transaction") {
891
- self.presentPaymentErrorVC(errorMessage: message)
892
- return
893
- }
894
-
895
- if let status = responseObject["status"] as? Int, status == 0,
896
- let message = responseObject["message"] as? String,
897
- message.lowercased().contains("duplicate transaction") {
898
- self.presentPaymentErrorVC(errorMessage: message)
899
- return
900
- }
901
-
902
- // ✅ Handle generic "status == 0" error case
903
- if let status = responseObject["status"] as? Int, status == 0 {
904
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
905
- self.presentPaymentErrorVC(errorMessage: errorMessage)
906
- return
907
- }
908
- else {
909
- DispatchQueue.main.async {
910
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
911
- paymentDoneVC.chargeData = responseObject
912
- paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
913
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate
914
- // Pass billing and additional info
915
- // Conditionally pass raw FieldItem array
916
- paymentDoneVC.visibility = self.visibility
917
- paymentDoneVC.request = self.request
918
-
919
- // if self.visibility?.billing == true {
920
- paymentDoneVC.billingInfoData = self.billingInfo
921
- var billingDict: [String: Any] = [:]
922
- self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
923
- paymentDoneVC.billingInfo = billingDict
924
- // }
925
-
926
- // if self.visibility?.additional == true {
927
- // Update additionalInfo values before sending
928
- if let index = self.additionalInfo?.firstIndex(where: { $0.name == "description" }) {
929
- self.additionalInfo?[index].value = self.txtFieldDescription.text ?? ""
930
- }
931
- if let index = self.additionalInfo?.firstIndex(where: { $0.name == "phone_number" }) {
932
- self.additionalInfo?[index].value = localPhone
933
- }
934
-
935
- paymentDoneVC.additionalInfoData = self.additionalInfo
936
-
937
- var additionalDict: [String: Any] = [:]
938
- self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
939
- paymentDoneVC.additionalInfo = additionalDict
940
- // }
941
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
942
- }
943
- }
944
- }
945
- } else {
946
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
947
- }
948
- } catch let jsonError {
949
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
950
- }
951
- } else {
952
- self.presentPaymentErrorVC(errorMessage: "No data received")
953
- }
954
- } else {
955
- if let data = serviceData,
956
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
957
- let message = responseObj["message"] as? String {
958
- self.presentPaymentErrorVC(errorMessage: message)
959
- } else {
960
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
961
- }
962
- }
963
- }
964
- task.resume()
965
- }
966
-
967
- //MARK: - Credit Card Charge Api from Add new card from saved cards.
968
- func paymentIntentAddNewCardApi(customerId: String?) {
969
- showLoadingIndicator()
970
-
971
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.charges.path()
972
-
973
- guard let serviceURL = URL(string: fullURL) else {
974
- print("Invalid URL")
975
- hideLoadingIndicator()
976
- return
977
- }
978
-
979
- var uRLRequest = URLRequest(url: serviceURL)
980
- uRLRequest.httpMethod = "POST"
981
- uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
982
-
983
- let token = UserStoreSingleton.shared.clientToken
984
- print("Setting clientToken header: \(token ?? "None")")
985
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
986
-
987
- // Extract only the digits from the phone number (local only, no country code)
988
- let localPhone = txtFieldPhoneNumber.text?.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() ?? ""
989
-
990
- // let emailPrefix = UserStoreSingleton.shared.verificationEmail?.components(separatedBy: "@").first ?? ""
991
-
992
- let finalEmail: String
993
- if let verificationEmail = UserStoreSingleton.shared.verificationEmail, !verificationEmail.isEmpty {
994
- finalEmail = verificationEmail
995
- } else {
996
- finalEmail = request.email ?? ""
997
- }
998
-
999
- let emailPrefix: String
1000
- if !finalEmail.isEmpty {
1001
- emailPrefix = finalEmail.components(separatedBy: "@").first ?? ""
1002
- } else {
1003
- emailPrefix = ""
1004
- }
1005
-
1006
- var params: [String: Any] = [
1007
- "name": nameOnCard ?? "",
1008
- "email": finalEmail,
1009
- "card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
1010
- "cardholder_name": nameOnCard ?? "",
1011
- "exp_month": expiryDate?.components(separatedBy: "/").first ?? "",
1012
- "exp_year": expiryDate?.components(separatedBy: "/").last ?? "",
1013
- "cvc": cvv ?? "",
1014
- "currency": "usd",
1015
- "payment_method": selectedPaymentMethod ?? "",
1016
- "save_card": isSavedNewCard ? 1 : 0
1017
- ]
1018
-
1019
- // Add is_default parameter if save_card is 1
1020
- if isSavedNewCard {
1021
- params["is_default"] = "1"
1022
- }
1023
-
1024
- // Conditionally add billing info
1025
- if let visibility = visibility, visibility.billing == true,
1026
- let billing = billingInfo, !billing.isEmpty {
1027
-
1028
- var billingInfoDict: [String: Any] = [:]
1029
- for item in billing {
1030
- billingInfoDict[item.name] = item.value
1031
- }
1032
-
1033
- params["address"] = billingInfoDict["address"] as? String ?? ""
1034
- params["country"] = billingInfoDict["country"] as? String ?? ""
1035
- params["state"] = billingInfoDict["state"] as? String ?? ""
1036
- params["city"] = billingInfoDict["city"] as? String ?? ""
1037
- params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
1038
- }
1039
-
1040
- // Conditionally add additional info
1041
- if let visibility = visibility, visibility.additional == true,
1042
- let additional = additionalInfo, !additional.isEmpty {
1043
- params["description"] = txtFieldDescription.text ?? ""
1044
- params["phone_number"] = localPhone
1045
- }
1046
-
1047
- // Add these if recurring is enabled
1048
- // if let req = request, req.is_recurring == true {
1049
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
1050
- // // Only send start_date if type is .custom and field is not empty
1051
- // if let startDateText = startDate, !startDateText.isEmpty {
1052
- // let inputFormatter = DateFormatter()
1053
- // inputFormatter.dateFormat = "dd/MM/yyyy"
1054
- //
1055
- // let outputFormatter = DateFormatter()
1056
- // outputFormatter.dateFormat = "MM/dd/yyyy"
1057
- //
1058
- // if let date = inputFormatter.date(from: startDateText) {
1059
- // let apiFormattedDate = outputFormatter.string(from: date)
1060
- // params["start_date"] = apiFormattedDate
1061
- // } else {
1062
- // print("Invalid date format in startDateText")
1063
- // }
1064
- // }
1065
- // }
1066
- //
1067
- // params["interval"] = chosenPlan?.lowercased()
1068
- // }
1069
-
1070
- // Add these if recurring is enabled
1071
- if let req = request, req.is_recurring == true {
1072
- if let startDateText = startDate, !startDateText.isEmpty {
1073
- let inputFormatter = DateFormatter()
1074
- inputFormatter.dateFormat = "dd/MM/yyyy"
1075
-
1076
- let outputFormatter = DateFormatter()
1077
- outputFormatter.dateFormat = "MM/dd/yyyy"
1078
-
1079
- if let date = inputFormatter.date(from: startDateText) {
1080
- let apiFormattedDate = outputFormatter.string(from: date)
1081
- params["start_date"] = apiFormattedDate
1082
- } else {
1083
- print("Invalid date format in startDateText")
1084
- }
1085
- }
1086
-
1087
- // interval is still required
1088
- params["interval"] = chosenPlan?.lowercased()
1089
- }
1090
-
1091
- if let customerId = customerId {
1092
- params["customer"] = customerId
1093
- params["customer_id"] = customerId
1094
- } else {
1095
- params["username"] = emailPrefix
1096
- params["email"] = finalEmail
1097
- }
1098
-
1099
- // ✅ Include metadata only if it has at least 1 key-value pair
1100
- if let metadata = request?.metadata, !metadata.isEmpty {
1101
- params["metadata"] = metadata
1102
- }
1103
-
1104
- print(params)
1105
-
1106
- do {
1107
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1108
- uRLRequest.httpBody = jsonData
1109
- if let jsonString = String(data: jsonData, encoding: .utf8) {
1110
- print("JSON Payload: \(jsonString)")
1111
- }
1112
- } catch let error {
1113
- print("Error creating JSON data: \(error)")
1114
- hideLoadingIndicator()
1115
- return
1116
- }
1117
-
1118
- let session = URLSession.shared
1119
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
1120
-
1121
- DispatchQueue.main.async {
1122
- self.hideLoadingIndicator() // Stop loader when response is received
1123
- }
1124
-
1125
- if let error = error {
1126
- self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
1127
- return
1128
- }
1129
-
1130
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1131
- self.presentPaymentErrorVC(errorMessage: "Invalid response")
1132
- return
1133
- }
1134
-
1135
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1136
- if let data = serviceData {
1137
- do {
1138
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1139
- print("Response Data: \(responseObject)")
1140
-
1141
- // ✅ Handle duplicate transaction case
1142
- if let status = responseObject["status"] as? Bool, status == false,
1143
- let message = responseObject["message"] as? String,
1144
- message.lowercased().contains("duplicate transaction") {
1145
- self.presentPaymentErrorVC(errorMessage: message)
1146
- return
1147
- }
1148
-
1149
- if let status = responseObject["status"] as? Int, status == 0,
1150
- let message = responseObject["message"] as? String,
1151
- message.lowercased().contains("duplicate transaction") {
1152
- self.presentPaymentErrorVC(errorMessage: message)
1153
- return
1154
- }
1155
-
1156
- // ✅ Handle generic "status == 0" error case
1157
- if let status = responseObject["status"] as? Int, status == 0 {
1158
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
1159
- self.presentPaymentErrorVC(errorMessage: errorMessage)
1160
- return
1161
- }
1162
- else {
1163
- DispatchQueue.main.async {
1164
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
1165
- paymentDoneVC.chargeData = responseObject
1166
- paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
1167
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate
1168
- // Pass billing and additional info
1169
- // Conditionally pass raw FieldItem array
1170
- paymentDoneVC.visibility = self.visibility
1171
- paymentDoneVC.request = self.request
1172
-
1173
- // if self.visibility?.billing == true {
1174
- paymentDoneVC.billingInfoData = self.billingInfo
1175
- var billingDict: [String: Any] = [:]
1176
- self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
1177
- paymentDoneVC.billingInfo = billingDict
1178
- // }
1179
-
1180
- // if self.visibility?.additional == true {
1181
- // Update additionalInfo values before sending
1182
- if let index = self.additionalInfo?.firstIndex(where: { $0.name == "description" }) {
1183
- self.additionalInfo?[index].value = self.txtFieldDescription.text ?? ""
1184
- }
1185
- if let index = self.additionalInfo?.firstIndex(where: { $0.name == "phone_number" }) {
1186
- self.additionalInfo?[index].value = localPhone
1187
- }
1188
-
1189
- paymentDoneVC.additionalInfoData = self.additionalInfo
1190
-
1191
- var additionalDict: [String: Any] = [:]
1192
- self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
1193
- paymentDoneVC.additionalInfo = additionalDict
1194
- // }
1195
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1196
- }
1197
- }
1198
- }
1199
- } else {
1200
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1201
- }
1202
- } catch let jsonError {
1203
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
1204
- }
1205
- } else {
1206
- self.presentPaymentErrorVC(errorMessage: "No data received")
1207
- }
1208
- } else {
1209
- if let data = serviceData,
1210
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1211
- let message = responseObj["message"] as? String {
1212
- self.presentPaymentErrorVC(errorMessage: message)
1213
- } else {
1214
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1215
- }
1216
- }
1217
- }
1218
- task.resume()
1219
- }
1220
-
1221
- //MARK: - Banking Account Charge Api
1222
- func accountChargeApi() {
1223
-
1224
- print(">>> accountChargeApi() called")
1225
- Thread.callStackSymbols.forEach { print($0) }
1226
-
1227
- showLoadingIndicator()
1228
-
1229
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
1230
-
1231
- guard let serviceURL = URL(string: fullURL) else {
1232
- print("Invalid URL")
1233
- hideLoadingIndicator()
1234
- return
1235
- }
1236
-
1237
- var uRLRequest = URLRequest(url: serviceURL)
1238
- uRLRequest.httpMethod = "POST"
1239
- uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1240
-
1241
- let token = UserStoreSingleton.shared.clientToken
1242
- print("Setting clientToken header: \(token ?? "None")")
1243
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1244
-
1245
- // Extract only the digits from the phone number (local only, no country code)
1246
- let localPhone = txtFieldPhoneNumber.text?.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() ?? ""
1247
-
1248
- var params: [String: Any] = [
1249
- // "name": accountName ?? "",
1250
- "name": !(request.name?.isEmpty ?? true) ? request.name! : (accountName ?? ""),
1251
- "email": userEmail ?? "",
1252
- "description": txtFieldDescription.text ?? "",
1253
- "currency": "usd",
1254
- "account_type": accountType?.lowercased() ?? "",
1255
- "routing_number": routingNumber ?? "",
1256
- "account_number": accountNumber ?? "",
1257
- "payment_mode": "auth_and_capture",
1258
- "levelIndicator": 1,
1259
- ]
1260
-
1261
- // ✅ Only for logged-in users
1262
- if UserStoreSingleton.shared.isLoggedIn == true {
1263
- let emailText = userEmail
1264
- let emailPrefix = emailText?.components(separatedBy: "@").first ?? ""
1265
-
1266
- params["save_account"] = (isSavedNewAccount ?? false) ? 1 : 0
1267
-
1268
- if let customerId = UserStoreSingleton.shared.customerId, !customerId.isEmpty {
1269
- params["customer"] = customerId
1270
- } else {
1271
- params["username"] = emailPrefix
1272
- }
1273
-
1274
- if UserStoreSingleton.shared.customerId == nil {
1275
- params["create_customer"] = "1"
1276
- }
1277
- }
1278
-
1279
- // Conditionally add billing info
1280
- if let visibility = visibility, visibility.billing == true,
1281
- let billing = billingInfo, !billing.isEmpty {
1282
-
1283
- var billingInfoDict: [String: Any] = [:]
1284
- for item in billing {
1285
- billingInfoDict[item.name] = item.value
1286
- }
1287
-
1288
- params["address"] = billingInfoDict["address"] as? String ?? ""
1289
- params["country"] = billingInfoDict["country"] as? String ?? ""
1290
- params["state"] = billingInfoDict["state"] as? String ?? ""
1291
- params["city"] = billingInfoDict["city"] as? String ?? ""
1292
- params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
1293
- }
1294
-
1295
- // Conditionally add additional info
1296
- if let visibility = visibility, visibility.additional == true,
1297
- let additional = additionalInfo, !additional.isEmpty {
1298
- params["description"] = txtFieldDescription.text ?? ""
1299
- params["phone_number"] = localPhone
1300
- }
1301
-
1302
- // Add these if recurring is enabled
1303
- // if let req = request, req.is_recurring == true {
1304
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
1305
- // // Only send start_date if type is .custom and field is not empty
1306
- // if let startDateText = startDate, !startDateText.isEmpty {
1307
- // let inputFormatter = DateFormatter()
1308
- // inputFormatter.dateFormat = "dd/MM/yyyy"
1309
- //
1310
- // let outputFormatter = DateFormatter()
1311
- // outputFormatter.dateFormat = "MM/dd/yyyy"
1312
- //
1313
- // if let date = inputFormatter.date(from: startDateText) {
1314
- // let apiFormattedDate = outputFormatter.string(from: date)
1315
- // params["start_date"] = apiFormattedDate
1316
- // } else {
1317
- // print("Invalid date format in startDateText")
1318
- // }
1319
- // }
1320
- // }
1321
- //
1322
- // params["interval"] = chosenPlan?.lowercased()
1323
- // }
1324
-
1325
- // Add these if recurring is enabled
1326
- if let req = request, req.is_recurring == true {
1327
- if let startDateText = startDate, !startDateText.isEmpty {
1328
- let inputFormatter = DateFormatter()
1329
- inputFormatter.dateFormat = "dd/MM/yyyy"
1330
-
1331
- let outputFormatter = DateFormatter()
1332
- outputFormatter.dateFormat = "MM/dd/yyyy"
1333
-
1334
- if let date = inputFormatter.date(from: startDateText) {
1335
- let apiFormattedDate = outputFormatter.string(from: date)
1336
- params["start_date"] = apiFormattedDate
1337
- } else {
1338
- print("Invalid date format in startDateText")
1339
- }
1340
- }
1341
-
1342
- // interval is still required
1343
- params["interval"] = chosenPlan?.lowercased()
1344
- }
1345
-
1346
- // ✅ Include metadata only if it has at least 1 key-value pair
1347
- if let metadata = request?.metadata, !metadata.isEmpty {
1348
- params["metadata"] = metadata
1349
- }
1350
-
1351
- print(params)
1352
-
1353
- do {
1354
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1355
- uRLRequest.httpBody = jsonData
1356
- if let jsonString = String(data: jsonData, encoding: .utf8) {
1357
- print("JSON Payload: \(jsonString)")
1358
- }
1359
- } catch let error {
1360
- print("Error creating JSON data: \(error)")
1361
- hideLoadingIndicator()
1362
- return
1363
- }
1364
-
1365
- let session = URLSession.shared
1366
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
1367
-
1368
- DispatchQueue.main.async {
1369
- self.hideLoadingIndicator() // Stop loader when response is received
1370
- }
1371
-
1372
- if let error = error {
1373
- print("Error: \(error.localizedDescription)")
1374
- return
1375
- }
1376
-
1377
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1378
- print("Invalid response")
1379
- return
1380
- }
1381
-
1382
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1383
- if let data = serviceData {
1384
- do {
1385
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1386
- print("Response Data: \(responseObject)")
1387
-
1388
- // ✅ Handle duplicate transaction case
1389
- if let status = responseObject["status"] as? Bool, status == false,
1390
- let message = responseObject["message"] as? String,
1391
- message.lowercased().contains("duplicate transaction") {
1392
- self.presentPaymentErrorVC(errorMessage: message)
1393
- return
1394
- }
1395
-
1396
- if let status = responseObject["status"] as? Int, status == 0,
1397
- let message = responseObject["message"] as? String,
1398
- message.lowercased().contains("duplicate transaction") {
1399
- self.presentPaymentErrorVC(errorMessage: message)
1400
- return
1401
- }
1402
-
1403
- // ✅ Handle generic "status == 0" error case
1404
- if let status = responseObject["status"] as? Int, status == 0 {
1405
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
1406
- self.presentPaymentErrorVC(errorMessage: errorMessage)
1407
- return
1408
- }
1409
- else {
1410
- DispatchQueue.main.async {
1411
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
1412
- paymentDoneVC.chargeData = responseObject
1413
- // Pass the selected payment method
1414
- paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
1415
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate // Pass the delegate
1416
- paymentDoneVC.bankPaymentParams = params
1417
- // Pass billing and additional info
1418
- // Conditionally pass raw FieldItem array
1419
- paymentDoneVC.visibility = self.visibility
1420
- paymentDoneVC.request = self.request
1421
-
1422
- // if self.visibility?.billing == true {
1423
- paymentDoneVC.billingInfoData = self.billingInfo
1424
- var billingDict: [String: Any] = [:]
1425
- self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
1426
- paymentDoneVC.billingInfo = billingDict
1427
- // }
1428
-
1429
- // if self.visibility?.additional == true {
1430
- // Update additionalInfo values before sending
1431
- if let index = self.additionalInfo?.firstIndex(where: { $0.name == "description" }) {
1432
- self.additionalInfo?[index].value = self.txtFieldDescription.text ?? ""
1433
- }
1434
- if let index = self.additionalInfo?.firstIndex(where: { $0.name == "phone_number" }) {
1435
- self.additionalInfo?[index].value = localPhone
1436
- }
1437
-
1438
- paymentDoneVC.additionalInfoData = self.additionalInfo
1439
-
1440
- var additionalDict: [String: Any] = [:]
1441
- self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
1442
- paymentDoneVC.additionalInfo = additionalDict
1443
- // }
1444
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1445
- }
1446
- }
1447
- }
1448
- } else {
1449
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1450
- }
1451
- } catch let jsonError {
1452
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
1453
- }
1454
- } else {
1455
- self.presentPaymentErrorVC(errorMessage: "No data received")
1456
- }
1457
- } else {
1458
- if let data = serviceData,
1459
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1460
- let message = responseObj["message"] as? String {
1461
- self.presentPaymentErrorVC(errorMessage: message)
1462
- } else {
1463
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1464
- }
1465
- }
1466
- }
1467
- task.resume()
1468
- }
1469
-
1470
- //MARK: - Banking Account Charge Api from Regular saved bank account
1471
- func accountChargeSavedBankAccountApi() {
1472
- showLoadingIndicator()
1473
-
1474
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
1475
-
1476
- guard let serviceURL = URL(string: fullURL) else {
1477
- print("Invalid URL")
1478
- hideLoadingIndicator()
1479
- return
1480
- }
1481
-
1482
- var uRLRequest = URLRequest(url: serviceURL)
1483
- uRLRequest.httpMethod = "POST"
1484
- uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1485
-
1486
- let token = UserStoreSingleton.shared.clientToken
1487
- print("Setting clientToken header: \(token ?? "None")")
1488
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1489
-
1490
- // Extract only the digits from the phone number (local only, no country code)
1491
- let localPhone = txtFieldPhoneNumber.text?.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() ?? ""
1492
-
1493
- // let emailText = UserStoreSingleton.shared.verificationEmail ?? ""
1494
- // let emailPrefix = emailText.components(separatedBy: "@").first ?? ""
1495
-
1496
- let finalEmail: String
1497
- if let verificationEmail = UserStoreSingleton.shared.verificationEmail, !verificationEmail.isEmpty {
1498
- finalEmail = verificationEmail
1499
- } else {
1500
- finalEmail = request.email ?? ""
1501
- }
1502
-
1503
- let emailPrefix: String
1504
- if !finalEmail.isEmpty {
1505
- emailPrefix = finalEmail.components(separatedBy: "@").first ?? ""
1506
- } else {
1507
- emailPrefix = ""
1508
- }
1509
-
1510
- // Determine name: use request.name if available, otherwise fallback to email prefix
1511
- let nameParam: String
1512
- if let requestName = request.name, !requestName.trimmingCharacters(in: .whitespaces).isEmpty {
1513
- nameParam = requestName
1514
- } else {
1515
- nameParam = emailPrefix
1516
- }
1517
-
1518
- var params: [String: Any] = [
1519
- // "name": UserStoreSingleton.shared.merchantName ?? "",
1520
- "name": nameParam,
1521
- "account_id": accountID ?? "",
1522
- "payment_method": "ach",
1523
- "customer": customerID ?? "",
1524
- "currency": "usd",
1525
- "email": finalEmail
1526
- ]
1527
-
1528
- // Conditionally add billing info
1529
- if let visibility = visibility, visibility.billing == true,
1530
- let billing = billingInfo, !billing.isEmpty {
1531
-
1532
- var billingInfoDict: [String: Any] = [:]
1533
- for item in billing {
1534
- billingInfoDict[item.name] = item.value
1535
- }
1536
-
1537
- params["address"] = billingInfoDict["address"] as? String ?? ""
1538
- params["country"] = billingInfoDict["country"] as? String ?? ""
1539
- params["state"] = billingInfoDict["state"] as? String ?? ""
1540
- params["city"] = billingInfoDict["city"] as? String ?? ""
1541
- params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
1542
- }
1543
-
1544
- // Conditionally add additional info
1545
- if let visibility = visibility, visibility.additional == true,
1546
- let additional = additionalInfo, !additional.isEmpty {
1547
- params["description"] = txtFieldDescription.text ?? ""
1548
- params["phone_number"] = localPhone
1549
- }
1550
-
1551
- // Add these if recurring is enabled
1552
- // if let req = request, req.is_recurring == true {
1553
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
1554
- // // Only send start_date if type is .custom and field is not empty
1555
- // if let startDateText = startDate, !startDateText.isEmpty {
1556
- // let inputFormatter = DateFormatter()
1557
- // inputFormatter.dateFormat = "dd/MM/yyyy"
1558
- //
1559
- // let outputFormatter = DateFormatter()
1560
- // outputFormatter.dateFormat = "MM/dd/yyyy"
1561
- //
1562
- // if let date = inputFormatter.date(from: startDateText) {
1563
- // let apiFormattedDate = outputFormatter.string(from: date)
1564
- // params["start_date"] = apiFormattedDate
1565
- // } else {
1566
- // print("Invalid date format in startDateText")
1567
- // }
1568
- // }
1569
- // }
1570
- //
1571
- // params["interval"] = chosenPlan?.lowercased()
1572
- // }
1573
-
1574
- // Add these if recurring is enabled
1575
- if let req = request, req.is_recurring == true {
1576
- if let startDateText = startDate, !startDateText.isEmpty {
1577
- let inputFormatter = DateFormatter()
1578
- inputFormatter.dateFormat = "dd/MM/yyyy"
1579
-
1580
- let outputFormatter = DateFormatter()
1581
- outputFormatter.dateFormat = "MM/dd/yyyy"
1582
-
1583
- if let date = inputFormatter.date(from: startDateText) {
1584
- let apiFormattedDate = outputFormatter.string(from: date)
1585
- params["start_date"] = apiFormattedDate
1586
- } else {
1587
- print("Invalid date format in startDateText")
1588
- }
1589
- }
1590
-
1591
- // interval is still required
1592
- params["interval"] = chosenPlan?.lowercased()
1593
- }
1594
-
1595
- // ✅ Include metadata only if it has at least 1 key-value pair
1596
- if let metadata = request?.metadata, !metadata.isEmpty {
1597
- params["metadata"] = metadata
1598
- }
1599
-
1600
- print(params)
1601
-
1602
- do {
1603
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1604
- uRLRequest.httpBody = jsonData
1605
- if let jsonString = String(data: jsonData, encoding: .utf8) {
1606
- print("JSON Payload: \(jsonString)")
1607
- }
1608
- } catch let error {
1609
- print("Error creating JSON data: \(error)")
1610
- hideLoadingIndicator()
1611
- return
1612
- }
1613
-
1614
- let session = URLSession.shared
1615
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
1616
-
1617
- DispatchQueue.main.async {
1618
- self.hideLoadingIndicator() // Stop loader when response is received
1619
- }
1620
-
1621
- if let error = error {
1622
- print("Error: \(error.localizedDescription)")
1623
- return
1624
- }
1625
-
1626
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1627
- print("Invalid response")
1628
- return
1629
- }
1630
-
1631
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1632
- if let data = serviceData {
1633
- do {
1634
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1635
- print("Response Data: \(responseObject)")
1636
-
1637
- // ✅ Handle duplicate transaction case
1638
- if let status = responseObject["status"] as? Bool, status == false,
1639
- let message = responseObject["message"] as? String,
1640
- message.lowercased().contains("duplicate transaction") {
1641
- self.presentPaymentErrorVC(errorMessage: message)
1642
- return
1643
- }
1644
-
1645
- if let status = responseObject["status"] as? Int, status == 0,
1646
- let message = responseObject["message"] as? String,
1647
- message.lowercased().contains("duplicate transaction") {
1648
- self.presentPaymentErrorVC(errorMessage: message)
1649
- return
1650
- }
1651
-
1652
- // ✅ Handle generic "status == 0" error case
1653
- if let status = responseObject["status"] as? Int, status == 0 {
1654
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
1655
- self.presentPaymentErrorVC(errorMessage: errorMessage)
1656
- return
1657
- }
1658
- else {
1659
- DispatchQueue.main.async {
1660
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
1661
- paymentDoneVC.chargeData = responseObject
1662
- // Pass the selected payment method
1663
- paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
1664
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate // Pass the delegate
1665
- paymentDoneVC.bankPaymentParams = params
1666
- // Pass billing and additional info
1667
- // Conditionally pass raw FieldItem array
1668
- paymentDoneVC.visibility = self.visibility
1669
- paymentDoneVC.request = self.request
1670
-
1671
- // if self.visibility?.billing == true {
1672
- paymentDoneVC.billingInfoData = self.billingInfo
1673
- var billingDict: [String: Any] = [:]
1674
- self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
1675
- paymentDoneVC.billingInfo = billingDict
1676
- // }
1677
-
1678
- // if self.visibility?.additional == true {
1679
- // Update additionalInfo values before sending
1680
- if let index = self.additionalInfo?.firstIndex(where: { $0.name == "description" }) {
1681
- self.additionalInfo?[index].value = self.txtFieldDescription.text ?? ""
1682
- }
1683
- if let index = self.additionalInfo?.firstIndex(where: { $0.name == "phone_number" }) {
1684
- self.additionalInfo?[index].value = localPhone
1685
- }
1686
-
1687
- paymentDoneVC.additionalInfoData = self.additionalInfo
1688
-
1689
- var additionalDict: [String: Any] = [:]
1690
- self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
1691
- paymentDoneVC.additionalInfo = additionalDict
1692
- // }
1693
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1694
- }
1695
- }
1696
- }
1697
- } else {
1698
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1699
- }
1700
- } catch let jsonError {
1701
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
1702
- }
1703
- } else {
1704
- self.presentPaymentErrorVC(errorMessage: "No data received")
1705
- }
1706
- } else {
1707
- if let data = serviceData,
1708
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1709
- let message = responseObj["message"] as? String {
1710
- self.presentPaymentErrorVC(errorMessage: message)
1711
- } else {
1712
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1713
- }
1714
- }
1715
- }
1716
- task.resume()
1717
- }
1718
-
1719
- //MARK: - Account Charge Api if user saved the account.
1720
- func accountChargeApi(customerId: String?) {
1721
- showLoadingIndicator()
1722
-
1723
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
1724
-
1725
- guard let serviceURL = URL(string: fullURL) else {
1726
- print("Invalid URL")
1727
- hideLoadingIndicator()
1728
- return
1729
- }
1730
-
1731
- var uRLRequest = URLRequest(url: serviceURL)
1732
- uRLRequest.httpMethod = "POST"
1733
- uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1734
-
1735
- let token = UserStoreSingleton.shared.clientToken
1736
- print("Setting clientToken header: \(token ?? "None")")
1737
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1738
-
1739
- // Extract only the digits from the phone number (local only, no country code)
1740
- let localPhone = txtFieldPhoneNumber.text?.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() ?? ""
1741
-
1742
- // let emailPrefix = UserStoreSingleton.shared.verificationEmail?.components(separatedBy: "@").first ?? ""
1743
-
1744
- let finalEmail: String
1745
- if let verificationEmail = UserStoreSingleton.shared.verificationEmail, !verificationEmail.isEmpty {
1746
- finalEmail = verificationEmail
1747
- } else {
1748
- finalEmail = request.email ?? ""
1749
- }
1750
-
1751
- let emailPrefix: String
1752
- if !finalEmail.isEmpty {
1753
- emailPrefix = finalEmail.components(separatedBy: "@").first ?? ""
1754
- } else {
1755
- emailPrefix = ""
1756
- }
1757
-
1758
- var params: [String: Any] = [
1759
- // "name": accountName ?? "",
1760
- "name": !(request.name?.isEmpty ?? true) ? request.name! : (accountName ?? ""),
1761
- "email": userEmail ?? "",
1762
- "currency": "usd",
1763
- "account_type": accountType?.lowercased() ?? "",
1764
- "routing_number": routingNumber ?? "",
1765
- "account_number": accountNumber ?? "",
1766
- "payment_mode": "auth_and_capture",
1767
- "levelIndicator": 1,
1768
- "save_account": (isSavedNewAccount ?? false) ? 1 : 0,
1769
- "payment_method": "ach"
1770
- ]
1771
-
1772
- if let customerId = customerId {
1773
- params["customer"] = customerId
1774
- } else {
1775
- params["username"] = emailPrefix
1776
- }
1777
-
1778
- // Conditionally add billing info
1779
- if let visibility = visibility, visibility.billing == true,
1780
- let billing = billingInfo, !billing.isEmpty {
1781
-
1782
- var billingInfoDict: [String: Any] = [:]
1783
- for item in billing {
1784
- billingInfoDict[item.name] = item.value
1785
- }
1786
-
1787
- params["address"] = billingInfoDict["address"] as? String ?? ""
1788
- params["country"] = billingInfoDict["country"] as? String ?? ""
1789
- params["state"] = billingInfoDict["state"] as? String ?? ""
1790
- params["city"] = billingInfoDict["city"] as? String ?? ""
1791
- params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
1792
- }
1793
-
1794
- // Conditionally add additional info
1795
- if let visibility = visibility, visibility.additional == true,
1796
- let additional = additionalInfo, !additional.isEmpty {
1797
-
1798
- var additionalInfoDict: [String: Any] = [:]
1799
- for item in additional {
1800
- additionalInfoDict[item.name] = item.value
1801
- }
1802
-
1803
- params["description"] = txtFieldDescription.text ?? ""
1804
- params["phone_number"] = localPhone
1805
- }
1806
-
1807
- // Add these if recurring is enabled
1808
- // if let req = request, req.is_recurring == true {
1809
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
1810
- // // Only send start_date if type is .custom and field is not empty
1811
- // if let startDateText = startDate, !startDateText.isEmpty {
1812
- // let inputFormatter = DateFormatter()
1813
- // inputFormatter.dateFormat = "dd/MM/yyyy"
1814
- //
1815
- // let outputFormatter = DateFormatter()
1816
- // outputFormatter.dateFormat = "MM/dd/yyyy"
1817
- //
1818
- // if let date = inputFormatter.date(from: startDateText) {
1819
- // let apiFormattedDate = outputFormatter.string(from: date)
1820
- // params["start_date"] = apiFormattedDate
1821
- // } else {
1822
- // print("Invalid date format in startDateText")
1823
- // }
1824
- // }
1825
- // }
1826
- //
1827
- // params["interval"] = chosenPlan?.lowercased()
1828
- // }
1829
-
1830
- // Add these if recurring is enabled
1831
- if let req = request, req.is_recurring == true {
1832
- if let startDateText = startDate, !startDateText.isEmpty {
1833
- let inputFormatter = DateFormatter()
1834
- inputFormatter.dateFormat = "dd/MM/yyyy"
1835
-
1836
- let outputFormatter = DateFormatter()
1837
- outputFormatter.dateFormat = "MM/dd/yyyy"
1838
-
1839
- if let date = inputFormatter.date(from: startDateText) {
1840
- let apiFormattedDate = outputFormatter.string(from: date)
1841
- params["start_date"] = apiFormattedDate
1842
- } else {
1843
- print("Invalid date format in startDateText")
1844
- }
1845
- }
1846
-
1847
- // interval is still required
1848
- params["interval"] = chosenPlan?.lowercased()
1849
- }
1850
-
1851
- // ✅ Include metadata only if it has at least 1 key-value pair
1852
- if let metadata = request?.metadata, !metadata.isEmpty {
1853
- params["metadata"] = metadata
1854
- }
1855
-
1856
- print(params)
1857
-
1858
- do {
1859
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
1860
- uRLRequest.httpBody = jsonData
1861
- if let jsonString = String(data: jsonData, encoding: .utf8) {
1862
- print("JSON Payload: \(jsonString)")
1863
- }
1864
- } catch let error {
1865
- print("Error creating JSON data: \(error)")
1866
- hideLoadingIndicator()
1867
- return
1868
- }
1869
-
1870
- let session = URLSession.shared
1871
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
1872
-
1873
- DispatchQueue.main.async {
1874
- self.hideLoadingIndicator() // Stop loader when response is received
1875
- }
1876
-
1877
- if let error = error {
1878
- self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
1879
- return
1880
- }
1881
-
1882
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
1883
- self.presentPaymentErrorVC(errorMessage: "Invalid response")
1884
- return
1885
- }
1886
-
1887
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
1888
- if let data = serviceData {
1889
- do {
1890
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
1891
- print("Response Data: \(responseObject)")
1892
-
1893
- // ✅ Handle duplicate transaction case
1894
- if let status = responseObject["status"] as? Bool, status == false,
1895
- let message = responseObject["message"] as? String,
1896
- message.lowercased().contains("duplicate transaction") {
1897
- self.presentPaymentErrorVC(errorMessage: message)
1898
- return
1899
- }
1900
-
1901
- if let status = responseObject["status"] as? Int, status == 0,
1902
- let message = responseObject["message"] as? String,
1903
- message.lowercased().contains("duplicate transaction") {
1904
- self.presentPaymentErrorVC(errorMessage: message)
1905
- return
1906
- }
1907
-
1908
- // ✅ Handle generic "status == 0" error case
1909
- if let status = responseObject["status"] as? Int, status == 0 {
1910
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
1911
- self.presentPaymentErrorVC(errorMessage: errorMessage)
1912
- return
1913
- }
1914
- else {
1915
- DispatchQueue.main.async {
1916
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
1917
- paymentDoneVC.chargeData = responseObject
1918
- paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
1919
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate
1920
- paymentDoneVC.bankPaymentParams = params
1921
- // Pass billing and additional info
1922
- // Conditionally pass raw FieldItem array
1923
- paymentDoneVC.visibility = self.visibility
1924
- paymentDoneVC.request = self.request
1925
-
1926
- // if self.visibility?.billing == true {
1927
- paymentDoneVC.billingInfoData = self.billingInfo
1928
- var billingDict: [String: Any] = [:]
1929
- self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
1930
- paymentDoneVC.billingInfo = billingDict
1931
- // }
1932
-
1933
- // if self.visibility?.additional == true {
1934
- // Update additionalInfo values before sending
1935
- if let index = self.additionalInfo?.firstIndex(where: { $0.name == "description" }) {
1936
- self.additionalInfo?[index].value = self.txtFieldDescription.text ?? ""
1937
- }
1938
- if let index = self.additionalInfo?.firstIndex(where: { $0.name == "phone_number" }) {
1939
- self.additionalInfo?[index].value = localPhone
1940
- }
1941
-
1942
- paymentDoneVC.additionalInfoData = self.additionalInfo
1943
-
1944
- var additionalDict: [String: Any] = [:]
1945
- self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
1946
- paymentDoneVC.additionalInfo = additionalDict
1947
- // }
1948
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
1949
- }
1950
- }
1951
- }
1952
- } else {
1953
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
1954
- }
1955
- } catch let jsonError {
1956
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
1957
- }
1958
- } else {
1959
- self.presentPaymentErrorVC(errorMessage: "No data received")
1960
- }
1961
- } else {
1962
- if let data = serviceData,
1963
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
1964
- let message = responseObj["message"] as? String {
1965
- self.presentPaymentErrorVC(errorMessage: message)
1966
- } else {
1967
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
1968
- }
1969
- }
1970
- }
1971
- task.resume()
1972
- }
1973
-
1974
- // MARK: - 3DS Functionality
1975
-
1976
- // MARK: - Credit Card Charge Api If Billing info is not nil and Without Login.
1977
- func threeDSecurePaymentApi() {
1978
- showLoadingIndicator()
1979
-
1980
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.threeDSecure.path()
1981
-
1982
- guard let serviceURL = URL(string: fullURL) else {
1983
- print("Invalid URL")
1984
- hideLoadingIndicator()
1985
- return
1986
- }
1987
-
1988
- var uRLRequest = URLRequest(url: serviceURL)
1989
- uRLRequest.httpMethod = "POST"
1990
- uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
1991
-
1992
- let token = UserStoreSingleton.shared.clientToken
1993
- print("Setting clientToken header: \(token ?? "None")")
1994
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
1995
-
1996
- // Extract only the digits from the phone number (local only, no country code)
1997
- let localPhone = txtFieldPhoneNumber.text?.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() ?? ""
1998
-
1999
- var params: [String: Any] = [
2000
- "name": nameOnCard ?? "",
2001
- "email": userEmail ?? "",
2002
- "card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
2003
- "cardholder_name": nameOnCard ?? "",
2004
- "exp_month": expiryDate?.components(separatedBy: "/").first ?? "",
2005
- "exp_year": expiryDate?.components(separatedBy: "/").last ?? "",
2006
- "cvc": cvv ?? "",
2007
- "currency": "usd",
2008
- "tokenize": request.tokenOnly ?? false
2009
- ]
2010
-
2011
- // ✅ Only for logged-in users
2012
- if UserStoreSingleton.shared.isLoggedIn == true {
2013
- let emailText = userEmail
2014
- let emailPrefix = emailText?.components(separatedBy: "@").first ?? ""
2015
-
2016
- params["save_card"] = isSavedNewCard ? 1 : 0
2017
- if isSavedNewCard {
2018
- params["is_default"] = "1"
2019
- }
2020
- params["tokenize"] = request.tokenOnly ?? ""
2021
- params["username"] = emailPrefix
2022
-
2023
- if let customerId = UserStoreSingleton.shared.customerId {
2024
- params["customer"] = customerId
2025
- params["customer_id"] = customerId
2026
- } else {
2027
- params["create_customer"] = "1"
2028
- }
2029
-
2030
- if UserStoreSingleton.shared.customerId == nil {
2031
- params["create_customer"] = "1"
2032
- }
2033
- }
2034
-
2035
- // Conditionally add billing info
2036
- if let visibility = visibility, visibility.billing == true,
2037
- let billing = billingInfo, !billing.isEmpty {
2038
-
2039
- var billingInfoDict: [String: Any] = [:]
2040
- for item in billing {
2041
- billingInfoDict[item.name] = item.value
2042
- }
2043
-
2044
- params["address"] = billingInfoDict["address"] as? String ?? ""
2045
- params["country"] = billingInfoDict["country"] as? String ?? ""
2046
- params["state"] = billingInfoDict["state"] as? String ?? ""
2047
- params["city"] = billingInfoDict["city"] as? String ?? ""
2048
- params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
2049
- }
2050
-
2051
- // Conditionally add additional info
2052
- if let visibility = visibility, visibility.additional == true,
2053
- let additional = additionalInfo, !additional.isEmpty {
2054
- params["description"] = txtFieldDescription.text ?? ""
2055
- params["phone_number"] = localPhone
2056
- }
2057
-
2058
- // Add these if recurring is enabled
2059
- // if let req = request, req.is_recurring == true {
2060
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
2061
- // // Only send start_date if type is .custom and field is not empty
2062
- // if let startDateText = startDate, !startDateText.isEmpty {
2063
- // let inputFormatter = DateFormatter()
2064
- // inputFormatter.dateFormat = "dd/MM/yyyy"
2065
- //
2066
- // let outputFormatter = DateFormatter()
2067
- // outputFormatter.dateFormat = "MM/dd/yyyy"
2068
- //
2069
- // if let date = inputFormatter.date(from: startDateText) {
2070
- // let apiFormattedDate = outputFormatter.string(from: date)
2071
- // params["start_date"] = apiFormattedDate
2072
- // } else {
2073
- // print("Invalid date format in startDateText")
2074
- // }
2075
- // }
2076
- // }
2077
- //
2078
- // params["interval"] = chosenPlan?.lowercased()
2079
- // }
2080
-
2081
- // Add these if recurring is enabled
2082
- if let req = request, req.is_recurring == true {
2083
- if let startDateText = startDate, !startDateText.isEmpty {
2084
- let inputFormatter = DateFormatter()
2085
- inputFormatter.dateFormat = "dd/MM/yyyy"
2086
-
2087
- let outputFormatter = DateFormatter()
2088
- outputFormatter.dateFormat = "MM/dd/yyyy"
2089
-
2090
- if let date = inputFormatter.date(from: startDateText) {
2091
- let apiFormattedDate = outputFormatter.string(from: date)
2092
- params["start_date"] = apiFormattedDate
2093
- } else {
2094
- print("Invalid date format in startDateText")
2095
- }
2096
- }
2097
-
2098
- // interval is still required
2099
- params["interval"] = chosenPlan?.lowercased()
2100
- }
2101
-
2102
- // ✅ Include metadata only if it has at least 1 key-value pair
2103
- if let metadata = request?.metadata, !metadata.isEmpty {
2104
- params["metadata"] = metadata
2105
- }
2106
-
2107
- print(params)
2108
-
2109
- do {
2110
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2111
- uRLRequest.httpBody = jsonData
2112
- if let jsonString = String(data: jsonData, encoding: .utf8) {
2113
- print("JSON Payload: \(jsonString)")
2114
- }
2115
- } catch let error {
2116
- print("Error creating JSON data: \(error)")
2117
- hideLoadingIndicator()
2118
- return
2119
- }
2120
-
2121
- let session = URLSession.shared
2122
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
2123
-
2124
- DispatchQueue.main.async {
2125
- self.hideLoadingIndicator() // Stop loader when response is received
2126
- }
2127
-
2128
- if let error = error {
2129
- print("Error: \(error.localizedDescription)")
2130
- return
2131
- }
2132
-
2133
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
2134
- print("Invalid response")
2135
- return
2136
- }
2137
-
2138
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
2139
- if let data = serviceData {
2140
- do {
2141
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
2142
- print("Response Data: \(responseObject)")
2143
-
2144
- // ✅ Handle duplicate transaction case
2145
- if let status = responseObject["status"] as? Bool, status == false,
2146
- let message = responseObject["message"] as? String,
2147
- message.lowercased().contains("duplicate transaction") {
2148
- self.presentPaymentErrorVC(errorMessage: message)
2149
- return
2150
- }
2151
-
2152
- if let status = responseObject["status"] as? Int, status == 0,
2153
- let message = responseObject["message"] as? String,
2154
- message.lowercased().contains("duplicate transaction") {
2155
- self.presentPaymentErrorVC(errorMessage: message)
2156
- return
2157
- }
2158
-
2159
- // ✅ Handle generic "status == 0" error case
2160
- if let status = responseObject["status"] as? Int, status == 0 {
2161
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
2162
- self.presentPaymentErrorVC(errorMessage: errorMessage)
2163
- return
2164
- }
2165
- else {
2166
- DispatchQueue.main.async {
2167
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "ThreeDSecurePaymentDoneVC") as? ThreeDSecurePaymentDoneVC {
2168
-
2169
- let urlString = responseObject["redirect_url"] as? String ?? responseObject["location_url"] as? String ?? ""
2170
- paymentDoneVC.redirectURL = urlString
2171
- paymentDoneVC.chargeData = responseObject
2172
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate
2173
- // Pass billing and additional info
2174
- // Conditionally pass raw FieldItem array
2175
- paymentDoneVC.visibility = self.visibility
2176
- paymentDoneVC.amount = self.amount
2177
- paymentDoneVC.cardApiParams = params
2178
- paymentDoneVC.request = self.request
2179
-
2180
- // if self.visibility?.billing == true {
2181
- paymentDoneVC.billingInfoData = self.billingInfo
2182
- var billingDict: [String: Any] = [:]
2183
- self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
2184
- paymentDoneVC.billingInfo = billingDict
2185
- // }
2186
-
2187
- // if self.visibility?.additional == true {
2188
- // Update additionalInfo values before sending
2189
- if let index = self.additionalInfo?.firstIndex(where: { $0.name == "description" }) {
2190
- self.additionalInfo?[index].value = self.txtFieldDescription.text ?? ""
2191
- }
2192
- if let index = self.additionalInfo?.firstIndex(where: { $0.name == "phone_number" }) {
2193
- self.additionalInfo?[index].value = localPhone
2194
- }
2195
-
2196
- paymentDoneVC.additionalInfoData = self.additionalInfo
2197
-
2198
- var additionalDict: [String: Any] = [:]
2199
- self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
2200
- paymentDoneVC.additionalInfo = additionalDict
2201
- // }
2202
-
2203
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
2204
- }
2205
- }
2206
- }
2207
- } else {
2208
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
2209
- }
2210
- } catch let jsonError {
2211
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
2212
- }
2213
- } else {
2214
- self.presentPaymentErrorVC(errorMessage: "No data received")
2215
- }
2216
- } else {
2217
- if let data = serviceData,
2218
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
2219
- let message = responseObj["message"] as? String {
2220
- self.presentPaymentErrorVC(errorMessage: message)
2221
- } else {
2222
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
2223
- }
2224
- }
2225
- }
2226
- task.resume()
2227
- }
2228
-
2229
- // MARK: - Credit Card Charge Api If Billing info is not nil With Login from Add New Card.
2230
- func threeDSecurePaymentAddNewCardApi(customerId: String?) {
2231
- showLoadingIndicator()
2232
-
2233
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.threeDSecure.path()
2234
-
2235
- guard let serviceURL = URL(string: fullURL) else {
2236
- print("Invalid URL")
2237
- hideLoadingIndicator()
2238
- return
2239
- }
2240
-
2241
- var uRLRequest = URLRequest(url: serviceURL)
2242
- uRLRequest.httpMethod = "POST"
2243
- uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
2244
-
2245
- let token = UserStoreSingleton.shared.clientToken
2246
- print("Setting clientToken header: \(token ?? "None")")
2247
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2248
-
2249
- // Extract only the digits from the phone number (local only, no country code)
2250
- let localPhone = txtFieldPhoneNumber.text?.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() ?? ""
2251
-
2252
- var params: [String: Any] = [
2253
- "name": nameOnCard ?? "",
2254
- "email": userEmail ?? "",
2255
- "card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
2256
- "cardholder_name": nameOnCard ?? "",
2257
- "exp_month": expiryDate?.components(separatedBy: "/").first ?? "",
2258
- "exp_year": expiryDate?.components(separatedBy: "/").last ?? "",
2259
- "cvc": cvv ?? "",
2260
- "description": "Test",
2261
- "currency": "usd",
2262
- "tokenize": request.tokenOnly ?? false,
2263
- "save_card": isSavedNewCard ? 1 : 0,
2264
- "customer_id": customerId ?? ""
2265
- ]
2266
-
2267
- // Add is_default parameter if save_card is 1
2268
- if isSavedNewCard {
2269
- params["is_default"] = "1"
2270
- }
2271
-
2272
- // Conditionally add billing info
2273
- if let visibility = visibility, visibility.billing == true,
2274
- let billing = billingInfo, !billing.isEmpty {
2275
-
2276
- var billingInfoDict: [String: Any] = [:]
2277
- for item in billing {
2278
- billingInfoDict[item.name] = item.value
2279
- }
2280
-
2281
- params["address"] = billingInfoDict["address"] as? String ?? ""
2282
- params["country"] = billingInfoDict["country"] as? String ?? ""
2283
- params["state"] = billingInfoDict["state"] as? String ?? ""
2284
- params["city"] = billingInfoDict["city"] as? String ?? ""
2285
- params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
2286
- }
2287
-
2288
- // Conditionally add additional info
2289
- if let visibility = visibility, visibility.additional == true,
2290
- let additional = additionalInfo, !additional.isEmpty {
2291
- params["description"] = txtFieldDescription.text ?? ""
2292
- params["phone_number"] = localPhone
2293
- }
2294
-
2295
- // Add these if recurring is enabled
2296
- // if let req = request, req.is_recurring == true {
2297
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
2298
- // // Only send start_date if type is .custom and field is not empty
2299
- // if let startDateText = startDate, !startDateText.isEmpty {
2300
- // let inputFormatter = DateFormatter()
2301
- // inputFormatter.dateFormat = "dd/MM/yyyy"
2302
- //
2303
- // let outputFormatter = DateFormatter()
2304
- // outputFormatter.dateFormat = "MM/dd/yyyy"
2305
- //
2306
- // if let date = inputFormatter.date(from: startDateText) {
2307
- // let apiFormattedDate = outputFormatter.string(from: date)
2308
- // params["start_date"] = apiFormattedDate
2309
- // } else {
2310
- // print("Invalid date format in startDateText")
2311
- // }
2312
- // }
2313
- // }
2314
- //
2315
- // params["interval"] = chosenPlan?.lowercased()
2316
- // }
2317
-
2318
- // Add these if recurring is enabled
2319
- if let req = request, req.is_recurring == true {
2320
- if let startDateText = startDate, !startDateText.isEmpty {
2321
- let inputFormatter = DateFormatter()
2322
- inputFormatter.dateFormat = "dd/MM/yyyy"
2323
-
2324
- let outputFormatter = DateFormatter()
2325
- outputFormatter.dateFormat = "MM/dd/yyyy"
2326
-
2327
- if let date = inputFormatter.date(from: startDateText) {
2328
- let apiFormattedDate = outputFormatter.string(from: date)
2329
- params["start_date"] = apiFormattedDate
2330
- } else {
2331
- print("Invalid date format in startDateText")
2332
- }
2333
- }
2334
-
2335
- // interval is still required
2336
- params["interval"] = chosenPlan?.lowercased()
2337
- }
2338
-
2339
- // ✅ Include metadata only if it has at least 1 key-value pair
2340
- if let metadata = request?.metadata, !metadata.isEmpty {
2341
- params["metadata"] = metadata
2342
- }
2343
-
2344
- print(params)
2345
-
2346
- do {
2347
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2348
- uRLRequest.httpBody = jsonData
2349
- if let jsonString = String(data: jsonData, encoding: .utf8) {
2350
- print("JSON Payload: \(jsonString)")
2351
- }
2352
- } catch let error {
2353
- print("Error creating JSON data: \(error)")
2354
- hideLoadingIndicator()
2355
- return
2356
- }
2357
-
2358
- let session = URLSession.shared
2359
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
2360
-
2361
- DispatchQueue.main.async {
2362
- self.hideLoadingIndicator() // Stop loader when response is received
2363
- }
2364
-
2365
- if let error = error {
2366
- print("Error: \(error.localizedDescription)")
2367
- return
2368
- }
2369
-
2370
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
2371
- print("Invalid response")
2372
- return
2373
- }
2374
-
2375
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
2376
- if let data = serviceData {
2377
- do {
2378
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
2379
- print("Response Data: \(responseObject)")
2380
-
2381
- // ✅ Handle duplicate transaction case
2382
- if let status = responseObject["status"] as? Bool, status == false,
2383
- let message = responseObject["message"] as? String,
2384
- message.lowercased().contains("duplicate transaction") {
2385
- self.presentPaymentErrorVC(errorMessage: message)
2386
- return
2387
- }
2388
-
2389
- if let status = responseObject["status"] as? Int, status == 0,
2390
- let message = responseObject["message"] as? String,
2391
- message.lowercased().contains("duplicate transaction") {
2392
- self.presentPaymentErrorVC(errorMessage: message)
2393
- return
2394
- }
2395
-
2396
- // ✅ Handle generic "status == 0" error case
2397
- if let status = responseObject["status"] as? Int, status == 0 {
2398
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
2399
- self.presentPaymentErrorVC(errorMessage: errorMessage)
2400
- return
2401
- }
2402
- else {
2403
- DispatchQueue.main.async {
2404
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "ThreeDSecurePaymentDoneVC") as? ThreeDSecurePaymentDoneVC {
2405
-
2406
- let urlString = responseObject["redirect_url"] as? String ?? responseObject["location_url"] as? String ?? ""
2407
- paymentDoneVC.redirectURL = urlString
2408
- paymentDoneVC.chargeData = responseObject
2409
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate
2410
- // Pass billing and additional info
2411
- // Conditionally pass raw FieldItem array
2412
- paymentDoneVC.visibility = self.visibility
2413
- paymentDoneVC.amount = self.amount
2414
- paymentDoneVC.cardApiParams = params
2415
- paymentDoneVC.request = self.request
2416
-
2417
- // if self.visibility?.billing == true {
2418
- paymentDoneVC.billingInfoData = self.billingInfo
2419
- var billingDict: [String: Any] = [:]
2420
- self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
2421
- paymentDoneVC.billingInfo = billingDict
2422
- // }
2423
-
2424
- // if self.visibility?.additional == true {
2425
- // Update additionalInfo values before sending
2426
- if let index = self.additionalInfo?.firstIndex(where: { $0.name == "description" }) {
2427
- self.additionalInfo?[index].value = self.txtFieldDescription.text ?? ""
2428
- }
2429
- if let index = self.additionalInfo?.firstIndex(where: { $0.name == "phone_number" }) {
2430
- self.additionalInfo?[index].value = localPhone
2431
- }
2432
-
2433
- paymentDoneVC.additionalInfoData = self.additionalInfo
2434
-
2435
- var additionalDict: [String: Any] = [:]
2436
- self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
2437
- paymentDoneVC.additionalInfo = additionalDict
2438
- // }
2439
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
2440
- }
2441
- }
2442
- }
2443
- } else {
2444
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
2445
- }
2446
- } catch let jsonError {
2447
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
2448
- }
2449
- } else {
2450
- self.presentPaymentErrorVC(errorMessage: "No data received")
2451
- }
2452
- } else {
2453
- if let data = serviceData,
2454
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
2455
- let message = responseObj["message"] as? String {
2456
- self.presentPaymentErrorVC(errorMessage: message)
2457
- } else {
2458
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
2459
- }
2460
- }
2461
- }
2462
- task.resume()
2463
- }
2464
-
2465
- //MARK: - GrailPay Account Charge Api if user not saved account but billing info available
2466
- func grailPayAccountChargeApi() {
2467
- showLoadingIndicator()
2468
-
2469
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
2470
-
2471
- guard let serviceURL = URL(string: fullURL) else {
2472
- print("Invalid URL")
2473
- hideLoadingIndicator()
2474
- return
2475
- }
2476
-
2477
- var uRLRequest = URLRequest(url: serviceURL)
2478
- uRLRequest.httpMethod = "POST"
2479
- uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
2480
-
2481
- let token = UserStoreSingleton.shared.clientToken
2482
- print("Setting clientToken header: \(token ?? "None")")
2483
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2484
-
2485
- // Extract only the digits from the phone number (local only, no country code)
2486
- let localPhone = txtFieldPhoneNumber.text?.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() ?? ""
2487
-
2488
- var params: [String: Any] = [
2489
- "account_id": self.grailPayAccountID ?? "",
2490
- "account_type": self.selectedGrailPayAccountType ?? "",
2491
- "name": self.selectedGrailPayAccountName ?? "",
2492
- "description": "payment checkout",
2493
- "email": userEmail ?? ""
2494
- ]
2495
-
2496
- // ✅ Only add these params for logged-in users
2497
- if UserStoreSingleton.shared.isLoggedIn == true {
2498
- let emailText = userEmail
2499
- let emailPrefix = emailText?.components(separatedBy: "@").first ?? ""
2500
-
2501
- params["save_account"] = (isSavedNewAccount ?? false) ? 1 : 0
2502
- params["is_default"] = 1
2503
- params["customer_id"] = UserStoreSingleton.shared.customerId ?? ""
2504
-
2505
- if let customerId = UserStoreSingleton.shared.customerId, !customerId.isEmpty {
2506
- params["customer"] = customerId
2507
- } else {
2508
- params["username"] = emailPrefix
2509
- }
2510
-
2511
- if UserStoreSingleton.shared.customerId == nil {
2512
- params["create_customer"] = "1"
2513
- }
2514
- }
2515
-
2516
- // Conditionally add billing info
2517
- if let visibility = visibility, visibility.billing == true,
2518
- let billing = billingInfo, !billing.isEmpty {
2519
-
2520
- var billingInfoDict: [String: Any] = [:]
2521
- for item in billing {
2522
- billingInfoDict[item.name] = item.value
2523
- }
2524
-
2525
- params["address"] = billingInfoDict["address"] as? String ?? ""
2526
- params["country"] = billingInfoDict["country"] as? String ?? ""
2527
- params["state"] = billingInfoDict["state"] as? String ?? ""
2528
- params["city"] = billingInfoDict["city"] as? String ?? ""
2529
- params["zip"] = billingInfoDict["postal_code"] as? String ?? ""
2530
- }
2531
-
2532
- // Additional Info or default description
2533
- var descriptionValue: String = "Hosted payment checkout" // default
2534
- if let visibility = visibility, visibility.additional == true,
2535
- let additional = additionalInfo, !additional.isEmpty {
2536
-
2537
- var additionalDict: [String: Any] = [:]
2538
- additional.forEach { additionalDict[$0.name] = $0.value }
2539
-
2540
- if let desc = additionalDict["description"] as? String, !desc.isEmpty {
2541
- descriptionValue = desc
2542
- }
2543
-
2544
- if let phone = additionalDict["phone_number"] as? String, !phone.isEmpty {
2545
- params["phone_number"] = phone
2546
- }
2547
- }
2548
- params["description"] = descriptionValue
2549
-
2550
- // Add these if recurring is enabled
2551
- // if let req = request, req.is_recurring == true {
2552
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
2553
- // // Only send start_date if type is .custom and field is not empty
2554
- // if let startDateText = startDate, !startDateText.isEmpty {
2555
- // let inputFormatter = DateFormatter()
2556
- // inputFormatter.dateFormat = "dd/MM/yyyy"
2557
- //
2558
- // let outputFormatter = DateFormatter()
2559
- // outputFormatter.dateFormat = "MM/dd/yyyy"
2560
- //
2561
- // if let date = inputFormatter.date(from: startDateText) {
2562
- // let apiFormattedDate = outputFormatter.string(from: date)
2563
- // params["start_date"] = apiFormattedDate
2564
- // } else {
2565
- // print("Invalid date format in startDateText")
2566
- // }
2567
- // }
2568
- // }
2569
- //
2570
- // params["interval"] = chosenPlan?.lowercased()
2571
- // }
2572
-
2573
- // Add these if recurring is enabled
2574
- if let req = request, req.is_recurring == true {
2575
- if let startDateText = startDate, !startDateText.isEmpty {
2576
- let inputFormatter = DateFormatter()
2577
- inputFormatter.dateFormat = "dd/MM/yyyy"
2578
-
2579
- let outputFormatter = DateFormatter()
2580
- outputFormatter.dateFormat = "MM/dd/yyyy"
2581
-
2582
- if let date = inputFormatter.date(from: startDateText) {
2583
- let apiFormattedDate = outputFormatter.string(from: date)
2584
- params["start_date"] = apiFormattedDate
2585
- } else {
2586
- print("Invalid date format in startDateText")
2587
- }
2588
- }
2589
-
2590
- // interval is still required
2591
- params["interval"] = chosenPlan?.lowercased()
2592
- }
2593
-
2594
- // ✅ Include metadata only if it has at least 1 key-value pair
2595
- if let metadata = request?.metadata, !metadata.isEmpty {
2596
- params["metadata"] = metadata
2597
- }
2598
-
2599
- print(params)
2600
-
2601
- do {
2602
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2603
- uRLRequest.httpBody = jsonData
2604
- if let jsonString = String(data: jsonData, encoding: .utf8) {
2605
- print("JSON Payload: \(jsonString)")
2606
- }
2607
- } catch let error {
2608
- print("Error creating JSON data: \(error)")
2609
- hideLoadingIndicator()
2610
- return
2611
- }
2612
-
2613
- let session = URLSession.shared
2614
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
2615
-
2616
- DispatchQueue.main.async {
2617
- self.hideLoadingIndicator() // Stop loader when response is received
2618
- }
2619
-
2620
- if let error = error {
2621
- self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
2622
- return
2623
- }
2624
-
2625
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
2626
- self.presentPaymentErrorVC(errorMessage: "Invalid response")
2627
- return
2628
- }
2629
-
2630
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
2631
- if let data = serviceData {
2632
- do {
2633
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
2634
- print("Response Data: \(responseObject)")
2635
-
2636
- // ✅ Handle duplicate transaction case
2637
- if let status = responseObject["status"] as? Bool, status == false,
2638
- let message = responseObject["message"] as? String,
2639
- message.lowercased().contains("duplicate transaction") {
2640
- self.presentPaymentErrorVC(errorMessage: message)
2641
- return
2642
- }
2643
-
2644
- if let status = responseObject["status"] as? Int, status == 0,
2645
- let message = responseObject["message"] as? String,
2646
- message.lowercased().contains("duplicate transaction") {
2647
- self.presentPaymentErrorVC(errorMessage: message)
2648
- return
2649
- }
2650
-
2651
- // ✅ Handle generic "status == 0" error case
2652
- if let status = responseObject["status"] as? Int, status == 0 {
2653
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
2654
- self.presentPaymentErrorVC(errorMessage: errorMessage)
2655
- return
2656
- }
2657
- else {
2658
- DispatchQueue.main.async {
2659
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
2660
- paymentDoneVC.chargeData = responseObject
2661
- paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
2662
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate
2663
- paymentDoneVC.bankPaymentParams = params
2664
- // Pass billing and additional info
2665
- // Conditionally pass raw FieldItem array
2666
- paymentDoneVC.visibility = self.visibility
2667
- paymentDoneVC.request = self.request
2668
-
2669
- // if self.visibility?.billing == true {
2670
- paymentDoneVC.billingInfoData = self.billingInfo
2671
- var billingDict: [String: Any] = [:]
2672
- self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
2673
- paymentDoneVC.billingInfo = billingDict
2674
- // }
2675
-
2676
- // if self.visibility?.additional == true {
2677
- // Update additionalInfo values before sending
2678
- if let index = self.additionalInfo?.firstIndex(where: { $0.name == "description" }) {
2679
- self.additionalInfo?[index].value = self.txtFieldDescription.text ?? ""
2680
- }
2681
- if let index = self.additionalInfo?.firstIndex(where: { $0.name == "phone_number" }) {
2682
- self.additionalInfo?[index].value = localPhone
2683
- }
2684
-
2685
- paymentDoneVC.additionalInfoData = self.additionalInfo
2686
-
2687
- var additionalDict: [String: Any] = [:]
2688
- self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
2689
- paymentDoneVC.additionalInfo = additionalDict
2690
- // }
2691
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
2692
- }
2693
- }
2694
- }
2695
- } else {
2696
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
2697
- }
2698
- } catch let jsonError {
2699
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
2700
- }
2701
- } else {
2702
- self.presentPaymentErrorVC(errorMessage: "No data received")
2703
- }
2704
- } else {
2705
- if let data = serviceData,
2706
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
2707
- let message = responseObj["message"] as? String {
2708
- self.presentPaymentErrorVC(errorMessage: message)
2709
- } else {
2710
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
2711
- }
2712
- }
2713
- }
2714
- task.resume()
2715
- }
2716
-
2717
- //MARK: - GrailPay Account Charge Api if user saved account
2718
- func grailPayAccountChargeApi(customerId: String?) {
2719
- showLoadingIndicator()
2720
-
2721
- let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
2722
-
2723
- guard let serviceURL = URL(string: fullURL) else {
2724
- print("Invalid URL")
2725
- hideLoadingIndicator()
2726
- return
2727
- }
2728
-
2729
- var uRLRequest = URLRequest(url: serviceURL)
2730
- uRLRequest.httpMethod = "POST"
2731
- uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
2732
-
2733
- let token = UserStoreSingleton.shared.clientToken
2734
- print("Setting clientToken header: \(token ?? "None")")
2735
- uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
2736
-
2737
- let emailPrefix = userEmail?.components(separatedBy: "@").first ?? ""
2738
-
2739
- var params: [String: Any] = [
2740
- "account_id": self.grailPayAccountID ?? "",
2741
- "account_type": self.selectedGrailPayAccountType ?? "",
2742
- "name": self.selectedGrailPayAccountName ?? "",
2743
- "save_account": (isSavedForFuture ?? false) ? 1 : 0,
2744
- "is_default": (isSavedForFuture ?? false) ? 1 : 0,
2745
- "customer_id": customerId ?? "",
2746
- "email": userEmail ?? "",
2747
- "create_customer": "1",
2748
- ]
2749
-
2750
- if let customerId = customerId {
2751
- params["customer"] = customerId
2752
- } else {
2753
- params["username"] = emailPrefix
2754
- }
2755
-
2756
- // // Billing Info
2757
- // if let visibility = visibility, visibility.billing == true,
2758
- // let billing = billingInfo, !billing.isEmpty {
2759
- // var billingDict: [String: Any] = [:]
2760
- // billing.forEach { billingDict[$0.name] = $0.value }
2761
- //
2762
- // params["address"] = billingDict["address"] as? String ?? ""
2763
- // params["country"] = billingDict["country"] as? String ?? ""
2764
- // params["state"] = billingDict["state"] as? String ?? ""
2765
- // params["city"] = billingDict["city"] as? String ?? ""
2766
- // params["zip"] = billingDict["postal_code"] as? String ?? ""
2767
- // }
2768
-
2769
- // Always include Billing Info if available
2770
- if let billing = billingInfo, !billing.isEmpty {
2771
- var billingDict: [String: Any] = [:]
2772
- billing.forEach { billingDict[$0.name] = $0.value }
2773
-
2774
- params["address"] = billingDict["address"] as? String ?? ""
2775
- params["country"] = billingDict["country"] as? String ?? ""
2776
- params["state"] = billingDict["state"] as? String ?? ""
2777
- params["city"] = billingDict["city"] as? String ?? ""
2778
- params["zip"] = billingDict["postal_code"] as? String ?? ""
2779
- }
2780
-
2781
- // // Additional Info or default description
2782
- // var descriptionValue: String = "Hosted payment checkout" // default
2783
- // if let visibility = visibility, visibility.additional == true,
2784
- // let additional = additionalInfo, !additional.isEmpty {
2785
- //
2786
- // var additionalDict: [String: Any] = [:]
2787
- // additional.forEach { additionalDict[$0.name] = $0.value }
2788
- //
2789
- // if let desc = additionalDict["description"] as? String, !desc.isEmpty {
2790
- // descriptionValue = desc
2791
- // }
2792
- //
2793
- // if let phone = additionalDict["phone_number"] as? String, !phone.isEmpty {
2794
- // params["phone_number"] = phone
2795
- // }
2796
- // }
2797
- // params["description"] = descriptionValue
2798
-
2799
- // Always include Additional Info if available
2800
- var descriptionValue: String = "Hosted payment checkout"
2801
- if let additional = additionalInfo, !additional.isEmpty {
2802
- var additionalDict: [String: Any] = [:]
2803
- additional.forEach { additionalDict[$0.name] = $0.value }
2804
-
2805
- if let desc = additionalDict["description"] as? String, !desc.isEmpty {
2806
- descriptionValue = desc
2807
- }
2808
-
2809
- if let phone = additionalDict["phone_number"] as? String, !phone.isEmpty {
2810
- params["phone_number"] = phone
2811
- }
2812
- }
2813
- params["description"] = descriptionValue
2814
-
2815
- // Add these if recurring is enabled
2816
- // if let req = request, req.is_recurring == true {
2817
- // if let recurringType = req.recurringStartDateType, recurringType == .custom {
2818
- // // Only send start_date if type is .custom and field is not empty
2819
- // if let startDateText = startDate, !startDateText.isEmpty {
2820
- // let inputFormatter = DateFormatter()
2821
- // inputFormatter.dateFormat = "dd/MM/yyyy"
2822
- //
2823
- // let outputFormatter = DateFormatter()
2824
- // outputFormatter.dateFormat = "MM/dd/yyyy"
2825
- //
2826
- // if let date = inputFormatter.date(from: startDateText) {
2827
- // let apiFormattedDate = outputFormatter.string(from: date)
2828
- // params["start_date"] = apiFormattedDate
2829
- // } else {
2830
- // print("Invalid date format in startDateText")
2831
- // }
2832
- // }
2833
- // }
2834
- //
2835
- // params["interval"] = chosenPlan?.lowercased()
2836
- // }
2837
-
2838
- // Add these if recurring is enabled
2839
- if let req = request, req.is_recurring == true {
2840
- if let startDateText = startDate, !startDateText.isEmpty {
2841
- let inputFormatter = DateFormatter()
2842
- inputFormatter.dateFormat = "dd/MM/yyyy"
2843
-
2844
- let outputFormatter = DateFormatter()
2845
- outputFormatter.dateFormat = "MM/dd/yyyy"
2846
-
2847
- if let date = inputFormatter.date(from: startDateText) {
2848
- let apiFormattedDate = outputFormatter.string(from: date)
2849
- params["start_date"] = apiFormattedDate
2850
- } else {
2851
- print("Invalid date format in startDateText")
2852
- }
2853
- }
2854
-
2855
- // interval is still required
2856
- params["interval"] = chosenPlan?.lowercased()
2857
- }
2858
-
2859
- // ✅ Include metadata only if it has at least 1 key-value pair
2860
- if let metadata = request?.metadata, !metadata.isEmpty {
2861
- params["metadata"] = metadata
2862
- }
2863
-
2864
- print(params)
2865
-
2866
- do {
2867
- let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
2868
- uRLRequest.httpBody = jsonData
2869
- if let jsonString = String(data: jsonData, encoding: .utf8) {
2870
- print("JSON Payload: \(jsonString)")
2871
- }
2872
- } catch let error {
2873
- print("Error creating JSON data: \(error)")
2874
- hideLoadingIndicator()
2875
- return
2876
- }
2877
-
2878
- let session = URLSession.shared
2879
- let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
2880
-
2881
- DispatchQueue.main.async {
2882
- self.hideLoadingIndicator() // Stop loader when response is received
2883
- }
2884
-
2885
- if let error = error {
2886
- self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
2887
- return
2888
- }
2889
-
2890
- guard let httpResponse = serviceResponse as? HTTPURLResponse else {
2891
- self.presentPaymentErrorVC(errorMessage: "Invalid response")
2892
- return
2893
- }
2894
-
2895
- if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
2896
- if let data = serviceData {
2897
- do {
2898
- if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
2899
- print("Response Data: \(responseObject)")
2900
-
2901
- // ✅ Handle duplicate transaction case
2902
- if let status = responseObject["status"] as? Bool, status == false,
2903
- let message = responseObject["message"] as? String,
2904
- message.lowercased().contains("duplicate transaction") {
2905
- self.presentPaymentErrorVC(errorMessage: message)
2906
- return
2907
- }
2908
-
2909
- if let status = responseObject["status"] as? Int, status == 0,
2910
- let message = responseObject["message"] as? String,
2911
- message.lowercased().contains("duplicate transaction") {
2912
- self.presentPaymentErrorVC(errorMessage: message)
2913
- return
2914
- }
2915
-
2916
- // ✅ Handle generic "status == 0" error case
2917
- if let status = responseObject["status"] as? Int, status == 0 {
2918
- let errorMessage = responseObject["message"] as? String ?? "Unknown error"
2919
- self.presentPaymentErrorVC(errorMessage: errorMessage)
2920
- return
2921
- }
2922
- else {
2923
- DispatchQueue.main.async {
2924
- if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
2925
- paymentDoneVC.chargeData = responseObject
2926
- paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
2927
- paymentDoneVC.easyPayDelegate = self.easyPayDelegate
2928
- paymentDoneVC.bankPaymentParams = params
2929
- // Pass billing and additional info
2930
- // Conditionally pass raw FieldItem array
2931
- paymentDoneVC.visibility = self.visibility
2932
- paymentDoneVC.request = self.request
2933
-
2934
- // if self.visibility?.billing == true {
2935
- paymentDoneVC.billingInfoData = self.billingInfo
2936
- var billingDict: [String: Any] = [:]
2937
- self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
2938
- paymentDoneVC.billingInfo = billingDict
2939
- // }
2940
-
2941
- // if self.visibility?.additional == true {
2942
- paymentDoneVC.additionalInfoData = self.additionalInfo
2943
- var additionalDict: [String: Any] = [:]
2944
- self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
2945
- paymentDoneVC.additionalInfo = additionalDict
2946
- // }
2947
- self.navigationController?.pushViewController(paymentDoneVC, animated: true)
2948
- }
2949
- }
2950
- }
2951
- } else {
2952
- self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
2953
- }
2954
- } catch let jsonError {
2955
- self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
2956
- }
2957
- } else {
2958
- self.presentPaymentErrorVC(errorMessage: "No data received")
2959
- }
2960
- } else {
2961
- if let data = serviceData,
2962
- let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
2963
- let message = responseObj["message"] as? String {
2964
- self.presentPaymentErrorVC(errorMessage: message)
2965
- } else {
2966
- self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
2967
- }
2968
- }
2969
- }
2970
- task.resume()
2971
- }
2972
-
2973
- }
2974
-
2975
- extension AdditionalInfoVC: UITextFieldDelegate {
2976
-
2977
- }
2978
-
2979
- extension AdditionalInfoVC: CountryListVCDelegate {
2980
- func didSelectCountry(_ country: Country) {
2981
- lblCountryCode.text = "\(country.flag ?? "") +\(country.extensionCode ?? "")"
2982
- }
2983
- }
2984
-
2985
- extension String {
2986
- static func flag(for countryCode: String) -> String {
2987
- let base : UInt32 = 127397
2988
- var s = ""
2989
- for v in countryCode.uppercased().unicodeScalars {
2990
- if let scalar = UnicodeScalar(base + v.value) {
2991
- s.unicodeScalars.append(scalar)
2992
- }
2993
- }
2994
- return s
2995
- }
2996
- }