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