@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.
- package/README.md +42 -113
- package/android/build.gradle +1 -1
- package/ios/Classes/EasyMerchantSdk.m +2 -2
- package/ios/easymerchantsdk.podspec +1 -1
- package/package.json +1 -1
- package/.idea/caches/deviceStreaming.xml +0 -340
- package/.idea/em-MobileCheckoutSDK-ReactNative.iml +0 -9
- package/.idea/misc.xml +0 -5
- package/.idea/modules.xml +0 -8
- package/.idea/vcs.xml +0 -6
- package/android/build/generated/source/buildConfig/debug/com/reactlibrary/BuildConfig.java +0 -10
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +0 -7
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +0 -18
- package/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +0 -6
- package/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +0 -1
- package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
- package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
- package/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +0 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +0 -1
- package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +0 -2
- package/android/build/intermediates/incremental/mergeDebugAssets/merger.xml +0 -2
- package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +0 -2
- package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +0 -2
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/BuildConfig.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$1$1.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule$3.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/reactlibrary/RNEasymerchantsdkPackage.class +0 -0
- package/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +0 -2
- package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +0 -7
- package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +0 -7
- package/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +0 -1
- package/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +0 -1
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/BuildConfig.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$1$1.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$1.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$2.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule$3.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkModule.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/reactlibrary/RNEasymerchantsdkPackage.class +0 -0
- package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
- package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +0 -1
- package/android/build/outputs/logs/manifest-merger-debug-report.txt +0 -16
- package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
- package/ios/Pods/UserDefaults/UserStoreSingleton.swift +0 -425
- package/ios/Pods/ViewControllers/AdditionalInfoVC.swift +0 -2996
- package/ios/Pods/ViewControllers/BaseVC.swift +0 -142
- package/ios/Pods/ViewControllers/BillingInfoVC/BillingInfoVC.swift +0 -3807
- package/ios/Pods/ViewControllers/BillingInfoVC/Cells/CityListTVC.swift +0 -46
- package/ios/Pods/ViewControllers/BillingInfoVC/Cells/CountryListTVC.swift +0 -47
- package/ios/Pods/ViewControllers/BillingInfoVC/Cells/StateListTVC.swift +0 -46
- package/ios/Pods/ViewControllers/Clean Runner_2025-07-23T14-58-05.txt +0 -13
- package/ios/Pods/ViewControllers/CountryListVC.swift +0 -435
- package/ios/Pods/ViewControllers/EmailVerificationVC.swift +0 -300
- package/ios/Pods/ViewControllers/GrailPayVC.swift +0 -492
- package/ios/Pods/ViewControllers/OTPVerificationVC.swift +0 -2278
- package/ios/Pods/ViewControllers/PaymentDoneVC.swift +0 -287
- package/ios/Pods/ViewControllers/PaymentErrorVC.swift +0 -85
- package/ios/Pods/ViewControllers/PaymentInformation/AccountTypeTVC.swift +0 -41
- package/ios/Pods/ViewControllers/PaymentInformation/PaymentInfoVC.swift +0 -13115
- package/ios/Pods/ViewControllers/PaymentInformation/PaymentInformationCVC.swift +0 -35
- package/ios/Pods/ViewControllers/PaymentInformation/RecurringTVC.swift +0 -40
- package/ios/Pods/ViewControllers/PaymentInformation/SavedAccountsTVC/SavedAccountTVC.swift +0 -80
- package/ios/Pods/ViewControllers/PaymentInformation/SavedAccountsTVC/SavedAccountTVC.xib +0 -163
- package/ios/Pods/ViewControllers/PaymentInformation/SavedCardsTVC/SavedCardsTVC.swift +0 -81
- package/ios/Pods/ViewControllers/PaymentInformation/SavedCardsTVC/SavedCardsTVC.xib +0 -188
- package/ios/Pods/ViewControllers/PaymentStatusWebViewVC.swift +0 -167
- package/ios/Pods/ViewControllers/TermAndConditionsVC.swift +0 -63
- 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
|
-
}
|