@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,2066 @@
|
|
|
1
|
+
//
|
|
2
|
+
// OTPVerificationVC.swift
|
|
3
|
+
// EasyPay
|
|
4
|
+
//
|
|
5
|
+
// Created by Mony's Mac on 14/08/24.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import UIKit
|
|
9
|
+
|
|
10
|
+
class OTPVerificationVC: BaseVC {
|
|
11
|
+
|
|
12
|
+
@IBOutlet weak var viewTextOTP1: UIView!
|
|
13
|
+
@IBOutlet weak var txtFieldOTPText1: UITextField!
|
|
14
|
+
@IBOutlet weak var viewTextOTP2: UIView!
|
|
15
|
+
@IBOutlet weak var txtFieldOTPText2: UITextField!
|
|
16
|
+
@IBOutlet weak var viewTextOTP3: UIView!
|
|
17
|
+
@IBOutlet weak var txtFieldOTPText3: UITextField!
|
|
18
|
+
@IBOutlet weak var viewTextOTP4: UIView!
|
|
19
|
+
@IBOutlet weak var txtFieldOTPText4: UITextField!
|
|
20
|
+
@IBOutlet weak var viewTextOTP5: UIView!
|
|
21
|
+
@IBOutlet weak var txtFieldOTPText5: UITextField!
|
|
22
|
+
@IBOutlet weak var viewTextOTP6: UIView!
|
|
23
|
+
@IBOutlet weak var txtFieldOTPText6: UITextField!
|
|
24
|
+
@IBOutlet weak var imgEsclamationMark: UIImageView!
|
|
25
|
+
@IBOutlet weak var lblOtpTimer: UILabel!
|
|
26
|
+
@IBOutlet weak var lblUntillResendOtp: UILabel!
|
|
27
|
+
@IBOutlet weak var btnResendOTP: UIButton!
|
|
28
|
+
|
|
29
|
+
@IBOutlet weak var btnConfirmCode: UIButton!
|
|
30
|
+
@IBOutlet weak var lblUsedSavedInfo: UILabel!
|
|
31
|
+
@IBOutlet weak var lblEnterOTP: UILabel!
|
|
32
|
+
|
|
33
|
+
var cardNumber: String?
|
|
34
|
+
var expiryDate: String?
|
|
35
|
+
var cvv: String?
|
|
36
|
+
var nameOnCard: String?
|
|
37
|
+
var billingInfoData: Data?
|
|
38
|
+
|
|
39
|
+
var email: String?
|
|
40
|
+
|
|
41
|
+
var selectedPaymentMethod: String?
|
|
42
|
+
|
|
43
|
+
//Banking Params
|
|
44
|
+
var accountName: String?
|
|
45
|
+
var routingNumber: String?
|
|
46
|
+
var accountType: String?
|
|
47
|
+
var accountNumber: String?
|
|
48
|
+
|
|
49
|
+
var easyPayDelegate: EasyPayViewControllerDelegate?
|
|
50
|
+
|
|
51
|
+
var easyPayVC: EasyPayViewController?
|
|
52
|
+
|
|
53
|
+
var isSavedForFuture: Bool = false
|
|
54
|
+
|
|
55
|
+
private var timeRemaining = 60 // 3 minutes
|
|
56
|
+
private var timer: Timer?
|
|
57
|
+
|
|
58
|
+
//GrailPay Params
|
|
59
|
+
var grailPayAccountID: String?
|
|
60
|
+
var selectedGrailPayAccountType: String?
|
|
61
|
+
var selectedGrailPayAccountName: String?
|
|
62
|
+
|
|
63
|
+
var request: Request!
|
|
64
|
+
|
|
65
|
+
var chosenPlan: String?
|
|
66
|
+
var startDate: String?
|
|
67
|
+
|
|
68
|
+
var userEmail: String?
|
|
69
|
+
|
|
70
|
+
var fieldSection: FieldSection?
|
|
71
|
+
var additionalInfo: [FieldItem]?
|
|
72
|
+
var billingInfo: [FieldItem]?
|
|
73
|
+
var visibility: FieldsVisibility?
|
|
74
|
+
|
|
75
|
+
// var amount: Int?
|
|
76
|
+
var amount: Double?
|
|
77
|
+
|
|
78
|
+
var isSavedNewCard: Bool = false
|
|
79
|
+
|
|
80
|
+
var isSavedNewAccount: Bool?
|
|
81
|
+
var isFrom = String()
|
|
82
|
+
|
|
83
|
+
override func viewDidLoad() {
|
|
84
|
+
super.viewDidLoad()
|
|
85
|
+
// emailVerificationApi()
|
|
86
|
+
|
|
87
|
+
uiFinishingTouchElements()
|
|
88
|
+
|
|
89
|
+
// Add tap gesture recognizer to dismiss the keyboard
|
|
90
|
+
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
|
|
91
|
+
view.addGestureRecognizer(tapGesture)
|
|
92
|
+
|
|
93
|
+
setupTextFields()
|
|
94
|
+
|
|
95
|
+
btnResendOTP.isHidden = true
|
|
96
|
+
|
|
97
|
+
print(fieldSection ?? "")
|
|
98
|
+
print(additionalInfo ?? "")
|
|
99
|
+
print(billingInfo ?? "")
|
|
100
|
+
print(visibility ?? "")
|
|
101
|
+
|
|
102
|
+
// Decode request.billingInfoData
|
|
103
|
+
if let billingData = request?.fields {
|
|
104
|
+
do {
|
|
105
|
+
let fieldSection = try JSONDecoder().decode(FieldSection.self, from: billingData)
|
|
106
|
+
self.visibility = fieldSection.visibility
|
|
107
|
+
self.billingInfo = fieldSection.billing
|
|
108
|
+
self.additionalInfo = fieldSection.additional
|
|
109
|
+
} catch {
|
|
110
|
+
print("Failed to decode billing info: \(error)")
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
decodeBillingInfo()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
override func viewWillAppear(_ animated: Bool) {
|
|
118
|
+
emailVerificationApi()
|
|
119
|
+
startTimer()
|
|
120
|
+
uiFinishingTouchElements()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// MARK: - Decode billingInfoData
|
|
124
|
+
private func decodeBillingInfo() {
|
|
125
|
+
guard let data = billingInfoData else {
|
|
126
|
+
print("No billingInfoData found")
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
do {
|
|
131
|
+
let decodedSection = try JSONDecoder().decode(FieldSection.self, from: data)
|
|
132
|
+
self.fieldSection = decodedSection
|
|
133
|
+
self.billingInfo = decodedSection.billing
|
|
134
|
+
self.additionalInfo = decodedSection.additional
|
|
135
|
+
self.visibility = decodedSection.visibility
|
|
136
|
+
|
|
137
|
+
print("Decoded billingInfo: \(billingInfo ?? [])")
|
|
138
|
+
} catch {
|
|
139
|
+
print("Failed to decode billing info data: \(error)")
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
//MARK: - Ui Colors Setup.
|
|
144
|
+
func uiFinishingTouchElements() {
|
|
145
|
+
if let containerBGcolor = UserStoreSingleton.shared.container_bg_col,
|
|
146
|
+
let uiColor = UIColor(hex: containerBGcolor) {
|
|
147
|
+
self.view.backgroundColor = uiColor
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if let primaryFontColor = UserStoreSingleton.shared.primary_font_col,
|
|
151
|
+
let uiColor = UIColor(hex: primaryFontColor) {
|
|
152
|
+
lblUsedSavedInfo.textColor = uiColor
|
|
153
|
+
txtFieldOTPText1.textColor = uiColor
|
|
154
|
+
txtFieldOTPText2.textColor = uiColor
|
|
155
|
+
txtFieldOTPText3.textColor = uiColor
|
|
156
|
+
txtFieldOTPText4.textColor = uiColor
|
|
157
|
+
txtFieldOTPText5.textColor = uiColor
|
|
158
|
+
txtFieldOTPText6.textColor = uiColor
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if let primaryFontColor = UserStoreSingleton.shared.secondary_font_col,
|
|
162
|
+
let uiColor = UIColor(hex: primaryFontColor) {
|
|
163
|
+
lblEnterOTP.textColor = uiColor
|
|
164
|
+
lblUntillResendOtp.textColor = uiColor
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if let primaryFontColor = UserStoreSingleton.shared.primary_btn_bg_col,
|
|
168
|
+
let uiColor = UIColor(hex: primaryFontColor) {
|
|
169
|
+
btnConfirmCode.backgroundColor = uiColor
|
|
170
|
+
btnResendOTP.tintColor = uiColor
|
|
171
|
+
txtFieldOTPText1.tintColor = uiColor
|
|
172
|
+
txtFieldOTPText2.tintColor = uiColor
|
|
173
|
+
txtFieldOTPText3.tintColor = uiColor
|
|
174
|
+
txtFieldOTPText4.tintColor = uiColor
|
|
175
|
+
txtFieldOTPText5.tintColor = uiColor
|
|
176
|
+
txtFieldOTPText6.tintColor = uiColor
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if let secondaryBtnBackgroundColor = UserStoreSingleton.shared.primary_btn_font_col,
|
|
180
|
+
let secondaryUIColor = UIColor(hex: secondaryBtnBackgroundColor) {
|
|
181
|
+
btnConfirmCode.setTitleColor(secondaryUIColor, for: .normal)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if let borderRadiusString = UserStoreSingleton.shared.border_radious,
|
|
185
|
+
let borderRadius = Double(borderRadiusString) { // Convert String to Double
|
|
186
|
+
btnConfirmCode.layer.cornerRadius = CGFloat(borderRadius) // Set corner radius
|
|
187
|
+
viewTextOTP1.layer.cornerRadius = CGFloat(borderRadius)
|
|
188
|
+
viewTextOTP2.layer.cornerRadius = CGFloat(borderRadius)
|
|
189
|
+
viewTextOTP3.layer.cornerRadius = CGFloat(borderRadius)
|
|
190
|
+
viewTextOTP4.layer.cornerRadius = CGFloat(borderRadius)
|
|
191
|
+
viewTextOTP5.layer.cornerRadius = CGFloat(borderRadius)
|
|
192
|
+
viewTextOTP6.layer.cornerRadius = CGFloat(borderRadius)
|
|
193
|
+
} else {
|
|
194
|
+
btnConfirmCode.layer.cornerRadius = 8 // Default value
|
|
195
|
+
}
|
|
196
|
+
btnConfirmCode.layer.masksToBounds = true // Ensure the corners are clipped properly
|
|
197
|
+
|
|
198
|
+
if let viewOtpPrimaryBackgroundColor = UserStoreSingleton.shared.primary_btn_bg_col,
|
|
199
|
+
let primaryUIColor = UIColor(hex: viewOtpPrimaryBackgroundColor),
|
|
200
|
+
let viewOtpSecondaryColor = UserStoreSingleton.shared.secondary_font_col,
|
|
201
|
+
let secondaryUIColor = UIColor(hex: viewOtpSecondaryColor) {
|
|
202
|
+
|
|
203
|
+
viewTextOTP1.layer.borderWidth = 1.0
|
|
204
|
+
viewTextOTP1.layer.borderColor = txtFieldOTPText1.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
|
|
205
|
+
viewTextOTP2.layer.borderWidth = 1.0
|
|
206
|
+
viewTextOTP2.layer.borderColor = txtFieldOTPText2.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
|
|
207
|
+
viewTextOTP3.layer.borderWidth = 1.0
|
|
208
|
+
viewTextOTP3.layer.borderColor = txtFieldOTPText3.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
|
|
209
|
+
viewTextOTP4.layer.borderWidth = 1.0
|
|
210
|
+
viewTextOTP4.layer.borderColor = txtFieldOTPText4.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
|
|
211
|
+
viewTextOTP5.layer.borderWidth = 1.0
|
|
212
|
+
viewTextOTP5.layer.borderColor = txtFieldOTPText5.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
|
|
213
|
+
viewTextOTP6.layer.borderWidth = 1.0
|
|
214
|
+
viewTextOTP6.layer.borderColor = txtFieldOTPText6.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if let fontSizeString = UserStoreSingleton.shared.fontSize,
|
|
218
|
+
let fontSizeDouble = Double(fontSizeString) { // Convert String to Double
|
|
219
|
+
let fontSize = CGFloat(fontSizeDouble) // Convert Double to CGFloat
|
|
220
|
+
lblEnterOTP.font = UIFont.systemFont(ofSize: fontSize)
|
|
221
|
+
lblOtpTimer.font = UIFont.systemFont(ofSize: fontSize)
|
|
222
|
+
lblUntillResendOtp.font = UIFont.systemFont(ofSize: fontSize)
|
|
223
|
+
btnResendOTP.titleLabel?.font = UIFont.systemFont(ofSize: fontSize)
|
|
224
|
+
btnConfirmCode.titleLabel?.font = UIFont.systemFont(ofSize: fontSize)
|
|
225
|
+
|
|
226
|
+
txtFieldOTPText1.font = UIFont.systemFont(ofSize: fontSize)
|
|
227
|
+
txtFieldOTPText2.font = UIFont.systemFont(ofSize: fontSize)
|
|
228
|
+
txtFieldOTPText3.font = UIFont.systemFont(ofSize: fontSize)
|
|
229
|
+
txtFieldOTPText4.font = UIFont.systemFont(ofSize: fontSize)
|
|
230
|
+
txtFieldOTPText5.font = UIFont.systemFont(ofSize: fontSize)
|
|
231
|
+
txtFieldOTPText6.font = UIFont.systemFont(ofSize: fontSize)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Method to dismiss the keyboard
|
|
236
|
+
@objc func dismissKeyboard() {
|
|
237
|
+
view.endEditing(true)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private func startTimer() {
|
|
241
|
+
timeRemaining = 60 // 3 minutes
|
|
242
|
+
lblOtpTimer.text = formatTime(timeRemaining)
|
|
243
|
+
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private func stopTimer() {
|
|
247
|
+
timer?.invalidate()
|
|
248
|
+
timer = nil
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
@objc private func updateTimer() {
|
|
252
|
+
if timeRemaining > 0 {
|
|
253
|
+
timeRemaining -= 1
|
|
254
|
+
lblOtpTimer.text = formatTime(timeRemaining)
|
|
255
|
+
} else {
|
|
256
|
+
stopTimer()
|
|
257
|
+
btnResendOTP.isHidden = false
|
|
258
|
+
imgEsclamationMark.isHidden = true
|
|
259
|
+
lblOtpTimer.isHidden = true
|
|
260
|
+
lblUntillResendOtp.isHidden = true
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private func formatTime(_ seconds: Int) -> String {
|
|
265
|
+
let minutes = seconds / 60
|
|
266
|
+
let seconds = seconds % 60
|
|
267
|
+
return String(format: "%02d:%02d", minutes, seconds)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private func setupTextFields() {
|
|
271
|
+
let textFields = [txtFieldOTPText1, txtFieldOTPText2, txtFieldOTPText3, txtFieldOTPText4, txtFieldOTPText5, txtFieldOTPText6]
|
|
272
|
+
|
|
273
|
+
for textField in textFields {
|
|
274
|
+
textField?.delegate = self
|
|
275
|
+
textField?.textContentType = .oneTimeCode
|
|
276
|
+
textField?.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
//MARK: - Send OTP Email Verification Api
|
|
281
|
+
func emailVerificationApi() {
|
|
282
|
+
showLoadingIndicator()
|
|
283
|
+
|
|
284
|
+
let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.emailVerification.path()
|
|
285
|
+
|
|
286
|
+
guard let serviceURL = URL(string: fullURL) else {
|
|
287
|
+
print("Invalid URL")
|
|
288
|
+
hideLoadingIndicator()
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
var request = URLRequest(url: serviceURL)
|
|
293
|
+
request.httpMethod = "POST"
|
|
294
|
+
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
295
|
+
|
|
296
|
+
let token = UserStoreSingleton.shared.clientToken
|
|
297
|
+
print("Setting clientToken header: \(token ?? "None")")
|
|
298
|
+
request.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
|
|
299
|
+
|
|
300
|
+
// Add API headers
|
|
301
|
+
if let apiKey = EnvironmentConfig.apiKey,
|
|
302
|
+
let apiSecret = EnvironmentConfig.apiSecret {
|
|
303
|
+
request.addValue(apiKey, forHTTPHeaderField: "x-api-key")
|
|
304
|
+
request.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
let params: [String: Any] = [
|
|
308
|
+
"card_search_value": email ?? "",
|
|
309
|
+
"card_search_key": "email"
|
|
310
|
+
]
|
|
311
|
+
|
|
312
|
+
do {
|
|
313
|
+
let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
|
|
314
|
+
request.httpBody = jsonData
|
|
315
|
+
if let jsonString = String(data: jsonData, encoding: .utf8) {
|
|
316
|
+
print("JSON Payload: \(jsonString)")
|
|
317
|
+
}
|
|
318
|
+
} catch let error {
|
|
319
|
+
print("Error creating JSON data: \(error)")
|
|
320
|
+
hideLoadingIndicator()
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
let session = URLSession.shared
|
|
325
|
+
let task = session.dataTask(with: request) { (serviceData, serviceResponse, error) in
|
|
326
|
+
|
|
327
|
+
DispatchQueue.main.async {
|
|
328
|
+
self.hideLoadingIndicator() // Stop loader when response is received
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if let error = error {
|
|
332
|
+
print("Error: \(error.localizedDescription)")
|
|
333
|
+
return
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
guard let httpResponse = serviceResponse as? HTTPURLResponse else {
|
|
337
|
+
print("Invalid response")
|
|
338
|
+
return
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
|
|
342
|
+
if let data = serviceData {
|
|
343
|
+
do {
|
|
344
|
+
if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
|
|
345
|
+
print("Response Data: \(responseObject)")
|
|
346
|
+
|
|
347
|
+
} else {
|
|
348
|
+
print("Invalid JSON format")
|
|
349
|
+
}
|
|
350
|
+
} catch let jsonError {
|
|
351
|
+
print("Error parsing JSON: \(jsonError)")
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
print("No data received")
|
|
355
|
+
}
|
|
356
|
+
} else {
|
|
357
|
+
print("HTTP Status Code: \(httpResponse.statusCode)")
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
task.resume()
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
//MARK: - OTP Verification Api
|
|
364
|
+
func otpVerificationApi() {
|
|
365
|
+
showLoadingIndicator()
|
|
366
|
+
|
|
367
|
+
let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.verifyOtp.path()
|
|
368
|
+
|
|
369
|
+
guard let serviceURL = URL(string: fullURL) else {
|
|
370
|
+
print("Invalid URL")
|
|
371
|
+
hideLoadingIndicator()
|
|
372
|
+
return
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
var uRLRequest = URLRequest(url: serviceURL)
|
|
376
|
+
uRLRequest.httpMethod = "POST"
|
|
377
|
+
uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
378
|
+
|
|
379
|
+
let token = UserStoreSingleton.shared.clientToken
|
|
380
|
+
print("Setting clientToken header: \(token ?? "None")")
|
|
381
|
+
uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
|
|
382
|
+
|
|
383
|
+
// Add API headers
|
|
384
|
+
if let apiKey = EnvironmentConfig.apiKey,
|
|
385
|
+
let apiSecret = EnvironmentConfig.apiSecret {
|
|
386
|
+
uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
|
|
387
|
+
uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
let params: [String: Any] = [
|
|
391
|
+
"card_search_value": email ?? "",
|
|
392
|
+
"card_search_key": "email",
|
|
393
|
+
"otp": getCombinedOTP()
|
|
394
|
+
]
|
|
395
|
+
|
|
396
|
+
do {
|
|
397
|
+
let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
|
|
398
|
+
uRLRequest.httpBody = jsonData
|
|
399
|
+
if let jsonString = String(data: jsonData, encoding: .utf8) {
|
|
400
|
+
print("JSON Payload: \(jsonString)")
|
|
401
|
+
}
|
|
402
|
+
} catch let error {
|
|
403
|
+
print("Error creating JSON data: \(error)")
|
|
404
|
+
hideLoadingIndicator()
|
|
405
|
+
return
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
let session = URLSession.shared
|
|
409
|
+
let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
|
|
410
|
+
|
|
411
|
+
DispatchQueue.main.async {
|
|
412
|
+
self.hideLoadingIndicator() // Stop loader when response is received
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if let error = error {
|
|
416
|
+
print("Error: \(error.localizedDescription)")
|
|
417
|
+
return
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
guard let httpResponse = serviceResponse as? HTTPURLResponse else {
|
|
421
|
+
print("Invalid response")
|
|
422
|
+
return
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
|
|
426
|
+
if let data = serviceData {
|
|
427
|
+
do {
|
|
428
|
+
if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
|
|
429
|
+
print("Response Data: \(responseObject)")
|
|
430
|
+
if self.selectedPaymentMethod == "Card"{
|
|
431
|
+
if let dataSection = responseObject["data"] as? [String: Any],
|
|
432
|
+
let innerData = dataSection["data"] as? [String: Any],
|
|
433
|
+
let customerId = innerData["customer_id"] as? String {
|
|
434
|
+
print("Extracted customer_id: \(customerId)")
|
|
435
|
+
if self.billingInfoData == nil {
|
|
436
|
+
if self.request.secureAuthentication == true {
|
|
437
|
+
self.threeDSecurePaymentApi(customerId: customerId)
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
self.paymentIntentWithSavedCardApi(customerId: customerId)
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
// self.paymentIntentApi(customerId: customerId)
|
|
445
|
+
if let request = self.request {
|
|
446
|
+
if request.secureAuthentication == true {
|
|
447
|
+
self.threeDSecurePaymentSavedCardApi(customerId: customerId)
|
|
448
|
+
} else {
|
|
449
|
+
self.paymentIntentApi(customerId: customerId)
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
} else {
|
|
454
|
+
print("customer_id not found in nested data. Falling back to nil.")
|
|
455
|
+
if let request = self.request {
|
|
456
|
+
if request.secureAuthentication == true {
|
|
457
|
+
self.threeDSecurePaymentSavedCardApi(customerId: nil)
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
self.paymentIntentApi(customerId: nil)
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
else if self.selectedPaymentMethod == "Bank" {
|
|
466
|
+
|
|
467
|
+
if let dataSection = responseObject["data"] as? [String: Any],
|
|
468
|
+
let innerData = dataSection["data"] as? [String: Any],
|
|
469
|
+
let customerId = innerData["customer_id"] as? String {
|
|
470
|
+
print("Extracted customer_id: \(customerId)")
|
|
471
|
+
if self.billingInfoData == nil {
|
|
472
|
+
self.accountChargeWithSavedAccountApi(customerId: customerId)
|
|
473
|
+
}
|
|
474
|
+
else {
|
|
475
|
+
self.accountChargeApi(customerId: customerId)
|
|
476
|
+
}
|
|
477
|
+
} else {
|
|
478
|
+
print("customer_id not found in nested data. Falling back to nil.")
|
|
479
|
+
if self.billingInfoData == nil {
|
|
480
|
+
self.accountChargeWithSavedAccountApi(customerId: nil)
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
self.accountChargeApi(customerId: nil)
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
else if self.selectedPaymentMethod == "GrailPay" {
|
|
488
|
+
if let dataSection = responseObject["data"] as? [String: Any],
|
|
489
|
+
let innerData = dataSection["data"] as? [String: Any],
|
|
490
|
+
let customerId = innerData["customer_id"] as? String {
|
|
491
|
+
print("Extracted customer_id: \(customerId)")
|
|
492
|
+
self.grailPayAccountChargeApi(customerId: customerId)
|
|
493
|
+
} else {
|
|
494
|
+
print("customer_id not found. Sending empty customerId.")
|
|
495
|
+
self.grailPayAccountChargeApi(customerId: nil)
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
} else {
|
|
499
|
+
print("Invalid JSON format")
|
|
500
|
+
}
|
|
501
|
+
} catch let jsonError {
|
|
502
|
+
print("Error parsing JSON: \(jsonError)")
|
|
503
|
+
}
|
|
504
|
+
} else {
|
|
505
|
+
print("No data received")
|
|
506
|
+
}
|
|
507
|
+
} else {
|
|
508
|
+
print("HTTP Status Code: \(httpResponse.statusCode)")
|
|
509
|
+
if let data = serviceData,
|
|
510
|
+
let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
|
|
511
|
+
let message = responseObj["message"] as? String {
|
|
512
|
+
DispatchQueue.main.async {
|
|
513
|
+
self.showToast(message: message)
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
task.resume()
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
//MARK: - Card Charge Api
|
|
522
|
+
func paymentIntentApi(customerId: String?) {
|
|
523
|
+
showLoadingIndicator()
|
|
524
|
+
|
|
525
|
+
let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.charges.path()
|
|
526
|
+
|
|
527
|
+
guard let serviceURL = URL(string: fullURL) else {
|
|
528
|
+
print("Invalid URL")
|
|
529
|
+
hideLoadingIndicator()
|
|
530
|
+
return
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
var uRLRequest = URLRequest(url: serviceURL)
|
|
534
|
+
uRLRequest.httpMethod = "POST"
|
|
535
|
+
uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
536
|
+
|
|
537
|
+
let token = UserStoreSingleton.shared.clientToken
|
|
538
|
+
print("Setting clientToken header: \(token ?? "None")")
|
|
539
|
+
uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
|
|
540
|
+
|
|
541
|
+
// Add API headers
|
|
542
|
+
if let apiKey = EnvironmentConfig.apiKey,
|
|
543
|
+
let apiSecret = EnvironmentConfig.apiSecret {
|
|
544
|
+
uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
|
|
545
|
+
uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
let emailPrefix = userEmail?.components(separatedBy: "@").first ?? ""
|
|
549
|
+
|
|
550
|
+
var params: [String: Any] = [
|
|
551
|
+
"name": nameOnCard ?? "",
|
|
552
|
+
"card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
|
|
553
|
+
"cardholder_name": nameOnCard ?? "",
|
|
554
|
+
"exp_month": expiryDate?.components(separatedBy: "/").first ?? "",
|
|
555
|
+
"exp_year": expiryDate?.components(separatedBy: "/").last ?? "",
|
|
556
|
+
"cvc": cvv ?? "",
|
|
557
|
+
"currency": "usd",
|
|
558
|
+
"payment_method": selectedPaymentMethod ?? "",
|
|
559
|
+
"save_card": 1,
|
|
560
|
+
"is_default": "1",
|
|
561
|
+
// "create_customer": "1"
|
|
562
|
+
"email": userEmail ?? "",
|
|
563
|
+
// "price":"6.0",
|
|
564
|
+
"tokenize": request.tokenOnly ?? "",
|
|
565
|
+
"username": emailPrefix
|
|
566
|
+
]
|
|
567
|
+
|
|
568
|
+
if let customerId = customerId {
|
|
569
|
+
params["customer"] = customerId
|
|
570
|
+
params["customer_id"] = customerId
|
|
571
|
+
}
|
|
572
|
+
// else {
|
|
573
|
+
// params["username"] = emailPrefix
|
|
574
|
+
// params["email"] = userEmail ?? ""
|
|
575
|
+
// }
|
|
576
|
+
|
|
577
|
+
if customerId == nil {
|
|
578
|
+
params["create_customer"] = "1"
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if let billingInfoData = request.fields {
|
|
582
|
+
do {
|
|
583
|
+
let fieldSection = try JSONDecoder().decode(FieldSection.self, from: billingInfoData)
|
|
584
|
+
|
|
585
|
+
// Billing Info
|
|
586
|
+
let billing = fieldSection.billing
|
|
587
|
+
if !billing.isEmpty {
|
|
588
|
+
var billingDict: [String: Any] = [:]
|
|
589
|
+
billing.forEach { billingDict[$0.name] = $0.value }
|
|
590
|
+
|
|
591
|
+
if let address = billingDict["address"] as? String, !address.isEmpty {
|
|
592
|
+
params["address"] = address
|
|
593
|
+
}
|
|
594
|
+
if let country = billingDict["country"] as? String, !country.isEmpty {
|
|
595
|
+
params["country"] = country
|
|
596
|
+
}
|
|
597
|
+
if let state = billingDict["state"] as? String, !state.isEmpty {
|
|
598
|
+
params["state"] = state
|
|
599
|
+
}
|
|
600
|
+
if let city = billingDict["city"] as? String, !city.isEmpty {
|
|
601
|
+
params["city"] = city
|
|
602
|
+
}
|
|
603
|
+
if let postalCode = billingDict["postal_code"] as? String, !postalCode.isEmpty {
|
|
604
|
+
params["zip"] = postalCode
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Additional Info
|
|
609
|
+
let additional = fieldSection.additional
|
|
610
|
+
if !additional.isEmpty {
|
|
611
|
+
var additionalDict: [String: Any] = [:]
|
|
612
|
+
additional.forEach { additionalDict[$0.name] = $0.value }
|
|
613
|
+
|
|
614
|
+
if let desc = additionalDict["description"] as? String, !desc.isEmpty {
|
|
615
|
+
params["description"] = desc
|
|
616
|
+
} else {
|
|
617
|
+
params["description"] = "Hosted payment checkout"
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if let phone = additionalDict["phone_number"] as? String, !phone.isEmpty {
|
|
621
|
+
params["phone_number"] = phone
|
|
622
|
+
}
|
|
623
|
+
if let email = additionalDict["email"] as? String, !email.isEmpty {
|
|
624
|
+
params["email"] = email
|
|
625
|
+
}
|
|
626
|
+
if let name = additionalDict["name"] as? String, !name.isEmpty {
|
|
627
|
+
params["name"] = name
|
|
628
|
+
}
|
|
629
|
+
} else {
|
|
630
|
+
// If no description in additional info, set default
|
|
631
|
+
params["description"] = "Hosted payment checkout"
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
} catch {
|
|
635
|
+
print("Failed to decode FieldSection: \(error)")
|
|
636
|
+
params["description"] = "Hosted payment checkout"
|
|
637
|
+
}
|
|
638
|
+
} else {
|
|
639
|
+
// Fallback if billingInfoData is missing
|
|
640
|
+
params["description"] = "Hosted payment checkout"
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// Add these if recurring is enabled
|
|
644
|
+
if let req = request, req.is_recurring == true {
|
|
645
|
+
if let recurringType = req.recurringStartDateType, recurringType == .custom {
|
|
646
|
+
// Only send start_date if type is .custom and field is not empty
|
|
647
|
+
if let startDateText = startDate, !startDateText.isEmpty {
|
|
648
|
+
let inputFormatter = DateFormatter()
|
|
649
|
+
inputFormatter.dateFormat = "dd/MM/yyyy"
|
|
650
|
+
|
|
651
|
+
let outputFormatter = DateFormatter()
|
|
652
|
+
outputFormatter.dateFormat = "MM/dd/yyyy"
|
|
653
|
+
|
|
654
|
+
if let date = inputFormatter.date(from: startDateText) {
|
|
655
|
+
let apiFormattedDate = outputFormatter.string(from: date)
|
|
656
|
+
params["start_date"] = apiFormattedDate
|
|
657
|
+
} else {
|
|
658
|
+
print("Invalid date format in startDateText")
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
params["interval"] = chosenPlan?.lowercased()
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// ✅ Include metadata only if it has at least 1 key-value pair
|
|
667
|
+
if let metadata = request?.metadata, !metadata.isEmpty {
|
|
668
|
+
params["metadata"] = metadata
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
print(params)
|
|
672
|
+
|
|
673
|
+
do {
|
|
674
|
+
let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
|
|
675
|
+
uRLRequest.httpBody = jsonData
|
|
676
|
+
if let jsonString = String(data: jsonData, encoding: .utf8) {
|
|
677
|
+
print("JSON Payload: \(jsonString)")
|
|
678
|
+
}
|
|
679
|
+
} catch let error {
|
|
680
|
+
print("Error creating JSON data: \(error)")
|
|
681
|
+
hideLoadingIndicator()
|
|
682
|
+
return
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
let session = URLSession.shared
|
|
686
|
+
let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
|
|
687
|
+
|
|
688
|
+
DispatchQueue.main.async {
|
|
689
|
+
self.hideLoadingIndicator() // Stop loader when response is received
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
if let error = error {
|
|
693
|
+
self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
|
|
694
|
+
return
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
guard let httpResponse = serviceResponse as? HTTPURLResponse else {
|
|
698
|
+
self.presentPaymentErrorVC(errorMessage: "Invalid response")
|
|
699
|
+
return
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
|
|
703
|
+
if let data = serviceData {
|
|
704
|
+
do {
|
|
705
|
+
if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
|
|
706
|
+
print("Response Data: \(responseObject)")
|
|
707
|
+
|
|
708
|
+
// Check if status is 0 and handle the error
|
|
709
|
+
if let status = responseObject["status"] as? Int, status == 0 {
|
|
710
|
+
let errorMessage = responseObject["message"] as? String ?? "Unknown error"
|
|
711
|
+
self.presentPaymentErrorVC(errorMessage: errorMessage)
|
|
712
|
+
} else {
|
|
713
|
+
DispatchQueue.main.async {
|
|
714
|
+
if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
|
|
715
|
+
paymentDoneVC.chargeData = responseObject
|
|
716
|
+
paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
|
|
717
|
+
paymentDoneVC.easyPayDelegate = self.easyPayDelegate
|
|
718
|
+
paymentDoneVC.request = self.request
|
|
719
|
+
|
|
720
|
+
// Pass billing info and additional info if available
|
|
721
|
+
if let billingInfoData = self.request.fields,
|
|
722
|
+
let fieldSection = try? JSONDecoder().decode(FieldSection.self, from: billingInfoData) {
|
|
723
|
+
|
|
724
|
+
// Filter billing info: only include non-empty values
|
|
725
|
+
let filteredBilling = fieldSection.billing.filter { !($0.value.trimmingCharacters(in: .whitespaces).isEmpty) }
|
|
726
|
+
paymentDoneVC.billingInfoData = filteredBilling
|
|
727
|
+
var billingDict: [String: Any] = [:]
|
|
728
|
+
filteredBilling.forEach { billingDict[$0.name] = $0.value }
|
|
729
|
+
paymentDoneVC.billingInfo = billingDict
|
|
730
|
+
|
|
731
|
+
// Filter additional info: only include non-empty values
|
|
732
|
+
let filteredAdditional = fieldSection.additional.filter { !($0.value.trimmingCharacters(in: .whitespaces).isEmpty) }
|
|
733
|
+
paymentDoneVC.additionalInfoData = filteredAdditional
|
|
734
|
+
var additionalDict: [String: Any] = [:]
|
|
735
|
+
filteredAdditional.forEach { additionalDict[$0.name] = $0.value }
|
|
736
|
+
paymentDoneVC.additionalInfo = additionalDict
|
|
737
|
+
}
|
|
738
|
+
self.navigationController?.pushViewController(paymentDoneVC, animated: true)
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
} else {
|
|
743
|
+
self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
|
|
744
|
+
}
|
|
745
|
+
} catch let jsonError {
|
|
746
|
+
self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
|
|
747
|
+
}
|
|
748
|
+
} else {
|
|
749
|
+
self.presentPaymentErrorVC(errorMessage: "No data received")
|
|
750
|
+
}
|
|
751
|
+
} else {
|
|
752
|
+
if let data = serviceData,
|
|
753
|
+
let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
|
|
754
|
+
let message = responseObj["message"] as? String {
|
|
755
|
+
self.presentPaymentErrorVC(errorMessage: message)
|
|
756
|
+
} else {
|
|
757
|
+
self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
task.resume()
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
//MARK: - Card Charge Api If billing info is nil and saved card
|
|
765
|
+
func paymentIntentWithSavedCardApi(customerId: String?) {
|
|
766
|
+
showLoadingIndicator()
|
|
767
|
+
|
|
768
|
+
let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.charges.path()
|
|
769
|
+
|
|
770
|
+
guard let serviceURL = URL(string: fullURL) else {
|
|
771
|
+
print("Invalid URL")
|
|
772
|
+
hideLoadingIndicator()
|
|
773
|
+
return
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
var urlRequest = URLRequest(url: serviceURL)
|
|
777
|
+
urlRequest.httpMethod = "POST"
|
|
778
|
+
urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
779
|
+
|
|
780
|
+
let token = UserStoreSingleton.shared.clientToken
|
|
781
|
+
print("Setting clientToken header: \(token ?? "None")")
|
|
782
|
+
urlRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
|
|
783
|
+
|
|
784
|
+
// Add API headers
|
|
785
|
+
if let apiKey = EnvironmentConfig.apiKey,
|
|
786
|
+
let apiSecret = EnvironmentConfig.apiSecret {
|
|
787
|
+
urlRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
|
|
788
|
+
urlRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
let emailPrefix = email?.components(separatedBy: "@").first ?? ""
|
|
792
|
+
|
|
793
|
+
var params: [String: Any] = [
|
|
794
|
+
"name": emailPrefix,
|
|
795
|
+
"card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
|
|
796
|
+
"cardholder_name": nameOnCard ?? "",
|
|
797
|
+
"exp_month": expiryDate?.components(separatedBy: "/").first ?? "",
|
|
798
|
+
"exp_year": expiryDate?.components(separatedBy: "/").last ?? "",
|
|
799
|
+
"cvc": cvv ?? "",
|
|
800
|
+
"description": "description",
|
|
801
|
+
"currency": "usd",
|
|
802
|
+
"payment_method": selectedPaymentMethod ?? "",
|
|
803
|
+
"save_card": 1,
|
|
804
|
+
"is_default": "1"
|
|
805
|
+
]
|
|
806
|
+
|
|
807
|
+
if let customerId = customerId {
|
|
808
|
+
params["customer"] = customerId
|
|
809
|
+
params["customer_id"] = customerId
|
|
810
|
+
} else {
|
|
811
|
+
params["username"] = emailPrefix
|
|
812
|
+
params["email"] = email ?? ""
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
if customerId == nil {
|
|
816
|
+
params["create_customer"] = "1"
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// Add these if recurring is enabled
|
|
820
|
+
if let req = request, req.is_recurring == true {
|
|
821
|
+
if let recurringType = req.recurringStartDateType, recurringType == .custom {
|
|
822
|
+
// Only send start_date if type is .custom and field is not empty
|
|
823
|
+
if let startDateText = startDate, !startDateText.isEmpty {
|
|
824
|
+
let inputFormatter = DateFormatter()
|
|
825
|
+
inputFormatter.dateFormat = "dd/MM/yyyy"
|
|
826
|
+
|
|
827
|
+
let outputFormatter = DateFormatter()
|
|
828
|
+
outputFormatter.dateFormat = "MM/dd/yyyy"
|
|
829
|
+
|
|
830
|
+
if let date = inputFormatter.date(from: startDateText) {
|
|
831
|
+
let apiFormattedDate = outputFormatter.string(from: date)
|
|
832
|
+
params["start_date"] = apiFormattedDate
|
|
833
|
+
} else {
|
|
834
|
+
print("Invalid date format in startDateText")
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
params["interval"] = chosenPlan?.lowercased()
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// ✅ Include metadata only if it has at least 1 key-value pair
|
|
843
|
+
if let metadata = request?.metadata, !metadata.isEmpty {
|
|
844
|
+
params["metadata"] = metadata
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
print(params)
|
|
848
|
+
|
|
849
|
+
do {
|
|
850
|
+
let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
|
|
851
|
+
urlRequest.httpBody = jsonData
|
|
852
|
+
if let jsonString = String(data: jsonData, encoding: .utf8) {
|
|
853
|
+
print("JSON Payload: \(jsonString)")
|
|
854
|
+
}
|
|
855
|
+
} catch let error {
|
|
856
|
+
print("Error creating JSON data: \(error)")
|
|
857
|
+
hideLoadingIndicator()
|
|
858
|
+
return
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
let session = URLSession.shared
|
|
862
|
+
let task = session.dataTask(with: urlRequest) { (serviceData, serviceResponse, error) in
|
|
863
|
+
|
|
864
|
+
DispatchQueue.main.async {
|
|
865
|
+
self.hideLoadingIndicator() // Stop loader when response is received
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
if let error = error {
|
|
869
|
+
self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
|
|
870
|
+
return
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
guard let httpResponse = serviceResponse as? HTTPURLResponse else {
|
|
874
|
+
self.presentPaymentErrorVC(errorMessage: "Invalid response")
|
|
875
|
+
return
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
|
|
879
|
+
if let data = serviceData {
|
|
880
|
+
do {
|
|
881
|
+
if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
|
|
882
|
+
print("Response Data: \(responseObject)")
|
|
883
|
+
|
|
884
|
+
// Check if status is 0 and handle the error
|
|
885
|
+
if let status = responseObject["status"] as? Int, status == 0 {
|
|
886
|
+
let errorMessage = responseObject["message"] as? String ?? "Unknown error"
|
|
887
|
+
self.presentPaymentErrorVC(errorMessage: errorMessage)
|
|
888
|
+
} else {
|
|
889
|
+
DispatchQueue.main.async {
|
|
890
|
+
if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
|
|
891
|
+
paymentDoneVC.chargeData = responseObject
|
|
892
|
+
paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
|
|
893
|
+
paymentDoneVC.easyPayDelegate = self.easyPayDelegate
|
|
894
|
+
paymentDoneVC.request = self.request
|
|
895
|
+
self.navigationController?.pushViewController(paymentDoneVC, animated: true)
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
} else {
|
|
900
|
+
self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
|
|
901
|
+
}
|
|
902
|
+
} catch let jsonError {
|
|
903
|
+
self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
|
|
904
|
+
}
|
|
905
|
+
} else {
|
|
906
|
+
self.presentPaymentErrorVC(errorMessage: "No data received")
|
|
907
|
+
}
|
|
908
|
+
} else {
|
|
909
|
+
if let data = serviceData,
|
|
910
|
+
let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
|
|
911
|
+
let message = responseObj["message"] as? String {
|
|
912
|
+
self.presentPaymentErrorVC(errorMessage: message)
|
|
913
|
+
} else {
|
|
914
|
+
self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
task.resume()
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
func presentPaymentErrorVC(errorMessage: String) {
|
|
922
|
+
DispatchQueue.main.async {
|
|
923
|
+
if let paymentErrorVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentErrorVC") as? PaymentErrorVC {
|
|
924
|
+
paymentErrorVC.errorMessage = errorMessage
|
|
925
|
+
paymentErrorVC.easyPayDelegate = self.easyPayDelegate // Pass the reference here
|
|
926
|
+
self.navigationController?.pushViewController(paymentErrorVC, animated: true)
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
//MARK: - Account Charge Api
|
|
932
|
+
func accountChargeApi(customerId: String?) {
|
|
933
|
+
showLoadingIndicator()
|
|
934
|
+
|
|
935
|
+
let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
|
|
936
|
+
|
|
937
|
+
guard let serviceURL = URL(string: fullURL) else {
|
|
938
|
+
print("Invalid URL")
|
|
939
|
+
hideLoadingIndicator()
|
|
940
|
+
return
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
var uRLRequest = URLRequest(url: serviceURL)
|
|
944
|
+
uRLRequest.httpMethod = "POST"
|
|
945
|
+
uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
946
|
+
|
|
947
|
+
let token = UserStoreSingleton.shared.clientToken
|
|
948
|
+
print("Setting clientToken header: \(token ?? "None")")
|
|
949
|
+
uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
|
|
950
|
+
|
|
951
|
+
// Add API headers
|
|
952
|
+
if let apiKey = EnvironmentConfig.apiKey,
|
|
953
|
+
let apiSecret = EnvironmentConfig.apiSecret {
|
|
954
|
+
uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
|
|
955
|
+
uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
let emailPrefix = email?.components(separatedBy: "@").first ?? ""
|
|
959
|
+
|
|
960
|
+
var params: [String: Any] = [
|
|
961
|
+
// "name": accountName ?? "",
|
|
962
|
+
"name": !(request.name?.isEmpty ?? true) ? request.name! : (accountName ?? ""),
|
|
963
|
+
"email": userEmail ?? "",
|
|
964
|
+
"currency": "usd",
|
|
965
|
+
"account_type": accountType?.lowercased() ?? "",
|
|
966
|
+
"routing_number": routingNumber ?? "",
|
|
967
|
+
"account_number": accountNumber ?? "",
|
|
968
|
+
"payment_mode": "auth_and_capture",
|
|
969
|
+
"payment_intent": UserStoreSingleton.shared.paymentIntent ?? "",
|
|
970
|
+
"levelIndicator": 1,
|
|
971
|
+
"save_account": 1,
|
|
972
|
+
"payment_method": "ach"
|
|
973
|
+
]
|
|
974
|
+
|
|
975
|
+
if let customerId = customerId {
|
|
976
|
+
params["customer"] = customerId
|
|
977
|
+
} else {
|
|
978
|
+
params["username"] = emailPrefix
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
if customerId == nil {
|
|
982
|
+
params["create_customer"] = "1"
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
if let billingInfoData = request.fields {
|
|
986
|
+
do {
|
|
987
|
+
let fieldSection = try JSONDecoder().decode(FieldSection.self, from: billingInfoData)
|
|
988
|
+
|
|
989
|
+
// Billing Info
|
|
990
|
+
let billing = fieldSection.billing
|
|
991
|
+
if !billing.isEmpty {
|
|
992
|
+
var billingDict: [String: Any] = [:]
|
|
993
|
+
billing.forEach { billingDict[$0.name] = $0.value }
|
|
994
|
+
|
|
995
|
+
if let address = billingDict["address"] as? String, !address.isEmpty {
|
|
996
|
+
params["address"] = address
|
|
997
|
+
}
|
|
998
|
+
if let country = billingDict["country"] as? String, !country.isEmpty {
|
|
999
|
+
params["country"] = country
|
|
1000
|
+
}
|
|
1001
|
+
if let state = billingDict["state"] as? String, !state.isEmpty {
|
|
1002
|
+
params["state"] = state
|
|
1003
|
+
}
|
|
1004
|
+
if let city = billingDict["city"] as? String, !city.isEmpty {
|
|
1005
|
+
params["city"] = city
|
|
1006
|
+
}
|
|
1007
|
+
if let postalCode = billingDict["postal_code"] as? String, !postalCode.isEmpty {
|
|
1008
|
+
params["zip"] = postalCode
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// Additional Info
|
|
1013
|
+
let additional = fieldSection.additional
|
|
1014
|
+
if !additional.isEmpty {
|
|
1015
|
+
var additionalDict: [String: Any] = [:]
|
|
1016
|
+
additional.forEach { additionalDict[$0.name] = $0.value }
|
|
1017
|
+
|
|
1018
|
+
if let desc = additionalDict["description"] as? String, !desc.isEmpty {
|
|
1019
|
+
params["description"] = desc
|
|
1020
|
+
} else {
|
|
1021
|
+
params["description"] = "Hosted payment checkout"
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
if let phone = additionalDict["phone_number"] as? String, !phone.isEmpty {
|
|
1025
|
+
params["phone_number"] = phone
|
|
1026
|
+
}
|
|
1027
|
+
if let email = additionalDict["email"] as? String, !email.isEmpty {
|
|
1028
|
+
params["email"] = email
|
|
1029
|
+
}
|
|
1030
|
+
if let name = additionalDict["name"] as? String, !name.isEmpty {
|
|
1031
|
+
params["name"] = name
|
|
1032
|
+
}
|
|
1033
|
+
} else {
|
|
1034
|
+
// If no description in additional info, set default
|
|
1035
|
+
params["description"] = "Hosted payment checkout"
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
} catch {
|
|
1039
|
+
print("Failed to decode FieldSection: \(error)")
|
|
1040
|
+
params["description"] = "Hosted payment checkout"
|
|
1041
|
+
}
|
|
1042
|
+
} else {
|
|
1043
|
+
// Fallback if billingInfoData is missing
|
|
1044
|
+
params["description"] = "Hosted payment checkout"
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// Add these if recurring is enabled
|
|
1048
|
+
if let req = request, req.is_recurring == true {
|
|
1049
|
+
if let recurringType = req.recurringStartDateType, recurringType == .custom {
|
|
1050
|
+
// Only send start_date if type is .custom and field is not empty
|
|
1051
|
+
if let startDateText = startDate, !startDateText.isEmpty {
|
|
1052
|
+
let inputFormatter = DateFormatter()
|
|
1053
|
+
inputFormatter.dateFormat = "dd/MM/yyyy"
|
|
1054
|
+
|
|
1055
|
+
let outputFormatter = DateFormatter()
|
|
1056
|
+
outputFormatter.dateFormat = "MM/dd/yyyy"
|
|
1057
|
+
|
|
1058
|
+
if let date = inputFormatter.date(from: startDateText) {
|
|
1059
|
+
let apiFormattedDate = outputFormatter.string(from: date)
|
|
1060
|
+
params["start_date"] = apiFormattedDate
|
|
1061
|
+
} else {
|
|
1062
|
+
print("Invalid date format in startDateText")
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
params["interval"] = chosenPlan?.lowercased()
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// ✅ Include metadata only if it has at least 1 key-value pair
|
|
1071
|
+
if let metadata = request?.metadata, !metadata.isEmpty {
|
|
1072
|
+
params["metadata"] = metadata
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
print(params)
|
|
1076
|
+
|
|
1077
|
+
do {
|
|
1078
|
+
let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
|
|
1079
|
+
uRLRequest.httpBody = jsonData
|
|
1080
|
+
if let jsonString = String(data: jsonData, encoding: .utf8) {
|
|
1081
|
+
print("JSON Payload: \(jsonString)")
|
|
1082
|
+
}
|
|
1083
|
+
} catch let error {
|
|
1084
|
+
print("Error creating JSON data: \(error)")
|
|
1085
|
+
hideLoadingIndicator()
|
|
1086
|
+
return
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
let session = URLSession.shared
|
|
1090
|
+
let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
|
|
1091
|
+
|
|
1092
|
+
DispatchQueue.main.async {
|
|
1093
|
+
self.hideLoadingIndicator() // Stop loader when response is received
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
if let error = error {
|
|
1097
|
+
self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
|
|
1098
|
+
return
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
guard let httpResponse = serviceResponse as? HTTPURLResponse else {
|
|
1102
|
+
self.presentPaymentErrorVC(errorMessage: "Invalid response")
|
|
1103
|
+
return
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
|
|
1107
|
+
if let data = serviceData {
|
|
1108
|
+
do {
|
|
1109
|
+
if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
|
|
1110
|
+
print("Response Data: \(responseObject)")
|
|
1111
|
+
|
|
1112
|
+
// Check if status is 0 and handle the error
|
|
1113
|
+
if let status = responseObject["status"] as? Int, status == 0 {
|
|
1114
|
+
let errorMessage = responseObject["message"] as? String ?? "Unknown error"
|
|
1115
|
+
self.presentPaymentErrorVC(errorMessage: errorMessage)
|
|
1116
|
+
} else {
|
|
1117
|
+
DispatchQueue.main.async {
|
|
1118
|
+
if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
|
|
1119
|
+
paymentDoneVC.chargeData = responseObject
|
|
1120
|
+
paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
|
|
1121
|
+
paymentDoneVC.easyPayDelegate = self.easyPayDelegate
|
|
1122
|
+
paymentDoneVC.bankPaymentParams = params
|
|
1123
|
+
paymentDoneVC.request = self.request
|
|
1124
|
+
// Pass billing info and additional info if available
|
|
1125
|
+
if let billingInfoData = self.request.fields,
|
|
1126
|
+
let fieldSection = try? JSONDecoder().decode(FieldSection.self, from: billingInfoData) {
|
|
1127
|
+
|
|
1128
|
+
// Filter billing info: only include non-empty values
|
|
1129
|
+
let filteredBilling = fieldSection.billing.filter { !($0.value.trimmingCharacters(in: .whitespaces).isEmpty) }
|
|
1130
|
+
paymentDoneVC.billingInfoData = filteredBilling
|
|
1131
|
+
var billingDict: [String: Any] = [:]
|
|
1132
|
+
filteredBilling.forEach { billingDict[$0.name] = $0.value }
|
|
1133
|
+
paymentDoneVC.billingInfo = billingDict
|
|
1134
|
+
|
|
1135
|
+
// Filter additional info: only include non-empty values
|
|
1136
|
+
let filteredAdditional = fieldSection.additional.filter { !($0.value.trimmingCharacters(in: .whitespaces).isEmpty) }
|
|
1137
|
+
paymentDoneVC.additionalInfoData = filteredAdditional
|
|
1138
|
+
var additionalDict: [String: Any] = [:]
|
|
1139
|
+
filteredAdditional.forEach { additionalDict[$0.name] = $0.value }
|
|
1140
|
+
paymentDoneVC.additionalInfo = additionalDict
|
|
1141
|
+
}
|
|
1142
|
+
self.navigationController?.pushViewController(paymentDoneVC, animated: true)
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
} else {
|
|
1147
|
+
self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
|
|
1148
|
+
}
|
|
1149
|
+
} catch let jsonError {
|
|
1150
|
+
self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
|
|
1151
|
+
}
|
|
1152
|
+
} else {
|
|
1153
|
+
self.presentPaymentErrorVC(errorMessage: "No data received")
|
|
1154
|
+
}
|
|
1155
|
+
} else {
|
|
1156
|
+
if let data = serviceData,
|
|
1157
|
+
let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
|
|
1158
|
+
let message = responseObj["message"] as? String {
|
|
1159
|
+
self.presentPaymentErrorVC(errorMessage: message)
|
|
1160
|
+
} else {
|
|
1161
|
+
self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
task.resume()
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
//MARK: - Account Charge Api if billing info is nil and saved account
|
|
1169
|
+
func accountChargeWithSavedAccountApi(customerId: String?) {
|
|
1170
|
+
showLoadingIndicator()
|
|
1171
|
+
|
|
1172
|
+
let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
|
|
1173
|
+
|
|
1174
|
+
guard let serviceURL = URL(string: fullURL) else {
|
|
1175
|
+
print("Invalid URL")
|
|
1176
|
+
hideLoadingIndicator()
|
|
1177
|
+
return
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
var uRLRequest = URLRequest(url: serviceURL)
|
|
1181
|
+
uRLRequest.httpMethod = "POST"
|
|
1182
|
+
uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
1183
|
+
|
|
1184
|
+
let token = UserStoreSingleton.shared.clientToken
|
|
1185
|
+
print("Setting clientToken header: \(token ?? "None")")
|
|
1186
|
+
uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
|
|
1187
|
+
|
|
1188
|
+
// Add API headers
|
|
1189
|
+
if let apiKey = EnvironmentConfig.apiKey,
|
|
1190
|
+
let apiSecret = EnvironmentConfig.apiSecret {
|
|
1191
|
+
uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
|
|
1192
|
+
uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
let emailPrefix = email?.components(separatedBy: "@").first ?? ""
|
|
1196
|
+
|
|
1197
|
+
var params: [String: Any] = [
|
|
1198
|
+
// "name": emailPrefix,
|
|
1199
|
+
"name": !(request.name?.isEmpty ?? true) ? request.name! : (emailPrefix),
|
|
1200
|
+
"email": email ?? "",
|
|
1201
|
+
"description": "Test Description",
|
|
1202
|
+
"currency": "usd",
|
|
1203
|
+
"account_type": accountType?.lowercased() ?? "",
|
|
1204
|
+
"routing_number": routingNumber ?? "",
|
|
1205
|
+
"account_number": accountNumber ?? "",
|
|
1206
|
+
"payment_mode": "auth_and_capture",
|
|
1207
|
+
"payment_intent": UserStoreSingleton.shared.paymentIntent ?? "",
|
|
1208
|
+
"levelIndicator": 1,
|
|
1209
|
+
"save_account": 1,
|
|
1210
|
+
"payment_method": "ach"
|
|
1211
|
+
]
|
|
1212
|
+
|
|
1213
|
+
if let customerId = customerId {
|
|
1214
|
+
params["customer"] = customerId
|
|
1215
|
+
} else {
|
|
1216
|
+
params["username"] = emailPrefix
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
if customerId == nil {
|
|
1220
|
+
params["create_customer"] = "1"
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
// Add these if recurring is enabled
|
|
1224
|
+
if let req = request, req.is_recurring == true {
|
|
1225
|
+
if let recurringType = req.recurringStartDateType, recurringType == .custom {
|
|
1226
|
+
// Only send start_date if type is .custom and field is not empty
|
|
1227
|
+
if let startDateText = startDate, !startDateText.isEmpty {
|
|
1228
|
+
let inputFormatter = DateFormatter()
|
|
1229
|
+
inputFormatter.dateFormat = "dd/MM/yyyy"
|
|
1230
|
+
|
|
1231
|
+
let outputFormatter = DateFormatter()
|
|
1232
|
+
outputFormatter.dateFormat = "MM/dd/yyyy"
|
|
1233
|
+
|
|
1234
|
+
if let date = inputFormatter.date(from: startDateText) {
|
|
1235
|
+
let apiFormattedDate = outputFormatter.string(from: date)
|
|
1236
|
+
params["start_date"] = apiFormattedDate
|
|
1237
|
+
} else {
|
|
1238
|
+
print("Invalid date format in startDateText")
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
params["interval"] = chosenPlan?.lowercased()
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// ✅ Include metadata only if it has at least 1 key-value pair
|
|
1247
|
+
if let metadata = request?.metadata, !metadata.isEmpty {
|
|
1248
|
+
params["metadata"] = metadata
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
print(params)
|
|
1252
|
+
|
|
1253
|
+
do {
|
|
1254
|
+
let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
|
|
1255
|
+
uRLRequest.httpBody = jsonData
|
|
1256
|
+
if let jsonString = String(data: jsonData, encoding: .utf8) {
|
|
1257
|
+
print("JSON Payload: \(jsonString)")
|
|
1258
|
+
}
|
|
1259
|
+
} catch let error {
|
|
1260
|
+
print("Error creating JSON data: \(error)")
|
|
1261
|
+
hideLoadingIndicator()
|
|
1262
|
+
return
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
let session = URLSession.shared
|
|
1266
|
+
let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
|
|
1267
|
+
|
|
1268
|
+
DispatchQueue.main.async {
|
|
1269
|
+
self.hideLoadingIndicator() // Stop loader when response is received
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
if let error = error {
|
|
1273
|
+
self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
|
|
1274
|
+
return
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
guard let httpResponse = serviceResponse as? HTTPURLResponse else {
|
|
1278
|
+
self.presentPaymentErrorVC(errorMessage: "Invalid response")
|
|
1279
|
+
return
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
|
|
1283
|
+
if let data = serviceData {
|
|
1284
|
+
do {
|
|
1285
|
+
if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
|
|
1286
|
+
print("Response Data: \(responseObject)")
|
|
1287
|
+
|
|
1288
|
+
// Check if status is 0 and handle the error
|
|
1289
|
+
if let status = responseObject["status"] as? Int, status == 0 {
|
|
1290
|
+
let errorMessage = responseObject["message"] as? String ?? "Unknown error"
|
|
1291
|
+
self.presentPaymentErrorVC(errorMessage: errorMessage)
|
|
1292
|
+
} else {
|
|
1293
|
+
DispatchQueue.main.async {
|
|
1294
|
+
if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
|
|
1295
|
+
paymentDoneVC.chargeData = responseObject
|
|
1296
|
+
paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
|
|
1297
|
+
paymentDoneVC.easyPayDelegate = self.easyPayDelegate
|
|
1298
|
+
paymentDoneVC.bankPaymentParams = params
|
|
1299
|
+
paymentDoneVC.request = self.request
|
|
1300
|
+
self.navigationController?.pushViewController(paymentDoneVC, animated: true)
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
} else {
|
|
1305
|
+
self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
|
|
1306
|
+
}
|
|
1307
|
+
} catch let jsonError {
|
|
1308
|
+
self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
|
|
1309
|
+
}
|
|
1310
|
+
} else {
|
|
1311
|
+
self.presentPaymentErrorVC(errorMessage: "No data received")
|
|
1312
|
+
}
|
|
1313
|
+
} else {
|
|
1314
|
+
if let data = serviceData,
|
|
1315
|
+
let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
|
|
1316
|
+
let message = responseObj["message"] as? String {
|
|
1317
|
+
self.presentPaymentErrorVC(errorMessage: message)
|
|
1318
|
+
} else {
|
|
1319
|
+
self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
task.resume()
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
//MARK: - GrailPay Account Charge Api if user saved account
|
|
1327
|
+
func grailPayAccountChargeApi(customerId: String?) {
|
|
1328
|
+
showLoadingIndicator()
|
|
1329
|
+
|
|
1330
|
+
let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.achCharge.path()
|
|
1331
|
+
|
|
1332
|
+
guard let serviceURL = URL(string: fullURL) else {
|
|
1333
|
+
print("Invalid URL")
|
|
1334
|
+
hideLoadingIndicator()
|
|
1335
|
+
return
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
var uRLRequest = URLRequest(url: serviceURL)
|
|
1339
|
+
uRLRequest.httpMethod = "POST"
|
|
1340
|
+
uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
1341
|
+
|
|
1342
|
+
let token = UserStoreSingleton.shared.clientToken
|
|
1343
|
+
print("Setting clientToken header: \(token ?? "None")")
|
|
1344
|
+
uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
|
|
1345
|
+
|
|
1346
|
+
if let apiKey = EnvironmentConfig.apiKey {
|
|
1347
|
+
uRLRequest.addValue(apiKey, forHTTPHeaderField: "X-Api-Key")
|
|
1348
|
+
}
|
|
1349
|
+
if let apiSecret = EnvironmentConfig.apiSecret {
|
|
1350
|
+
uRLRequest.addValue(apiSecret, forHTTPHeaderField: "X-Api-Secret")
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
let emailPrefix = userEmail?.components(separatedBy: "@").first ?? ""
|
|
1354
|
+
|
|
1355
|
+
var params: [String: Any] = [
|
|
1356
|
+
"account_id": self.grailPayAccountID ?? "",
|
|
1357
|
+
"account_type": self.selectedGrailPayAccountType ?? "",
|
|
1358
|
+
"name": self.selectedGrailPayAccountName ?? "",
|
|
1359
|
+
"save_account": 1,
|
|
1360
|
+
"is_default": 1,
|
|
1361
|
+
"customer_id": customerId ?? "",
|
|
1362
|
+
"email": userEmail ?? "",
|
|
1363
|
+
"create_customer": "1",
|
|
1364
|
+
]
|
|
1365
|
+
|
|
1366
|
+
if let customerId = customerId {
|
|
1367
|
+
params["customer"] = customerId
|
|
1368
|
+
} else {
|
|
1369
|
+
params["username"] = emailPrefix
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
if customerId == nil {
|
|
1373
|
+
params["create_customer"] = "1"
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
// // Billing Info
|
|
1377
|
+
// if let visibility = visibility, visibility.billing == true,
|
|
1378
|
+
// let billing = billingInfo, !billing.isEmpty {
|
|
1379
|
+
// var billingDict: [String: Any] = [:]
|
|
1380
|
+
// billing.forEach { billingDict[$0.name] = $0.value }
|
|
1381
|
+
//
|
|
1382
|
+
// params["address"] = billingDict["address"] as? String ?? ""
|
|
1383
|
+
// params["country"] = billingDict["country"] as? String ?? ""
|
|
1384
|
+
// params["state"] = billingDict["state"] as? String ?? ""
|
|
1385
|
+
// params["city"] = billingDict["city"] as? String ?? ""
|
|
1386
|
+
// params["zip"] = billingDict["postal_code"] as? String ?? ""
|
|
1387
|
+
// }
|
|
1388
|
+
|
|
1389
|
+
// Always include Billing Info if available
|
|
1390
|
+
if let billing = billingInfo, !billing.isEmpty {
|
|
1391
|
+
var billingDict: [String: Any] = [:]
|
|
1392
|
+
billing.forEach { billingDict[$0.name] = $0.value }
|
|
1393
|
+
|
|
1394
|
+
params["address"] = billingDict["address"] as? String ?? ""
|
|
1395
|
+
params["country"] = billingDict["country"] as? String ?? ""
|
|
1396
|
+
params["state"] = billingDict["state"] as? String ?? ""
|
|
1397
|
+
params["city"] = billingDict["city"] as? String ?? ""
|
|
1398
|
+
params["zip"] = billingDict["postal_code"] as? String ?? ""
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
// // Additional Info or default description
|
|
1402
|
+
// var descriptionValue: String = "Hosted payment checkout" // default
|
|
1403
|
+
// if let visibility = visibility, visibility.additional == true,
|
|
1404
|
+
// let additional = additionalInfo, !additional.isEmpty {
|
|
1405
|
+
//
|
|
1406
|
+
// var additionalDict: [String: Any] = [:]
|
|
1407
|
+
// additional.forEach { additionalDict[$0.name] = $0.value }
|
|
1408
|
+
//
|
|
1409
|
+
// if let desc = additionalDict["description"] as? String, !desc.isEmpty {
|
|
1410
|
+
// descriptionValue = desc
|
|
1411
|
+
// }
|
|
1412
|
+
//
|
|
1413
|
+
// if let phone = additionalDict["phone_number"] as? String, !phone.isEmpty {
|
|
1414
|
+
// params["phone_number"] = phone
|
|
1415
|
+
// }
|
|
1416
|
+
// }
|
|
1417
|
+
// params["description"] = descriptionValue
|
|
1418
|
+
|
|
1419
|
+
// Always include Additional Info if available
|
|
1420
|
+
var descriptionValue: String = "Hosted payment checkout"
|
|
1421
|
+
if let additional = additionalInfo, !additional.isEmpty {
|
|
1422
|
+
var additionalDict: [String: Any] = [:]
|
|
1423
|
+
additional.forEach { additionalDict[$0.name] = $0.value }
|
|
1424
|
+
|
|
1425
|
+
if let desc = additionalDict["description"] as? String, !desc.isEmpty {
|
|
1426
|
+
descriptionValue = desc
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
if let phone = additionalDict["phone_number"] as? String, !phone.isEmpty {
|
|
1430
|
+
params["phone_number"] = phone
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
params["description"] = descriptionValue
|
|
1434
|
+
|
|
1435
|
+
// Add these if recurring is enabled
|
|
1436
|
+
if let req = request, req.is_recurring == true {
|
|
1437
|
+
if let recurringType = req.recurringStartDateType, recurringType == .custom {
|
|
1438
|
+
// Only send start_date if type is .custom and field is not empty
|
|
1439
|
+
if let startDateText = startDate, !startDateText.isEmpty {
|
|
1440
|
+
let inputFormatter = DateFormatter()
|
|
1441
|
+
inputFormatter.dateFormat = "dd/MM/yyyy"
|
|
1442
|
+
|
|
1443
|
+
let outputFormatter = DateFormatter()
|
|
1444
|
+
outputFormatter.dateFormat = "MM/dd/yyyy"
|
|
1445
|
+
|
|
1446
|
+
if let date = inputFormatter.date(from: startDateText) {
|
|
1447
|
+
let apiFormattedDate = outputFormatter.string(from: date)
|
|
1448
|
+
params["start_date"] = apiFormattedDate
|
|
1449
|
+
} else {
|
|
1450
|
+
print("Invalid date format in startDateText")
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
params["interval"] = chosenPlan?.lowercased()
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// ✅ Include metadata only if it has at least 1 key-value pair
|
|
1459
|
+
if let metadata = request?.metadata, !metadata.isEmpty {
|
|
1460
|
+
params["metadata"] = metadata
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
print(params)
|
|
1464
|
+
|
|
1465
|
+
do {
|
|
1466
|
+
let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
|
|
1467
|
+
uRLRequest.httpBody = jsonData
|
|
1468
|
+
if let jsonString = String(data: jsonData, encoding: .utf8) {
|
|
1469
|
+
print("JSON Payload: \(jsonString)")
|
|
1470
|
+
}
|
|
1471
|
+
} catch let error {
|
|
1472
|
+
print("Error creating JSON data: \(error)")
|
|
1473
|
+
hideLoadingIndicator()
|
|
1474
|
+
return
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
let session = URLSession.shared
|
|
1478
|
+
let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
|
|
1479
|
+
|
|
1480
|
+
DispatchQueue.main.async {
|
|
1481
|
+
self.hideLoadingIndicator() // Stop loader when response is received
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
if let error = error {
|
|
1485
|
+
self.presentPaymentErrorVC(errorMessage: error.localizedDescription)
|
|
1486
|
+
return
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
guard let httpResponse = serviceResponse as? HTTPURLResponse else {
|
|
1490
|
+
self.presentPaymentErrorVC(errorMessage: "Invalid response")
|
|
1491
|
+
return
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
|
|
1495
|
+
if let data = serviceData {
|
|
1496
|
+
do {
|
|
1497
|
+
if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
|
|
1498
|
+
print("Response Data: \(responseObject)")
|
|
1499
|
+
|
|
1500
|
+
// Check if status is 0 and handle the error
|
|
1501
|
+
if let status = responseObject["status"] as? Int, status == 0 {
|
|
1502
|
+
let errorMessage = responseObject["message"] as? String ?? "Unknown error"
|
|
1503
|
+
self.presentPaymentErrorVC(errorMessage: errorMessage)
|
|
1504
|
+
} else {
|
|
1505
|
+
DispatchQueue.main.async {
|
|
1506
|
+
if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "PaymentDoneVC") as? PaymentDoneVC {
|
|
1507
|
+
paymentDoneVC.chargeData = responseObject
|
|
1508
|
+
paymentDoneVC.selectedPaymentMethod = self.selectedPaymentMethod
|
|
1509
|
+
paymentDoneVC.easyPayDelegate = self.easyPayDelegate
|
|
1510
|
+
paymentDoneVC.bankPaymentParams = params
|
|
1511
|
+
// Pass billing and additional info
|
|
1512
|
+
// Conditionally pass raw FieldItem array
|
|
1513
|
+
paymentDoneVC.visibility = self.visibility
|
|
1514
|
+
paymentDoneVC.request = self.request
|
|
1515
|
+
|
|
1516
|
+
// if self.visibility?.billing == true {
|
|
1517
|
+
paymentDoneVC.billingInfoData = self.billingInfo
|
|
1518
|
+
var billingDict: [String: Any] = [:]
|
|
1519
|
+
self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
|
|
1520
|
+
paymentDoneVC.billingInfo = billingDict
|
|
1521
|
+
// }
|
|
1522
|
+
|
|
1523
|
+
// if self.visibility?.additional == true {
|
|
1524
|
+
paymentDoneVC.additionalInfoData = self.additionalInfo
|
|
1525
|
+
var additionalDict: [String: Any] = [:]
|
|
1526
|
+
self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
|
|
1527
|
+
paymentDoneVC.additionalInfo = additionalDict
|
|
1528
|
+
// }
|
|
1529
|
+
self.navigationController?.pushViewController(paymentDoneVC, animated: true)
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
} else {
|
|
1534
|
+
self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
|
|
1535
|
+
}
|
|
1536
|
+
} catch let jsonError {
|
|
1537
|
+
self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
|
|
1538
|
+
}
|
|
1539
|
+
} else {
|
|
1540
|
+
self.presentPaymentErrorVC(errorMessage: "No data received")
|
|
1541
|
+
}
|
|
1542
|
+
} else {
|
|
1543
|
+
if let data = serviceData,
|
|
1544
|
+
let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
|
|
1545
|
+
let message = responseObj["message"] as? String {
|
|
1546
|
+
self.presentPaymentErrorVC(errorMessage: message)
|
|
1547
|
+
} else {
|
|
1548
|
+
self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
task.resume()
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
// MARK: - 3DS Functionality
|
|
1556
|
+
|
|
1557
|
+
// MARK: - Credit Card Charge Api If Billing info is nil and Without Login.
|
|
1558
|
+
func threeDSecurePaymentApi(customerId: String?) {
|
|
1559
|
+
showLoadingIndicator()
|
|
1560
|
+
|
|
1561
|
+
let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.threeDSecure.path()
|
|
1562
|
+
|
|
1563
|
+
guard let serviceURL = URL(string: fullURL) else {
|
|
1564
|
+
print("Invalid URL")
|
|
1565
|
+
hideLoadingIndicator()
|
|
1566
|
+
return
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
var uRLRequest = URLRequest(url: serviceURL)
|
|
1570
|
+
uRLRequest.httpMethod = "POST"
|
|
1571
|
+
uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
1572
|
+
|
|
1573
|
+
let token = UserStoreSingleton.shared.clientToken
|
|
1574
|
+
print("Setting clientToken header: \(token ?? "None")")
|
|
1575
|
+
uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
|
|
1576
|
+
|
|
1577
|
+
// Add API headers
|
|
1578
|
+
if let apiKey = EnvironmentConfig.apiKey,
|
|
1579
|
+
let apiSecret = EnvironmentConfig.apiSecret {
|
|
1580
|
+
uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
|
|
1581
|
+
uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
var params: [String: Any] = [
|
|
1585
|
+
"name": nameOnCard ?? "",
|
|
1586
|
+
"email": userEmail ?? "",
|
|
1587
|
+
"card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
|
|
1588
|
+
"cardholder_name": nameOnCard ?? "",
|
|
1589
|
+
"exp_month": expiryDate?.components(separatedBy: "/").first ?? "",
|
|
1590
|
+
"exp_year": expiryDate?.components(separatedBy: "/").last ?? "",
|
|
1591
|
+
"cvc": cvv ?? "",
|
|
1592
|
+
"description": "payment checkout",
|
|
1593
|
+
"currency": "usd",
|
|
1594
|
+
"payment_method": "card",
|
|
1595
|
+
"tokenize": request.tokenOnly ?? false,
|
|
1596
|
+
"save_card": 1,
|
|
1597
|
+
"is_default": "1",
|
|
1598
|
+
"customer_id": customerId ?? ""
|
|
1599
|
+
]
|
|
1600
|
+
|
|
1601
|
+
if customerId == nil {
|
|
1602
|
+
params["create_customer"] = "1"
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
// Add these if recurring is enabled
|
|
1606
|
+
if let req = request, req.is_recurring == true {
|
|
1607
|
+
if let recurringType = req.recurringStartDateType, recurringType == .custom {
|
|
1608
|
+
// Only send start_date if type is .custom and field is not empty
|
|
1609
|
+
if let startDateText = startDate, !startDateText.isEmpty {
|
|
1610
|
+
let inputFormatter = DateFormatter()
|
|
1611
|
+
inputFormatter.dateFormat = "dd/MM/yyyy"
|
|
1612
|
+
|
|
1613
|
+
let outputFormatter = DateFormatter()
|
|
1614
|
+
outputFormatter.dateFormat = "MM/dd/yyyy"
|
|
1615
|
+
|
|
1616
|
+
if let date = inputFormatter.date(from: startDateText) {
|
|
1617
|
+
let apiFormattedDate = outputFormatter.string(from: date)
|
|
1618
|
+
params["start_date"] = apiFormattedDate
|
|
1619
|
+
} else {
|
|
1620
|
+
print("Invalid date format in startDateText")
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
params["interval"] = chosenPlan?.lowercased()
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
// ✅ Include metadata only if it has at least 1 key-value pair
|
|
1629
|
+
if let metadata = request?.metadata, !metadata.isEmpty {
|
|
1630
|
+
params["metadata"] = metadata
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
print(params)
|
|
1634
|
+
|
|
1635
|
+
do {
|
|
1636
|
+
let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
|
|
1637
|
+
uRLRequest.httpBody = jsonData
|
|
1638
|
+
if let jsonString = String(data: jsonData, encoding: .utf8) {
|
|
1639
|
+
print("JSON Payload: \(jsonString)")
|
|
1640
|
+
}
|
|
1641
|
+
} catch let error {
|
|
1642
|
+
print("Error creating JSON data: \(error)")
|
|
1643
|
+
hideLoadingIndicator()
|
|
1644
|
+
return
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
let session = URLSession.shared
|
|
1648
|
+
let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
|
|
1649
|
+
|
|
1650
|
+
DispatchQueue.main.async {
|
|
1651
|
+
self.hideLoadingIndicator() // Stop loader when response is received
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
if let error = error {
|
|
1655
|
+
print("Error: \(error.localizedDescription)")
|
|
1656
|
+
return
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
guard let httpResponse = serviceResponse as? HTTPURLResponse else {
|
|
1660
|
+
print("Invalid response")
|
|
1661
|
+
return
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
|
|
1665
|
+
if let data = serviceData {
|
|
1666
|
+
do {
|
|
1667
|
+
if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
|
|
1668
|
+
print("Response Data: \(responseObject)")
|
|
1669
|
+
|
|
1670
|
+
// Check if status is 0 and handle the error
|
|
1671
|
+
if let status = responseObject["status"] as? Int, status == 0 {
|
|
1672
|
+
let errorMessage = responseObject["message"] as? String ?? "Unknown error"
|
|
1673
|
+
self.presentPaymentErrorVC(errorMessage: errorMessage)
|
|
1674
|
+
} else {
|
|
1675
|
+
DispatchQueue.main.async {
|
|
1676
|
+
if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "ThreeDSecurePaymentDoneVC") as? ThreeDSecurePaymentDoneVC {
|
|
1677
|
+
|
|
1678
|
+
let urlString = responseObject["redirect_url"] as? String ?? responseObject["location_url"] as? String ?? ""
|
|
1679
|
+
paymentDoneVC.redirectURL = urlString
|
|
1680
|
+
paymentDoneVC.chargeData = responseObject
|
|
1681
|
+
paymentDoneVC.easyPayDelegate = self.easyPayDelegate
|
|
1682
|
+
paymentDoneVC.cardApiParams = params
|
|
1683
|
+
paymentDoneVC.request = self.request
|
|
1684
|
+
self.navigationController?.pushViewController(paymentDoneVC, animated: true)
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
} else {
|
|
1689
|
+
self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
|
|
1690
|
+
}
|
|
1691
|
+
} catch let jsonError {
|
|
1692
|
+
self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
|
|
1693
|
+
}
|
|
1694
|
+
} else {
|
|
1695
|
+
self.presentPaymentErrorVC(errorMessage: "No data received")
|
|
1696
|
+
}
|
|
1697
|
+
} else {
|
|
1698
|
+
if let data = serviceData,
|
|
1699
|
+
let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
|
|
1700
|
+
let message = responseObj["message"] as? String {
|
|
1701
|
+
self.presentPaymentErrorVC(errorMessage: message)
|
|
1702
|
+
} else {
|
|
1703
|
+
self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
task.resume()
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
// MARK: - Credit Card Charge Api If Billing info is not nil and Without Login.
|
|
1711
|
+
func threeDSecurePaymentSavedCardApi(customerId: String?) {
|
|
1712
|
+
showLoadingIndicator()
|
|
1713
|
+
|
|
1714
|
+
let fullURL = EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.threeDSecure.path()
|
|
1715
|
+
|
|
1716
|
+
guard let serviceURL = URL(string: fullURL) else {
|
|
1717
|
+
print("Invalid URL")
|
|
1718
|
+
hideLoadingIndicator()
|
|
1719
|
+
return
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
var uRLRequest = URLRequest(url: serviceURL)
|
|
1723
|
+
uRLRequest.httpMethod = "POST"
|
|
1724
|
+
uRLRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
1725
|
+
|
|
1726
|
+
let token = UserStoreSingleton.shared.clientToken
|
|
1727
|
+
print("Setting clientToken header: \(token ?? "None")")
|
|
1728
|
+
uRLRequest.addValue(token ?? "", forHTTPHeaderField: "Client-Token")
|
|
1729
|
+
|
|
1730
|
+
// Add API headers
|
|
1731
|
+
if let apiKey = EnvironmentConfig.apiKey,
|
|
1732
|
+
let apiSecret = EnvironmentConfig.apiSecret {
|
|
1733
|
+
uRLRequest.addValue(apiKey, forHTTPHeaderField: "x-api-key")
|
|
1734
|
+
uRLRequest.addValue(apiSecret, forHTTPHeaderField: "x-api-secret")
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
var params: [String: Any] = [
|
|
1738
|
+
"name": nameOnCard ?? "",
|
|
1739
|
+
"email": email ?? "",
|
|
1740
|
+
"card_number": cardNumber?.replacingOccurrences(of: " ", with: "") ?? "",
|
|
1741
|
+
"cardholder_name": nameOnCard ?? "",
|
|
1742
|
+
"exp_month": expiryDate?.components(separatedBy: "/").first ?? "",
|
|
1743
|
+
"exp_year": expiryDate?.components(separatedBy: "/").last ?? "",
|
|
1744
|
+
"cvc": cvv ?? "",
|
|
1745
|
+
"currency": "usd",
|
|
1746
|
+
"tokenize": request.tokenOnly ?? false,
|
|
1747
|
+
"save_card": "1",
|
|
1748
|
+
"is_default": "1",
|
|
1749
|
+
"customer_id": customerId ?? "",
|
|
1750
|
+
"customer" : customerId ?? ""
|
|
1751
|
+
]
|
|
1752
|
+
|
|
1753
|
+
if customerId == nil {
|
|
1754
|
+
params["create_customer"] = "1"
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
// Always include Billing Info if available
|
|
1758
|
+
if let billing = billingInfo, !billing.isEmpty {
|
|
1759
|
+
var billingDict: [String: Any] = [:]
|
|
1760
|
+
billing.forEach { billingDict[$0.name] = $0.value }
|
|
1761
|
+
|
|
1762
|
+
params["address"] = billingDict["address"] as? String ?? ""
|
|
1763
|
+
params["country"] = billingDict["country"] as? String ?? ""
|
|
1764
|
+
params["state"] = billingDict["state"] as? String ?? ""
|
|
1765
|
+
params["city"] = billingDict["city"] as? String ?? ""
|
|
1766
|
+
params["zip"] = billingDict["postal_code"] as? String ?? ""
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
// Always include Additional Info if available
|
|
1770
|
+
var descriptionValue: String = "Hosted payment checkout"
|
|
1771
|
+
if let additional = additionalInfo, !additional.isEmpty {
|
|
1772
|
+
var additionalDict: [String: Any] = [:]
|
|
1773
|
+
additional.forEach { additionalDict[$0.name] = $0.value }
|
|
1774
|
+
|
|
1775
|
+
if let desc = additionalDict["description"] as? String, !desc.isEmpty {
|
|
1776
|
+
descriptionValue = desc
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
if let phone = additionalDict["phone_number"] as? String, !phone.isEmpty {
|
|
1780
|
+
params["phone_number"] = phone
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
params["description"] = descriptionValue
|
|
1784
|
+
|
|
1785
|
+
// Add these if recurring is enabled
|
|
1786
|
+
if let req = request, req.is_recurring == true {
|
|
1787
|
+
if let recurringType = req.recurringStartDateType, recurringType == .custom {
|
|
1788
|
+
// Only send start_date if type is .custom and field is not empty
|
|
1789
|
+
if let startDateText = startDate, !startDateText.isEmpty {
|
|
1790
|
+
let inputFormatter = DateFormatter()
|
|
1791
|
+
inputFormatter.dateFormat = "dd/MM/yyyy"
|
|
1792
|
+
|
|
1793
|
+
let outputFormatter = DateFormatter()
|
|
1794
|
+
outputFormatter.dateFormat = "MM/dd/yyyy"
|
|
1795
|
+
|
|
1796
|
+
if let date = inputFormatter.date(from: startDateText) {
|
|
1797
|
+
let apiFormattedDate = outputFormatter.string(from: date)
|
|
1798
|
+
params["start_date"] = apiFormattedDate
|
|
1799
|
+
} else {
|
|
1800
|
+
print("Invalid date format in startDateText")
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
params["interval"] = chosenPlan?.lowercased()
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
// ✅ Include metadata only if it has at least 1 key-value pair
|
|
1809
|
+
if let metadata = request?.metadata, !metadata.isEmpty {
|
|
1810
|
+
params["metadata"] = metadata
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
print(params)
|
|
1814
|
+
|
|
1815
|
+
do {
|
|
1816
|
+
let jsonData = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
|
|
1817
|
+
uRLRequest.httpBody = jsonData
|
|
1818
|
+
if let jsonString = String(data: jsonData, encoding: .utf8) {
|
|
1819
|
+
print("JSON Payload: \(jsonString)")
|
|
1820
|
+
}
|
|
1821
|
+
} catch let error {
|
|
1822
|
+
print("Error creating JSON data: \(error)")
|
|
1823
|
+
hideLoadingIndicator()
|
|
1824
|
+
return
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
let session = URLSession.shared
|
|
1828
|
+
let task = session.dataTask(with: uRLRequest) { (serviceData, serviceResponse, error) in
|
|
1829
|
+
|
|
1830
|
+
DispatchQueue.main.async {
|
|
1831
|
+
self.hideLoadingIndicator() // Stop loader when response is received
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
if let error = error {
|
|
1835
|
+
print("Error: \(error.localizedDescription)")
|
|
1836
|
+
return
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
guard let httpResponse = serviceResponse as? HTTPURLResponse else {
|
|
1840
|
+
print("Invalid response")
|
|
1841
|
+
return
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
|
|
1845
|
+
if let data = serviceData {
|
|
1846
|
+
do {
|
|
1847
|
+
if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
|
|
1848
|
+
print("Response Data: \(responseObject)")
|
|
1849
|
+
|
|
1850
|
+
// Check if status is 0 and handle the error
|
|
1851
|
+
if let status = responseObject["status"] as? Int, status == 0 {
|
|
1852
|
+
let errorMessage = responseObject["message"] as? String ?? "Unknown error"
|
|
1853
|
+
self.presentPaymentErrorVC(errorMessage: errorMessage)
|
|
1854
|
+
} else {
|
|
1855
|
+
DispatchQueue.main.async {
|
|
1856
|
+
if let paymentDoneVC = self.storyboard?.instantiateViewController(withIdentifier: "ThreeDSecurePaymentDoneVC") as? ThreeDSecurePaymentDoneVC {
|
|
1857
|
+
|
|
1858
|
+
let urlString = responseObject["redirect_url"] as? String ?? responseObject["location_url"] as? String ?? ""
|
|
1859
|
+
paymentDoneVC.redirectURL = urlString
|
|
1860
|
+
paymentDoneVC.chargeData = responseObject
|
|
1861
|
+
paymentDoneVC.easyPayDelegate = self.easyPayDelegate
|
|
1862
|
+
// Pass billing and additional info
|
|
1863
|
+
// Conditionally pass raw FieldItem array
|
|
1864
|
+
paymentDoneVC.visibility = self.visibility
|
|
1865
|
+
paymentDoneVC.amount = self.amount
|
|
1866
|
+
paymentDoneVC.cardApiParams = params
|
|
1867
|
+
paymentDoneVC.request = self.request
|
|
1868
|
+
|
|
1869
|
+
// if self.visibility?.billing == true {
|
|
1870
|
+
paymentDoneVC.billingInfoData = self.billingInfo
|
|
1871
|
+
var billingDict: [String: Any] = [:]
|
|
1872
|
+
self.billingInfo?.forEach { billingDict[$0.name] = $0.value }
|
|
1873
|
+
paymentDoneVC.billingInfo = billingDict
|
|
1874
|
+
// }
|
|
1875
|
+
|
|
1876
|
+
// if self.visibility?.additional == true {
|
|
1877
|
+
paymentDoneVC.additionalInfoData = self.additionalInfo
|
|
1878
|
+
var additionalDict: [String: Any] = [:]
|
|
1879
|
+
self.additionalInfo?.forEach { additionalDict[$0.name] = $0.value }
|
|
1880
|
+
paymentDoneVC.additionalInfo = additionalDict
|
|
1881
|
+
// }
|
|
1882
|
+
self.navigationController?.pushViewController(paymentDoneVC, animated: true)
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
} else {
|
|
1887
|
+
self.presentPaymentErrorVC(errorMessage: "Invalid JSON format")
|
|
1888
|
+
}
|
|
1889
|
+
} catch let jsonError {
|
|
1890
|
+
self.presentPaymentErrorVC(errorMessage: "Error parsing JSON: \(jsonError)")
|
|
1891
|
+
}
|
|
1892
|
+
} else {
|
|
1893
|
+
self.presentPaymentErrorVC(errorMessage: "No data received")
|
|
1894
|
+
}
|
|
1895
|
+
} else {
|
|
1896
|
+
if let data = serviceData,
|
|
1897
|
+
let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
|
|
1898
|
+
let message = responseObj["message"] as? String {
|
|
1899
|
+
self.presentPaymentErrorVC(errorMessage: message)
|
|
1900
|
+
} else {
|
|
1901
|
+
self.presentPaymentErrorVC(errorMessage: "HTTP Status Code: \(httpResponse.statusCode)")
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
task.resume()
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
@IBAction func actionBtnCancel(_ sender: UIButton) {
|
|
1909
|
+
stopTimer()
|
|
1910
|
+
for viewController in self.navigationController?.viewControllers ?? [] {
|
|
1911
|
+
if viewController is PaymentInfoVC {
|
|
1912
|
+
self.navigationController?.popToViewController(viewController, animated: true)
|
|
1913
|
+
break
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
@IBAction func actionBtnResendOTP(_ sender: UIButton) {
|
|
1919
|
+
startTimer()
|
|
1920
|
+
btnResendOTP.isHidden = true
|
|
1921
|
+
imgEsclamationMark.isHidden = false
|
|
1922
|
+
lblOtpTimer.isHidden = false
|
|
1923
|
+
lblUntillResendOtp.isHidden = false
|
|
1924
|
+
|
|
1925
|
+
// Call email verification APi
|
|
1926
|
+
emailVerificationApi()
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
@IBAction func actionBtnConfirmCode(_ sender: UIButton) {
|
|
1930
|
+
otpVerificationApi()
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
extension OTPVerificationVC: UITextFieldDelegate {
|
|
1936
|
+
|
|
1937
|
+
func textFieldDidChangeSelection(_ textField: UITextField) {
|
|
1938
|
+
guard let otpCode = textField.text, otpCode.count >= 6, textField.textContentType == .oneTimeCode else { return }
|
|
1939
|
+
txtFieldOTPText1.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 0)])
|
|
1940
|
+
txtFieldOTPText2.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 1)])
|
|
1941
|
+
txtFieldOTPText3.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 2)])
|
|
1942
|
+
txtFieldOTPText4.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 3)])
|
|
1943
|
+
txtFieldOTPText5.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 4)])
|
|
1944
|
+
txtFieldOTPText6.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 5)])
|
|
1945
|
+
txtFieldOTPText6.becomeFirstResponder()
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
|
1949
|
+
if [txtFieldOTPText1, txtFieldOTPText2, txtFieldOTPText3, txtFieldOTPText4, txtFieldOTPText5, txtFieldOTPText6].contains(textField) {
|
|
1950
|
+
if string.count == 1 {
|
|
1951
|
+
// Single character input
|
|
1952
|
+
textField.text = string
|
|
1953
|
+
switch textField {
|
|
1954
|
+
case txtFieldOTPText1:
|
|
1955
|
+
txtFieldOTPText2.becomeFirstResponder()
|
|
1956
|
+
case txtFieldOTPText2:
|
|
1957
|
+
txtFieldOTPText3.becomeFirstResponder()
|
|
1958
|
+
case txtFieldOTPText3:
|
|
1959
|
+
txtFieldOTPText4.becomeFirstResponder()
|
|
1960
|
+
case txtFieldOTPText4:
|
|
1961
|
+
txtFieldOTPText5.becomeFirstResponder()
|
|
1962
|
+
case txtFieldOTPText5:
|
|
1963
|
+
txtFieldOTPText6.becomeFirstResponder()
|
|
1964
|
+
case txtFieldOTPText6:
|
|
1965
|
+
txtFieldOTPText6.resignFirstResponder()
|
|
1966
|
+
// Call OTP verification API when the last field is filled
|
|
1967
|
+
if getCombinedOTP().count == 6 {
|
|
1968
|
+
// otpVerificationApi()
|
|
1969
|
+
}
|
|
1970
|
+
default:
|
|
1971
|
+
break
|
|
1972
|
+
}
|
|
1973
|
+
updateViewColors()
|
|
1974
|
+
return false
|
|
1975
|
+
} else if string.isEmpty {
|
|
1976
|
+
// Handle backspace
|
|
1977
|
+
textField.text = ""
|
|
1978
|
+
switch textField {
|
|
1979
|
+
case txtFieldOTPText6:
|
|
1980
|
+
txtFieldOTPText5.becomeFirstResponder()
|
|
1981
|
+
case txtFieldOTPText5:
|
|
1982
|
+
txtFieldOTPText4.becomeFirstResponder()
|
|
1983
|
+
case txtFieldOTPText4:
|
|
1984
|
+
txtFieldOTPText3.becomeFirstResponder()
|
|
1985
|
+
case txtFieldOTPText3:
|
|
1986
|
+
txtFieldOTPText2.becomeFirstResponder()
|
|
1987
|
+
case txtFieldOTPText2:
|
|
1988
|
+
txtFieldOTPText1.becomeFirstResponder()
|
|
1989
|
+
default:
|
|
1990
|
+
break
|
|
1991
|
+
}
|
|
1992
|
+
updateViewColors()
|
|
1993
|
+
return false
|
|
1994
|
+
}
|
|
1995
|
+
return textField.text?.count == 0
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
return true
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
// Update the view colors based on text presence in OTP fields
|
|
2002
|
+
private func updateViewColors() {
|
|
2003
|
+
if let primaryBtnBackgroundColor = UserStoreSingleton.shared.primary_btn_bg_col,
|
|
2004
|
+
let primaryUIColor = UIColor(hex: primaryBtnBackgroundColor),
|
|
2005
|
+
let secondaryFontColor = UserStoreSingleton.shared.secondary_font_col,
|
|
2006
|
+
let secondaryUIColor = UIColor(hex: secondaryFontColor) {
|
|
2007
|
+
|
|
2008
|
+
viewTextOTP1.layer.borderWidth = 1.0
|
|
2009
|
+
viewTextOTP1.layer.borderColor = txtFieldOTPText1.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
|
|
2010
|
+
viewTextOTP2.layer.borderWidth = 1.0
|
|
2011
|
+
viewTextOTP2.layer.borderColor = txtFieldOTPText2.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
|
|
2012
|
+
viewTextOTP3.layer.borderWidth = 1.0
|
|
2013
|
+
viewTextOTP3.layer.borderColor = txtFieldOTPText3.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
|
|
2014
|
+
viewTextOTP4.layer.borderWidth = 1.0
|
|
2015
|
+
viewTextOTP4.layer.borderColor = txtFieldOTPText4.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
|
|
2016
|
+
viewTextOTP5.layer.borderWidth = 1.0
|
|
2017
|
+
viewTextOTP5.layer.borderColor = txtFieldOTPText5.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
|
|
2018
|
+
viewTextOTP6.layer.borderWidth = 1.0
|
|
2019
|
+
viewTextOTP6.layer.borderColor = txtFieldOTPText6.text?.isEmpty == false ? primaryUIColor.cgColor : secondaryUIColor.cgColor
|
|
2020
|
+
}
|
|
2021
|
+
else {
|
|
2022
|
+
viewTextOTP1.layer.borderWidth = 1.0
|
|
2023
|
+
viewTextOTP1.layer.borderColor = (txtFieldOTPText1.text?.isEmpty == false ? UIColor.systemBlue : UIColor.systemGray).cgColor
|
|
2024
|
+
viewTextOTP2.layer.borderWidth = 1.0
|
|
2025
|
+
viewTextOTP2.layer.borderColor = (txtFieldOTPText2.text?.isEmpty == false ? UIColor.systemBlue : UIColor.systemGray).cgColor
|
|
2026
|
+
viewTextOTP3.layer.borderWidth = 1.0
|
|
2027
|
+
viewTextOTP3.layer.borderColor = (txtFieldOTPText3.text?.isEmpty == false ? UIColor.systemBlue : UIColor.systemGray).cgColor
|
|
2028
|
+
viewTextOTP4.layer.borderWidth = 1.0
|
|
2029
|
+
viewTextOTP4.layer.borderColor = (txtFieldOTPText4.text?.isEmpty == false ? UIColor.systemBlue : UIColor.systemGray).cgColor
|
|
2030
|
+
viewTextOTP5.layer.borderWidth = 1.0
|
|
2031
|
+
viewTextOTP5.layer.borderColor = (txtFieldOTPText5.text?.isEmpty == false ? UIColor.systemBlue : UIColor.systemGray).cgColor
|
|
2032
|
+
viewTextOTP6.layer.borderWidth = 1.0
|
|
2033
|
+
viewTextOTP6.layer.borderColor = (txtFieldOTPText6.text?.isEmpty == false ? UIColor.systemBlue : UIColor.systemGray).cgColor
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
@objc func textFieldDidChange(_ textField: UITextField) {
|
|
2038
|
+
guard let otpCode = textField.text, otpCode.count >= 6, textField.textContentType == .oneTimeCode else { return }
|
|
2039
|
+
// Split the OTP into each text field
|
|
2040
|
+
txtFieldOTPText1.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 0)])
|
|
2041
|
+
txtFieldOTPText2.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 1)])
|
|
2042
|
+
txtFieldOTPText3.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 2)])
|
|
2043
|
+
txtFieldOTPText4.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 3)])
|
|
2044
|
+
txtFieldOTPText5.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 4)])
|
|
2045
|
+
txtFieldOTPText6.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 5)])
|
|
2046
|
+
// Automatically move to the last text field
|
|
2047
|
+
txtFieldOTPText6.becomeFirstResponder()
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
func getCombinedOTP() -> String {
|
|
2051
|
+
let otp1 = txtFieldOTPText1.text ?? ""
|
|
2052
|
+
let otp2 = txtFieldOTPText2.text ?? ""
|
|
2053
|
+
let otp3 = txtFieldOTPText3.text ?? ""
|
|
2054
|
+
let otp4 = txtFieldOTPText4.text ?? ""
|
|
2055
|
+
let otp5 = txtFieldOTPText5.text ?? ""
|
|
2056
|
+
let otp6 = txtFieldOTPText6.text ?? ""
|
|
2057
|
+
|
|
2058
|
+
return otp1 + otp2 + otp3 + otp4 + otp5 + otp6
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
|
|
2064
|
+
|
|
2065
|
+
|
|
2066
|
+
|