@jimrising/easymerchantsdk-react-native 1.3.8 → 1.4.1
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/.idea/caches/deviceStreaming.xml +714 -0
- package/.idea/em-MobileCheckoutSDK-ReactNative.iml +9 -0
- package/.idea/misc.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/README.md +140 -81
- package/android/.gradle/8.10/checksums/checksums.lock +0 -0
- package/android/.gradle/8.10/checksums/md5-checksums.bin +0 -0
- package/android/.gradle/8.10/checksums/sha1-checksums.bin +0 -0
- package/android/.gradle/8.10/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/android/.gradle/8.9/checksums/md5-checksums.bin +0 -0
- package/android/.gradle/8.9/checksums/sha1-checksums.bin +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +2 -2
- package/ios/Classes/EasyMerchantSdk.m +106 -55
- package/ios/Classes/EasyMerchantSdk.swift +232 -117
- package/ios/Classes/EasyPayViewController.swift +1 -1
- package/ios/CustomComponents/DatePickerHandler.swift +15 -4
- package/ios/EnvironmentConfig.swift +32 -30
- package/ios/Models/Request.swift +176 -14
- package/ios/Models/Result.swift +15 -35
- package/ios/Pods/ViewControllers/AdditionalInfoVC.swift +851 -342
- package/ios/Pods/ViewControllers/BaseVC.swift +39 -35
- package/ios/Pods/ViewControllers/BillingInfoVC/BillingInfoVC.swift +1975 -170
- package/ios/Pods/ViewControllers/CountryListVC.swift +0 -1
- package/ios/Pods/ViewControllers/EmailVerificationVC.swift +74 -5
- package/ios/Pods/ViewControllers/GrailPayVC.swift +131 -107
- package/ios/Pods/ViewControllers/OTPVerificationVC.swift +305 -107
- package/ios/Pods/ViewControllers/PaymentDoneVC.swift +61 -14
- package/ios/Pods/ViewControllers/PaymentInformation/PaymentInfoVC.swift +1277 -501
- package/ios/Pods/ViewControllers/ThreeDSecurePaymentDoneVC.swift +133 -2
- package/ios/easymerchantsdk.podspec +1 -1
- package/ios/easymerchantsdk.storyboard +713 -590
- package/package.json +2 -2
|
@@ -1,51 +1,67 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
import UIKit
|
|
3
3
|
import React
|
|
4
|
+
//import EasyPay // Import the EasyPay module
|
|
4
5
|
|
|
5
6
|
@objc(EasyMerchantSdkPlugin)
|
|
6
7
|
public class EasyMerchantSdkPlugin: NSObject, RCTBridgeModule {
|
|
7
8
|
|
|
8
9
|
// MARK: - React Native Module Setup
|
|
9
|
-
public static func moduleName() -> String {
|
|
10
|
-
|
|
10
|
+
public static func moduleName() -> String! {
|
|
11
|
+
return "EasyMerchantSdk"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public static func requiresMainQueueSetup() -> Bool {
|
|
15
|
+
return true
|
|
16
|
+
}
|
|
11
17
|
|
|
12
18
|
public var bridge: RCTBridge?
|
|
13
19
|
|
|
14
|
-
// MARK: -
|
|
15
|
-
private var viewController: UIViewController?
|
|
16
|
-
private let viewControllerQueue = DispatchQueue(label: "com.easymerchantsdk.viewController")
|
|
17
|
-
|
|
20
|
+
// MARK: - Stored Promise Callbacks
|
|
18
21
|
private var billingResolver: RCTPromiseResolveBlock?
|
|
19
22
|
private var billingRejecter: RCTPromiseRejectBlock?
|
|
23
|
+
private var paymentReferenceResolver: RCTPromiseResolveBlock?
|
|
24
|
+
private var paymentReferenceRejecter: RCTPromiseRejectBlock?
|
|
20
25
|
|
|
21
|
-
// MARK: -
|
|
26
|
+
// MARK: - View Controller Reference
|
|
27
|
+
private let viewControllerQueue = DispatchQueue(label: "com.easymerchantsdk.viewController")
|
|
28
|
+
private var viewController: UIViewController?
|
|
29
|
+
|
|
30
|
+
// MARK: - Init
|
|
22
31
|
@objc public override init() {
|
|
23
32
|
super.init()
|
|
24
33
|
}
|
|
25
34
|
|
|
26
|
-
// Called when the bridge finishes loading
|
|
27
35
|
public func bridgeDidFinishLoading() {
|
|
28
|
-
if
|
|
29
|
-
print("
|
|
30
|
-
} else {
|
|
31
|
-
print("Bridge initialization failed.")
|
|
36
|
+
if bridge != nil {
|
|
37
|
+
print("✅ EasyMerchantSdkPlugin bridge initialized")
|
|
32
38
|
}
|
|
33
39
|
}
|
|
34
40
|
|
|
35
|
-
//
|
|
36
|
-
@objc public func setViewController(_
|
|
41
|
+
// Expose setter for RN to inject the host view controller
|
|
42
|
+
@objc public func setViewController(_ vc: UIViewController) {
|
|
37
43
|
viewControllerQueue.sync {
|
|
38
|
-
self.viewController =
|
|
44
|
+
self.viewController = vc
|
|
39
45
|
}
|
|
40
|
-
print("ViewController set: \(String(describing: self.viewController))")
|
|
41
46
|
}
|
|
42
47
|
|
|
43
|
-
// MARK: -
|
|
48
|
+
// MARK: - configureEnvironment(...) Exposed to RN
|
|
49
|
+
@objc public func configureEnvironment(_ env: String, apiKey: String, apiSecret: String) {
|
|
50
|
+
switch env.lowercased() {
|
|
51
|
+
case "production": EnvironmentConfig.setEnvironment(.production)
|
|
52
|
+
case "sandbox": EnvironmentConfig.setEnvironment(.sandbox)
|
|
53
|
+
default: EnvironmentConfig.setEnvironment(.staging)
|
|
54
|
+
}
|
|
55
|
+
EnvironmentConfig.configure(apiKey: apiKey, apiSecret: apiSecret)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// MARK: - billing(...) Exposed to RN
|
|
44
59
|
@objc public func billing(
|
|
45
60
|
_ amount: String,
|
|
46
|
-
|
|
61
|
+
currency: String?,
|
|
62
|
+
billingInfo: [String: Any]?,
|
|
47
63
|
paymentMethods: [String],
|
|
48
|
-
themeConfiguration: [String: Any]
|
|
64
|
+
themeConfiguration: [String: Any]?,
|
|
49
65
|
tokenOnly: Bool,
|
|
50
66
|
saveCard: Bool,
|
|
51
67
|
saveAccount: Bool,
|
|
@@ -56,187 +72,286 @@ public class EasyMerchantSdkPlugin: NSObject, RCTBridgeModule {
|
|
|
56
72
|
recurringIntervals: [String]?,
|
|
57
73
|
recurringStartDateType: String?,
|
|
58
74
|
recurringStartDate: String?,
|
|
75
|
+
secureAuthentication: Bool,
|
|
76
|
+
showReceipt: Bool,
|
|
77
|
+
showTotal: Bool,
|
|
78
|
+
showSubmitButton: Bool,
|
|
59
79
|
enable3DS: Bool,
|
|
60
80
|
resolver: @escaping RCTPromiseResolveBlock,
|
|
61
81
|
rejecter: @escaping RCTPromiseRejectBlock
|
|
62
82
|
) {
|
|
63
|
-
// Validate amount
|
|
64
|
-
guard let amountValue = Double(amount), amountValue
|
|
65
|
-
rejecter("INVALID_AMOUNT", "Amount must be
|
|
83
|
+
// 1) Validate amount
|
|
84
|
+
guard let amountValue = Double(amount), amountValue >= 0.50 else {
|
|
85
|
+
rejecter("INVALID_AMOUNT", "Amount must be at least $0.50", nil)
|
|
66
86
|
return
|
|
67
87
|
}
|
|
68
88
|
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
89
|
+
// 2) Process billing info into FieldSection
|
|
90
|
+
var billingInfoData: Data? = nil
|
|
91
|
+
if let billingDict = billingInfo {
|
|
92
|
+
var billingFields: [FieldItem] = []
|
|
93
|
+
var additionalFields: [FieldItem] = []
|
|
94
|
+
var billingVisibility = true
|
|
95
|
+
var additionalVisibility = true
|
|
96
|
+
|
|
97
|
+
// Parse billing fields
|
|
98
|
+
if let billing = billingDict["billing"] as? [String: Any] {
|
|
99
|
+
for (key, value) in billing {
|
|
100
|
+
guard let fieldValue = value as? String else { continue }
|
|
101
|
+
let required = (billingDict["billingRequired"] as? [String: Bool])?[key] ?? false
|
|
102
|
+
if let billingField = BillingFieldName(rawValue: key) {
|
|
103
|
+
billingFields.append(FieldItem(name: billingField, required: required, value: fieldValue))
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Parse additional fields
|
|
109
|
+
if let additional = billingDict["additional"] as? [String: Any] {
|
|
110
|
+
for (key, value) in additional {
|
|
111
|
+
guard let fieldValue = value as? String else { continue }
|
|
112
|
+
let required = (billingDict["additionalRequired"] as? [String: Bool])?[key] ?? false
|
|
113
|
+
if let additionalField = AdditionalFieldName(rawValue: key) {
|
|
114
|
+
additionalFields.append(FieldItem(name: additionalField, required: required, value: fieldValue))
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Parse visibility
|
|
120
|
+
if let visibility = billingDict["visibility"] as? [String: Bool] {
|
|
121
|
+
billingVisibility = visibility["billing"] ?? true
|
|
122
|
+
additionalVisibility = visibility["additional"] ?? true
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let fields = FieldSection(
|
|
126
|
+
visibility: FieldsVisibility(billing: billingVisibility, additional: additionalVisibility),
|
|
127
|
+
billing: billingFields,
|
|
128
|
+
additional: additionalFields
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
do {
|
|
132
|
+
billingInfoData = try JSONEncoder().encode(fields)
|
|
133
|
+
} catch {
|
|
134
|
+
rejecter("INVALID_BILLING_INFO", "Failed to encode billing info: \(error.localizedDescription)", nil)
|
|
74
135
|
return
|
|
75
136
|
}
|
|
76
137
|
}
|
|
77
138
|
|
|
78
|
-
// Map payment
|
|
139
|
+
// 3) Map payment methods
|
|
79
140
|
let methods: [PaymentMethod] = paymentMethods.compactMap {
|
|
80
141
|
switch $0.lowercased() {
|
|
81
|
-
case "card":
|
|
82
|
-
case "bank":
|
|
142
|
+
case "card": return .Card
|
|
143
|
+
case "bank": return .Bank
|
|
83
144
|
case "crypto": return .Crypto
|
|
84
|
-
|
|
145
|
+
case "wallet": return .Wallet
|
|
146
|
+
default: return nil
|
|
85
147
|
}
|
|
86
148
|
}
|
|
87
149
|
|
|
88
|
-
//
|
|
150
|
+
// 4) Prepare GrailPayRequest if needed
|
|
89
151
|
var grailParams: GrailPayRequest? = nil
|
|
90
152
|
if authenticatedACH, let params = grailPayParams {
|
|
91
153
|
grailParams = GrailPayRequest(
|
|
92
|
-
accessToken: params["accessToken"] as? String ?? "",
|
|
93
|
-
vendorId: params["vendorId"] as? String ?? "",
|
|
154
|
+
accessToken: params["accessToken"] as? String ?? "251|uTijpDGfrS88UR2V1cZNMQ8S4hUJA0sVzsnsoUZF",
|
|
155
|
+
vendorId: params["vendorId"] as? String ?? "251",
|
|
94
156
|
role: params["role"] as? String ?? "business",
|
|
95
157
|
timeout: params["timeout"] as? Int ?? 10,
|
|
96
158
|
isSandbox: params["isSandbox"] as? Bool ?? true,
|
|
97
|
-
brandingName: params["brandingName"] as? String ?? "Payments",
|
|
98
|
-
finderSubtitle: params["finderSubtitle"] as? String ?? "
|
|
99
|
-
searchPlaceholder: params["searchPlaceholder"] as? String ?? "
|
|
159
|
+
brandingName: params["brandingName"] as? String ?? "Lyfecycle Payments",
|
|
160
|
+
finderSubtitle: params["finderSubtitle"] as? String ?? "Search for your bank",
|
|
161
|
+
searchPlaceholder: params["searchPlaceholder"] as? String ?? "Enter bank name"
|
|
100
162
|
)
|
|
101
163
|
}
|
|
102
164
|
|
|
103
|
-
//
|
|
165
|
+
// 5) Build theme config
|
|
104
166
|
let themeConfig = ThemeConfiguration(
|
|
105
|
-
bodyBackgroundColor: themeConfiguration["bodyBackgroundColor"] as? String ?? "#
|
|
106
|
-
containerBackgroundColor: themeConfiguration["containerBackgroundColor"] as? String ?? "#
|
|
107
|
-
primaryFontColor: themeConfiguration["primaryFontColor"] as? String ?? "#
|
|
108
|
-
secondaryFontColor: themeConfiguration["secondaryFontColor"] as? String ?? "#
|
|
109
|
-
primaryButtonBackgroundColor: themeConfiguration["primaryButtonBackgroundColor"] as? String ?? "#
|
|
110
|
-
primaryButtonHoverColor: themeConfiguration["primaryButtonHoverColor"] as? String ?? "#
|
|
111
|
-
primaryButtonFontColor: themeConfiguration["primaryButtonFontColor"] as? String ?? "#FFFFFF",
|
|
112
|
-
secondaryButtonBackgroundColor: themeConfiguration["secondaryButtonBackgroundColor"] as? String ?? "#
|
|
113
|
-
secondaryButtonHoverColor: themeConfiguration["secondaryButtonHoverColor"] as? String ?? "#
|
|
114
|
-
secondaryButtonFontColor: themeConfiguration["secondaryButtonFontColor"] as? String ?? "#
|
|
115
|
-
borderRadius: themeConfiguration["borderRadius"] as? String ?? "8",
|
|
116
|
-
fontSize: themeConfiguration["fontSize"] as? String ?? "14"
|
|
167
|
+
bodyBackgroundColor: themeConfiguration?["bodyBackgroundColor"] as? String ?? "#121212",
|
|
168
|
+
containerBackgroundColor: themeConfiguration?["containerBackgroundColor"] as? String ?? "#1E1E1E",
|
|
169
|
+
primaryFontColor: themeConfiguration?["primaryFontColor"] as? String ?? "#FFFFFF",
|
|
170
|
+
secondaryFontColor: themeConfiguration?["secondaryFontColor"] as? String ?? "#B0B0B0",
|
|
171
|
+
primaryButtonBackgroundColor: themeConfiguration?["primaryButtonBackgroundColor"] as? String ?? "#2563EB",
|
|
172
|
+
primaryButtonHoverColor: themeConfiguration?["primaryButtonHoverColor"] as? String ?? "#1D4ED8",
|
|
173
|
+
primaryButtonFontColor: themeConfiguration?["primaryButtonFontColor"] as? String ?? "#FFFFFF",
|
|
174
|
+
secondaryButtonBackgroundColor: themeConfiguration?["secondaryButtonBackgroundColor"] as? String ?? "#374151",
|
|
175
|
+
secondaryButtonHoverColor: themeConfiguration?["secondaryButtonHoverColor"] as? String ?? "#4B5563",
|
|
176
|
+
secondaryButtonFontColor: themeConfiguration?["secondaryButtonFontColor"] as? String ?? "#E5E7EB",
|
|
177
|
+
borderRadius: themeConfiguration?["borderRadius"] as? String ?? "8",
|
|
178
|
+
fontSize: themeConfiguration?["fontSize"] as? String ?? "14",
|
|
179
|
+
fontWeight: themeConfiguration?["fontWeight"] as? Int ?? 500,
|
|
180
|
+
fontFamily: themeConfiguration?["fontFamily"] as? String ?? "\"Inter\", sans-serif"
|
|
117
181
|
)
|
|
118
182
|
|
|
119
|
-
//
|
|
120
|
-
let
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
print("🔥 recurringIntervals raw from JS:", recurringIntervals as Any)
|
|
124
|
-
|
|
125
|
-
// Updated mapping to strip leading "." if present
|
|
126
|
-
let intervals = (recurringIntervals ?? []).compactMap { raw -> RecurringIntervals? in
|
|
127
|
-
// 1) Trim whitespace, lowercase
|
|
183
|
+
// 6) Map recurring intervals
|
|
184
|
+
let intervals: [RecurringIntervals] = (recurringIntervals ?? []).compactMap { raw in
|
|
128
185
|
let cleaned = raw.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
|
129
|
-
// 2) If it starts with ".", drop that character
|
|
130
186
|
let noDot = cleaned.hasPrefix(".") ? String(cleaned.dropFirst()) : cleaned
|
|
131
|
-
// 3) Try to initialize your enum
|
|
132
187
|
return RecurringIntervals(rawValue: noDot)
|
|
133
188
|
}
|
|
134
189
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
guard !intervals.isEmpty else {
|
|
190
|
+
if isRecurring && intervals.isEmpty {
|
|
138
191
|
rejecter("NO_RECURRING_INTERVALS", "No recurring intervals available.", nil)
|
|
139
192
|
return
|
|
140
|
-
|
|
141
|
-
|
|
193
|
+
}
|
|
142
194
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
195
|
+
// 7) Map start date type
|
|
196
|
+
let startType: RecurringStartDateType? = {
|
|
197
|
+
switch recurringStartDateType?.lowercased() {
|
|
198
|
+
case "custom": return .custom
|
|
199
|
+
case "fixed": return .fixed
|
|
200
|
+
default: return nil
|
|
201
|
+
}
|
|
202
|
+
}()
|
|
151
203
|
|
|
152
|
-
//
|
|
204
|
+
// 8) Build the request object
|
|
153
205
|
let request = Request(
|
|
154
206
|
amount: amountValue,
|
|
207
|
+
currency: currency ?? "usd",
|
|
155
208
|
billingInfoData: billingInfoData,
|
|
156
|
-
paymentMethods: methods,
|
|
209
|
+
paymentMethods: methods.isEmpty ? [.Card, .Bank] : methods,
|
|
157
210
|
themeConfiguration: themeConfig,
|
|
158
211
|
tokenOnly: tokenOnly,
|
|
159
212
|
saveCard: saveCard,
|
|
160
213
|
saveAccount: saveAccount,
|
|
161
214
|
submitButtonText: submitButtonText ?? "Submit",
|
|
162
215
|
authenticatedACH: authenticatedACH,
|
|
163
|
-
grailPayParams:
|
|
216
|
+
grailPayParams: grailParams,
|
|
164
217
|
is_recurring: isRecurring,
|
|
165
218
|
recurringIntervals: intervals,
|
|
166
|
-
recurringStartDateType:
|
|
219
|
+
recurringStartDateType: startType,
|
|
167
220
|
recurringStartDate: recurringStartDate,
|
|
168
|
-
|
|
221
|
+
secureAuthentication: secureAuthentication,
|
|
222
|
+
showReceipt: showReceipt,
|
|
223
|
+
showTotal: showTotal,
|
|
224
|
+
showSubmitButton: showSubmitButton,
|
|
225
|
+
referenceID: enable3DS,
|
|
226
|
+
referenceToken: nil
|
|
169
227
|
)
|
|
170
228
|
|
|
171
|
-
// Store
|
|
229
|
+
// Store resolvers
|
|
172
230
|
self.billingResolver = resolver
|
|
173
231
|
self.billingRejecter = rejecter
|
|
174
232
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
233
|
+
// Handle tokenOnly or present EasyPayViewController
|
|
234
|
+
if tokenOnly {
|
|
235
|
+
NotificationCenter.default.addObserver(
|
|
236
|
+
forName: NSNotification.Name("ClientTokenReceived"),
|
|
237
|
+
object: nil,
|
|
238
|
+
queue: .main
|
|
239
|
+
) { [weak self] notification in
|
|
240
|
+
guard let self = self else { return }
|
|
241
|
+
if let clientToken = notification.object as? String {
|
|
242
|
+
self.billingResolver?(["status": "success", "clientToken": clientToken])
|
|
243
|
+
} else {
|
|
244
|
+
self.billingRejecter?("TOKEN_ERROR", "Failed to receive client token.", nil)
|
|
245
|
+
}
|
|
183
246
|
self.clearResolvers()
|
|
184
247
|
}
|
|
248
|
+
} else {
|
|
249
|
+
DispatchQueue.main.async {
|
|
250
|
+
let vc = EasyPayViewController(request: request, delegate: self)
|
|
251
|
+
if let host = self.viewController {
|
|
252
|
+
host.present(vc, animated: true, completion: nil)
|
|
253
|
+
} else if let root = UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.rootViewController {
|
|
254
|
+
root.present(vc, animated: true, completion: nil)
|
|
255
|
+
} else {
|
|
256
|
+
rejecter("NO_VIEW_CONTROLLER", "Cannot find a view controller to present.", nil)
|
|
257
|
+
self.clearResolvers()
|
|
258
|
+
}
|
|
259
|
+
}
|
|
185
260
|
}
|
|
186
261
|
}
|
|
187
262
|
|
|
188
|
-
// MARK: -
|
|
189
|
-
@objc public func
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
263
|
+
// MARK: - paymentReference(...) Exposed to RN
|
|
264
|
+
@objc public func paymentReference(
|
|
265
|
+
_ referenceToken: String,
|
|
266
|
+
resolver: @escaping RCTPromiseResolveBlock,
|
|
267
|
+
rejecter: @escaping RCTPromiseRejectBlock
|
|
268
|
+
) {
|
|
269
|
+
// Validate reference token
|
|
270
|
+
guard !referenceToken.isEmpty else {
|
|
271
|
+
rejecter("INVALID_REFERENCE_TOKEN", "Reference token cannot be empty.", nil)
|
|
272
|
+
return
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Store resolvers
|
|
276
|
+
self.paymentReferenceResolver = resolver
|
|
277
|
+
self.paymentReferenceRejecter = rejecter
|
|
278
|
+
|
|
279
|
+
// Build the request object
|
|
280
|
+
let request = Request(
|
|
281
|
+
referenceID: true,
|
|
282
|
+
referenceToken: referenceToken
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
// Set up notification observer for ReferenceIDResponse
|
|
286
|
+
DispatchQueue.main.async {
|
|
287
|
+
NotificationCenter.default.addObserver(
|
|
288
|
+
forName: NSNotification.Name("ReferenceIDResponse"),
|
|
289
|
+
object: nil,
|
|
290
|
+
queue: .main
|
|
291
|
+
) { [weak self] notification in
|
|
292
|
+
guard let self = self else { return }
|
|
293
|
+
if let data = notification.object as? [String: Any] {
|
|
294
|
+
self.paymentReferenceResolver?(data)
|
|
295
|
+
} else {
|
|
296
|
+
self.paymentReferenceRejecter?("INVALID_RESPONSE", "Invalid response data.", nil)
|
|
297
|
+
}
|
|
298
|
+
self.clearReferenceResolvers()
|
|
299
|
+
}
|
|
197
300
|
}
|
|
198
|
-
EnvironmentConfig.configure(apiKey: apiKey, apiSecret: apiSecret)
|
|
199
301
|
}
|
|
200
302
|
|
|
201
|
-
// MARK: -
|
|
303
|
+
// MARK: - getPlatformVersion(...) Exposed to RN
|
|
202
304
|
@objc public func getPlatformVersion(
|
|
203
|
-
_ resolver:
|
|
204
|
-
rejecter:
|
|
305
|
+
_ resolver: RCTPromiseResolveBlock,
|
|
306
|
+
rejecter: RCTPromiseRejectBlock
|
|
205
307
|
) {
|
|
206
308
|
resolver("iOS \(UIDevice.current.systemVersion)")
|
|
207
309
|
}
|
|
208
310
|
|
|
209
|
-
// MARK: -
|
|
311
|
+
// MARK: - Private Helpers
|
|
210
312
|
private func clearResolvers() {
|
|
211
|
-
|
|
212
|
-
|
|
313
|
+
billingResolver = nil
|
|
314
|
+
billingRejecter = nil
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private func clearReferenceResolvers() {
|
|
318
|
+
paymentReferenceResolver = nil
|
|
319
|
+
paymentReferenceRejecter = nil
|
|
320
|
+
NotificationCenter.default.removeObserver(self, name: NSNotification.Name("ReferenceIDResponse"), object: nil)
|
|
213
321
|
}
|
|
214
322
|
}
|
|
215
323
|
|
|
216
|
-
// MARK: -
|
|
324
|
+
// MARK: - Delegate Implementation
|
|
217
325
|
extension EasyMerchantSdkPlugin: EasyPayViewControllerDelegate {
|
|
218
326
|
public func easyPayController(_ controller: EasyPayViewController, didFinishWith result: Result) {
|
|
219
327
|
DispatchQueue.main.async {
|
|
220
328
|
switch result.type {
|
|
221
329
|
case .cancelled:
|
|
222
330
|
self.billingResolver?(["status": "cancelled", "message": "User cancelled"])
|
|
331
|
+
|
|
223
332
|
case .success:
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
333
|
+
var payload: [String: Any] = ["status": "success"]
|
|
334
|
+
if let cd = result.chargeData { payload["chargeData"] = cd }
|
|
335
|
+
if let bi = result.billingInfo { payload["billingInfo"] = bi }
|
|
336
|
+
if let ai = result.additionalInfo { payload["additionalInfo"] = ai }
|
|
337
|
+
|
|
338
|
+
// Extract referenceToken for 3DS
|
|
339
|
+
if let additional = result.additionalInfo,
|
|
340
|
+
let threeDSecureStatus = additional["threeDSecureStatus"] as? [String: Any],
|
|
341
|
+
let data = threeDSecureStatus["data"] as? [String: Any],
|
|
342
|
+
let token = data["ref_token"] as? String {
|
|
343
|
+
payload["referenceToken"] = token
|
|
229
344
|
}
|
|
345
|
+
|
|
346
|
+
self.billingResolver?(payload)
|
|
347
|
+
|
|
230
348
|
case .error:
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
} else {
|
|
234
|
-
self.billingRejecter?("PAYMENT_ERROR", "Unknown error", nil)
|
|
235
|
-
}
|
|
349
|
+
let errMsg = (result.chargeData?["errorMessage"] as? String) ?? "Unknown error"
|
|
350
|
+
self.billingRejecter?("PAYMENT_ERROR", errMsg, nil)
|
|
236
351
|
}
|
|
352
|
+
|
|
237
353
|
self.clearResolvers()
|
|
238
|
-
controller.dismiss(animated: true)
|
|
354
|
+
controller.dismiss(animated: true, completion: nil)
|
|
239
355
|
}
|
|
240
356
|
}
|
|
241
357
|
}
|
|
242
|
-
|
|
@@ -63,7 +63,7 @@ public final class EasyPayViewController: UINavigationController {
|
|
|
63
63
|
let vc = UIStoryboard(name: "easymerchantsdk", bundle: Bundle.easyPayBundle).instantiateViewController(withIdentifier: "PaymentInfoVC") as! PaymentInfoVC
|
|
64
64
|
vc.modalPresentationStyle = .overFullScreen
|
|
65
65
|
vc.configureWith(request: request, delegate: easyPayDelegate)
|
|
66
|
-
vc.amount = Int(request.amount) // Set the amount here
|
|
66
|
+
vc.amount = Int(request.amount ?? 0.0) // Set the amount here
|
|
67
67
|
self.navigationBar.isHidden = true
|
|
68
68
|
self.setViewControllers([vc], animated: false)
|
|
69
69
|
}
|
|
@@ -19,12 +19,14 @@ class DatePickerHandler: NSObject {
|
|
|
19
19
|
|
|
20
20
|
self.targetTextField = textField
|
|
21
21
|
configureDatePicker()
|
|
22
|
+
setupInputView(for: textField)
|
|
22
23
|
|
|
23
24
|
if let date = initialDate {
|
|
24
25
|
datePicker.date = date
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
// Add observer for when the user taps the textField
|
|
29
|
+
textField.addTarget(self, action: #selector(textFieldEditingDidBegin), for: .editingDidBegin)
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
private func configureDatePicker() {
|
|
@@ -32,7 +34,7 @@ class DatePickerHandler: NSObject {
|
|
|
32
34
|
datePicker.preferredDatePickerStyle = .inline
|
|
33
35
|
}
|
|
34
36
|
datePicker.datePickerMode = .date
|
|
35
|
-
datePicker.minimumDate = Date()
|
|
37
|
+
datePicker.minimumDate = Calendar.current.startOfDay(for: Date())
|
|
36
38
|
datePicker.addTarget(self, action: #selector(dateChanged(_:)), for: .valueChanged)
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -49,6 +51,17 @@ class DatePickerHandler: NSObject {
|
|
|
49
51
|
textField.inputAccessoryView = toolbar
|
|
50
52
|
}
|
|
51
53
|
|
|
54
|
+
@objc private func textFieldEditingDidBegin() {
|
|
55
|
+
// When the textField is tapped, if it has a date, set it back on the date picker
|
|
56
|
+
if let text = targetTextField?.text, !text.isEmpty {
|
|
57
|
+
let formatter = DateFormatter()
|
|
58
|
+
formatter.dateFormat = "dd/MM/yyyy"
|
|
59
|
+
if let selectedDate = formatter.date(from: text) {
|
|
60
|
+
datePicker.setDate(selectedDate, animated: true)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
52
65
|
@objc private func dateChanged(_ sender: UIDatePicker) {
|
|
53
66
|
let formatter = DateFormatter()
|
|
54
67
|
formatter.dateFormat = "dd/MM/yyyy"
|
|
@@ -66,6 +79,4 @@ class DatePickerHandler: NSObject {
|
|
|
66
79
|
}
|
|
67
80
|
targetTextField?.resignFirstResponder()
|
|
68
81
|
}
|
|
69
|
-
|
|
70
82
|
}
|
|
71
|
-
|
|
@@ -39,34 +39,36 @@ public class EnvironmentConfig {
|
|
|
39
39
|
self.currentEnvironment = environment
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
42
|
+
public enum Endpoints {
|
|
43
|
+
case paymentIntent
|
|
44
|
+
case hostedCheckouts
|
|
45
|
+
case emailVerification
|
|
46
|
+
case verifyOtp
|
|
47
|
+
case getCards
|
|
48
|
+
case creditCharges
|
|
49
|
+
case account
|
|
50
|
+
case achCharge
|
|
51
|
+
case charges
|
|
52
|
+
case accountConnect
|
|
53
|
+
case threeDSecure
|
|
54
|
+
case threeDSecureStatus(String)
|
|
55
|
+
|
|
56
|
+
func path() -> String {
|
|
57
|
+
switch self {
|
|
58
|
+
case .paymentIntent: return "/api/v1/paymentintent"
|
|
59
|
+
case .hostedCheckouts: return "/api/v1/hostedcheckouts"
|
|
60
|
+
case .emailVerification: return "/api/v1/customer/send_otp"
|
|
61
|
+
case .verifyOtp: return "/api/v1/customer/verify_otp"
|
|
62
|
+
// case .getCards: return "/api/v1/customer/card"
|
|
63
|
+
case .getCards: return "/api/v1/card"
|
|
64
|
+
case .creditCharges: return "/api/v1/customer/charges"
|
|
65
|
+
case .account: return "/api/v1/ach/account"
|
|
66
|
+
case .achCharge: return "/api/v1/ach/charge"
|
|
67
|
+
case .charges: return "/api/v1/charges"
|
|
68
|
+
case .accountConnect: return "/api/v1/ach/account/connect"
|
|
69
|
+
case .threeDSecure: return "/api/v1/3dsecure"
|
|
70
|
+
case .threeDSecureStatus(let token): return "/api/v1/3dsecure/\(token)/status"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
72
74
|
}
|