react-native-amwal-ecr 0.1.0
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/CHANGELOG.md +30 -0
- package/LICENSE +20 -0
- package/README.md +278 -0
- package/RNAmwalEcr.podspec +27 -0
- package/android/build.gradle +53 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/com/amwalecr/AmwalEcrModule.kt +325 -0
- package/android/src/main/java/com/amwalecr/AmwalEcrPackage.kt +31 -0
- package/android/src/main/java/com/amwalecr/EcrMapping.kt +135 -0
- package/docs/compatibility.md +22 -0
- package/ios/AmwalEcr.h +4 -0
- package/ios/AmwalEcr.mm +121 -0
- package/ios/AmwalEcr.swift +424 -0
- package/ios/EcrMapping.swift +142 -0
- package/lib/module/EcrOperation.js +18 -0
- package/lib/module/EcrOperation.js.map +1 -0
- package/lib/module/EcrTerminal.js +211 -0
- package/lib/module/EcrTerminal.js.map +1 -0
- package/lib/module/NativeAmwalEcr.js +5 -0
- package/lib/module/NativeAmwalEcr.js.map +1 -0
- package/lib/module/index.js +6 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/native.js +16 -0
- package/lib/module/native.js.map +1 -0
- package/lib/module/native.native.js +4 -0
- package/lib/module/native.native.js.map +1 -0
- package/lib/module/normalize.js +87 -0
- package/lib/module/normalize.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/EcrOperation.d.ts +10 -0
- package/lib/typescript/src/EcrOperation.d.ts.map +1 -0
- package/lib/typescript/src/EcrTerminal.d.ts +34 -0
- package/lib/typescript/src/EcrTerminal.d.ts.map +1 -0
- package/lib/typescript/src/NativeAmwalEcr.d.ts +14 -0
- package/lib/typescript/src/NativeAmwalEcr.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +5 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/native.d.ts +3 -0
- package/lib/typescript/src/native.d.ts.map +1 -0
- package/lib/typescript/src/native.native.d.ts +2 -0
- package/lib/typescript/src/native.native.d.ts.map +1 -0
- package/lib/typescript/src/normalize.d.ts +5 -0
- package/lib/typescript/src/normalize.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +157 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +153 -0
- package/src/EcrOperation.ts +25 -0
- package/src/EcrTerminal.ts +344 -0
- package/src/NativeAmwalEcr.ts +28 -0
- package/src/index.tsx +34 -0
- package/src/native.native.ts +1 -0
- package/src/native.ts +18 -0
- package/src/normalize.ts +116 -0
- package/src/types.ts +189 -0
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import AmwalECR
|
|
2
|
+
import Foundation
|
|
3
|
+
import React
|
|
4
|
+
|
|
5
|
+
@objc(AmwalEcrImpl)
|
|
6
|
+
final class AmwalEcrImpl: NSObject {
|
|
7
|
+
private static let invalidArgument = "ECR_INVALID_ARGUMENT"
|
|
8
|
+
private static let internalError = "ECR_INTERNAL"
|
|
9
|
+
private static let cancelledMessage =
|
|
10
|
+
"The caller cancelled while the terminal had the request. "
|
|
11
|
+
+ "The transaction may still have completed — inquire before retrying."
|
|
12
|
+
|
|
13
|
+
private let queue = DispatchQueue(
|
|
14
|
+
label: "com.amwalpay.react-native-ecr",
|
|
15
|
+
qos: .userInitiated,
|
|
16
|
+
attributes: .concurrent
|
|
17
|
+
)
|
|
18
|
+
private let lock = NSLock()
|
|
19
|
+
private var running: [String: RunningOperation] = [:]
|
|
20
|
+
|
|
21
|
+
@objc static func requiresMainQueueSetup() -> Bool { false }
|
|
22
|
+
|
|
23
|
+
@objc func isReachable(
|
|
24
|
+
_ request: [String: Any],
|
|
25
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
26
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
27
|
+
) {
|
|
28
|
+
do {
|
|
29
|
+
let call = try Call(request)
|
|
30
|
+
guard call.isIpTransport else {
|
|
31
|
+
resolve(false)
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
let session = try call.makeSession()
|
|
35
|
+
queue.async { resolve(session.probeReachability()?.reachable ?? false) }
|
|
36
|
+
} catch {
|
|
37
|
+
rejectError(error, reject)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@objc func sale(
|
|
42
|
+
_ request: [String: Any],
|
|
43
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
44
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
45
|
+
) {
|
|
46
|
+
run(request, resolve, reject) { call, session in
|
|
47
|
+
ReactEcrMapping.result(
|
|
48
|
+
try session.sale(
|
|
49
|
+
amount: try call.requiredAmount(),
|
|
50
|
+
merchantReference: call.merchantReferenceId
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@objc func voidTransaction(
|
|
57
|
+
_ request: [String: Any],
|
|
58
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
59
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
60
|
+
) {
|
|
61
|
+
run(request, resolve, reject) { call, session in
|
|
62
|
+
ReactEcrMapping.result(
|
|
63
|
+
try session.void(
|
|
64
|
+
receiptNumber: try call.requiredReceiptNumber("A void"),
|
|
65
|
+
originalTerminalId: call.originalTerminalId,
|
|
66
|
+
merchantReference: call.merchantReferenceId
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@objc func refund(
|
|
73
|
+
_ request: [String: Any],
|
|
74
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
75
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
76
|
+
) {
|
|
77
|
+
run(request, resolve, reject) { call, session in
|
|
78
|
+
ReactEcrMapping.result(
|
|
79
|
+
try session.refund(
|
|
80
|
+
amount: try call.requiredAmount(),
|
|
81
|
+
receiptNumber: try call.requiredReceiptNumber("A refund"),
|
|
82
|
+
transactionDate: call.transactionDate,
|
|
83
|
+
originalTerminalId: call.originalTerminalId,
|
|
84
|
+
merchantReference: call.merchantReferenceId
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@objc func inquire(
|
|
91
|
+
_ request: [String: Any],
|
|
92
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
93
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
94
|
+
) {
|
|
95
|
+
run(request, resolve, reject) { call, session in
|
|
96
|
+
ReactEcrMapping.inquiry(
|
|
97
|
+
try session.inquire(
|
|
98
|
+
receiptNumber: try call.requiredReceiptNumber("An inquiry"),
|
|
99
|
+
transactionDate: call.transactionDate,
|
|
100
|
+
originalTerminalId: call.originalTerminalId,
|
|
101
|
+
merchantReference: call.merchantReferenceId
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@objc func inquireByReference(
|
|
108
|
+
_ request: [String: Any],
|
|
109
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
110
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
111
|
+
) {
|
|
112
|
+
run(request, resolve, reject) { call, session in
|
|
113
|
+
ReactEcrMapping.inquiry(
|
|
114
|
+
try session.inquireByReference(
|
|
115
|
+
try call.requiredOriginalReference(),
|
|
116
|
+
transactionDate: call.transactionDate,
|
|
117
|
+
originalTerminalId: call.originalTerminalId,
|
|
118
|
+
merchantReference: call.merchantReferenceId
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@objc func receipt(
|
|
125
|
+
_ request: [String: Any],
|
|
126
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
127
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
128
|
+
) {
|
|
129
|
+
run(request, resolve, reject) { call, session in
|
|
130
|
+
ReactEcrMapping.receipt(
|
|
131
|
+
try session.receipt(
|
|
132
|
+
receiptNumber: try call.requiredReceiptNumber("A receipt"),
|
|
133
|
+
transactionDate: call.transactionDate,
|
|
134
|
+
originalTerminalId: call.originalTerminalId,
|
|
135
|
+
merchantReference: call.merchantReferenceId
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@objc func cancel(
|
|
142
|
+
_ operationId: String,
|
|
143
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
144
|
+
rejecter _: @escaping RCTPromiseRejectBlock
|
|
145
|
+
) {
|
|
146
|
+
lock.lock()
|
|
147
|
+
let operation = running.removeValue(forKey: operationId)
|
|
148
|
+
lock.unlock()
|
|
149
|
+
|
|
150
|
+
guard let operation else {
|
|
151
|
+
resolve(false)
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
operation.session.cancel()
|
|
156
|
+
operation.reply.resolve(
|
|
157
|
+
ReactEcrMapping.failedResult(
|
|
158
|
+
merchantReferenceId: operation.merchantReferenceId,
|
|
159
|
+
kind: ReactEcrMapping.cancelled,
|
|
160
|
+
message: Self.cancelledMessage
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
resolve(true)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@objc func invalidate() {
|
|
167
|
+
lock.lock()
|
|
168
|
+
let operations = Array(running.values)
|
|
169
|
+
running.removeAll()
|
|
170
|
+
lock.unlock()
|
|
171
|
+
|
|
172
|
+
for operation in operations {
|
|
173
|
+
operation.session.cancel()
|
|
174
|
+
operation.reply.resolve(
|
|
175
|
+
ReactEcrMapping.failedResult(
|
|
176
|
+
merchantReferenceId: operation.merchantReferenceId,
|
|
177
|
+
kind: ReactEcrMapping.cancelled,
|
|
178
|
+
message: "The React Native host was invalidated while the terminal had the request. "
|
|
179
|
+
+ "The transaction may still have completed — inquire before retrying."
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private func run(
|
|
186
|
+
_ request: [String: Any],
|
|
187
|
+
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
188
|
+
_ reject: @escaping RCTPromiseRejectBlock,
|
|
189
|
+
_ block: @escaping (Call, EcrOpenedSession) throws -> Any
|
|
190
|
+
) {
|
|
191
|
+
do {
|
|
192
|
+
let call = try Call(request)
|
|
193
|
+
let reply = OneShotReply(resolve: resolve, reject: reject)
|
|
194
|
+
|
|
195
|
+
guard call.isSupportedTransport else {
|
|
196
|
+
reply.resolve(
|
|
197
|
+
ReactEcrMapping.failedResult(
|
|
198
|
+
merchantReferenceId: "",
|
|
199
|
+
kind: ReactEcrMapping.unsupported,
|
|
200
|
+
message: "This library drives a terminal over ethernet, wifi, or the Web Service "
|
|
201
|
+
+ "REST path. Transport '\(call.transport)' is not handled, so nothing was sent."
|
|
202
|
+
)
|
|
203
|
+
)
|
|
204
|
+
return
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let session = try call.makeSession()
|
|
208
|
+
let operation = RunningOperation(
|
|
209
|
+
session: session,
|
|
210
|
+
reply: reply,
|
|
211
|
+
merchantReferenceId: call.merchantReferenceId
|
|
212
|
+
)
|
|
213
|
+
lock.lock()
|
|
214
|
+
guard running[call.operationId] == nil else {
|
|
215
|
+
lock.unlock()
|
|
216
|
+
reply.reject(
|
|
217
|
+
code: Self.invalidArgument,
|
|
218
|
+
message: "operationId '\(call.operationId)' is already running",
|
|
219
|
+
error: nil
|
|
220
|
+
)
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
running[call.operationId] = operation
|
|
224
|
+
lock.unlock()
|
|
225
|
+
|
|
226
|
+
queue.async { [weak self] in
|
|
227
|
+
defer { self?.deregister(call.operationId, operation) }
|
|
228
|
+
do {
|
|
229
|
+
reply.resolve(try block(call, session))
|
|
230
|
+
} catch {
|
|
231
|
+
self?.rejectError(error, reply)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
} catch {
|
|
235
|
+
rejectError(error, reject)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private func deregister(_ operationId: String, _ operation: RunningOperation) {
|
|
240
|
+
lock.lock()
|
|
241
|
+
if running[operationId] === operation {
|
|
242
|
+
running.removeValue(forKey: operationId)
|
|
243
|
+
}
|
|
244
|
+
lock.unlock()
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private func rejectError(_ error: Error, _ reject: RCTPromiseRejectBlock) {
|
|
248
|
+
if let invalid = error as? EcrInvalidArgument {
|
|
249
|
+
reject(Self.invalidArgument, invalid.message, invalid)
|
|
250
|
+
} else {
|
|
251
|
+
reject(Self.internalError, error.localizedDescription, error)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private func rejectError(_ error: Error, _ reply: OneShotReply) {
|
|
256
|
+
if let invalid = error as? EcrInvalidArgument {
|
|
257
|
+
reply.reject(
|
|
258
|
+
code: Self.invalidArgument,
|
|
259
|
+
message: invalid.message,
|
|
260
|
+
error: invalid
|
|
261
|
+
)
|
|
262
|
+
} else {
|
|
263
|
+
reply.reject(
|
|
264
|
+
code: Self.internalError,
|
|
265
|
+
message: error.localizedDescription,
|
|
266
|
+
error: error
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private final class RunningOperation {
|
|
273
|
+
let session: EcrOpenedSession
|
|
274
|
+
let reply: OneShotReply
|
|
275
|
+
let merchantReferenceId: String
|
|
276
|
+
|
|
277
|
+
init(session: EcrOpenedSession, reply: OneShotReply, merchantReferenceId: String) {
|
|
278
|
+
self.session = session
|
|
279
|
+
self.reply = reply
|
|
280
|
+
self.merchantReferenceId = merchantReferenceId
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private final class OneShotReply {
|
|
285
|
+
private let lock = NSLock()
|
|
286
|
+
private var answered = false
|
|
287
|
+
private let onResolve: RCTPromiseResolveBlock
|
|
288
|
+
private let onReject: RCTPromiseRejectBlock
|
|
289
|
+
|
|
290
|
+
init(
|
|
291
|
+
resolve: @escaping RCTPromiseResolveBlock,
|
|
292
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
293
|
+
) {
|
|
294
|
+
onResolve = resolve
|
|
295
|
+
onReject = reject
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private func claim() -> Bool {
|
|
299
|
+
lock.lock()
|
|
300
|
+
defer { lock.unlock() }
|
|
301
|
+
if answered { return false }
|
|
302
|
+
answered = true
|
|
303
|
+
return true
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
func resolve(_ value: Any?) {
|
|
307
|
+
if claim() { onResolve(value) }
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
func reject(code: String, message: String, error: Error?) {
|
|
311
|
+
if claim() { onReject(code, message, error) }
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
private struct Call {
|
|
316
|
+
let operationId: String
|
|
317
|
+
let transport: String
|
|
318
|
+
let transactionDate: String
|
|
319
|
+
let originalTerminalId: String
|
|
320
|
+
let merchantReferenceId: String
|
|
321
|
+
let host: String
|
|
322
|
+
let serialNumber: String
|
|
323
|
+
let config: EcrConfig
|
|
324
|
+
private let raw: [String: Any]
|
|
325
|
+
|
|
326
|
+
init(_ raw: [String: Any]) throws {
|
|
327
|
+
self.raw = raw
|
|
328
|
+
operationId = try Self.requiredString(raw, "operationId")
|
|
329
|
+
transport = raw["transport"] as? String ?? "wifi"
|
|
330
|
+
// The payment host addresses a Web Service terminal; only the IP
|
|
331
|
+
// transports dial an address of their own.
|
|
332
|
+
if transport == "ethernet" || transport == "wifi" {
|
|
333
|
+
host = try Self.requiredString(raw, "host")
|
|
334
|
+
} else {
|
|
335
|
+
host = (raw["host"] as? String)?.trimmed ?? ""
|
|
336
|
+
}
|
|
337
|
+
serialNumber = raw["serialNumber"] as? String ?? ""
|
|
338
|
+
transactionDate = raw["transactionDate"] as? String ?? ""
|
|
339
|
+
originalTerminalId = raw["originalTerminalId"] as? String ?? ""
|
|
340
|
+
merchantReferenceId = raw["merchantReferenceId"] as? String ?? ""
|
|
341
|
+
config = try Self.config(raw["config"] as? [String: Any])
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
var isIpTransport: Bool { transport == "ethernet" || transport == "wifi" }
|
|
345
|
+
var isWebService: Bool { transport == "webService" || transport == "web_service" }
|
|
346
|
+
var isSupportedTransport: Bool { isIpTransport || isWebService }
|
|
347
|
+
|
|
348
|
+
/// The SDK session for this call's transport: TCP for ethernet/wifi, the
|
|
349
|
+
/// hosted REST path for webService.
|
|
350
|
+
func makeSession() throws -> EcrOpenedSession {
|
|
351
|
+
let link: EcrLink = isWebService
|
|
352
|
+
? .webService(merchantId: config.merchantId, terminalId: config.terminalId)
|
|
353
|
+
: .lan(host: host, port: config.port)
|
|
354
|
+
let plan = EcrSessions.plan(link: link, config: config)
|
|
355
|
+
guard plan.isReady else {
|
|
356
|
+
throw EcrInvalidArgument(plan.issues.joined(separator: "; "))
|
|
357
|
+
}
|
|
358
|
+
return EcrSessions.open(terminalSerial: serialNumber, plan: plan)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
func requiredAmount() throws -> Decimal {
|
|
362
|
+
let text = try Self.requiredString(raw, "amount")
|
|
363
|
+
guard let amount = EcrDecimal.parse(text) else {
|
|
364
|
+
throw EcrInvalidArgument("'\(text)' is not a decimal amount")
|
|
365
|
+
}
|
|
366
|
+
return amount
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
func requiredReceiptNumber(_ operation: String) throws -> String {
|
|
370
|
+
let value = (raw["receiptNumber"] as? String)?.trimmed ?? ""
|
|
371
|
+
guard !value.isEmpty else {
|
|
372
|
+
throw EcrInvalidArgument("\(operation) needs the original receipt number")
|
|
373
|
+
}
|
|
374
|
+
return value
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
func requiredOriginalReference() throws -> String {
|
|
378
|
+
let value = (raw["originalMerchantReference"] as? String)?.trimmed ?? ""
|
|
379
|
+
guard !value.isEmpty else {
|
|
380
|
+
throw EcrInvalidArgument("An inquiry by reference needs the original reference")
|
|
381
|
+
}
|
|
382
|
+
return value
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
private static func requiredString(_ raw: [String: Any], _ key: String) throws -> String {
|
|
386
|
+
let value = (raw[key] as? String)?.trimmed ?? ""
|
|
387
|
+
guard !value.isEmpty else {
|
|
388
|
+
throw EcrInvalidArgument("'\(key)' is required and was missing")
|
|
389
|
+
}
|
|
390
|
+
return value
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
private static func config(_ raw: [String: Any]?) throws -> EcrConfig {
|
|
394
|
+
let raw = raw ?? [:]
|
|
395
|
+
var value = EcrConfig()
|
|
396
|
+
if let field = raw["ecrId"] as? String, !field.isEmpty { value.ecrId = field }
|
|
397
|
+
if let field = raw["currencyCode"] as? String, !field.isEmpty { value.currencyCode = field }
|
|
398
|
+
if let field = raw["minorUnitDigits"] as? NSNumber { value.minorUnitDigits = field.intValue }
|
|
399
|
+
if let field = raw["port"] as? NSNumber { value.port = field.intValue }
|
|
400
|
+
if let field = raw["connectTimeoutMs"] as? NSNumber { value.connectTimeout = field.doubleValue / 1000 }
|
|
401
|
+
if let field = raw["responseTimeoutMs"] as? NSNumber { value.responseTimeout = field.doubleValue / 1000 }
|
|
402
|
+
if let field = raw["probeTimeoutMs"] as? NSNumber { value.probeTimeout = field.doubleValue / 1000 }
|
|
403
|
+
if let field = raw["secureHashKey"] as? String { value.secureHashKey = field }
|
|
404
|
+
if let field = raw["autoInquireOnFailure"] as? NSNumber { value.autoInquireOnFailure = field.boolValue }
|
|
405
|
+
if let field = raw["merchantId"] as? String { value.merchantId = field }
|
|
406
|
+
if let field = raw["terminalId"] as? String { value.terminalId = field }
|
|
407
|
+
if let field = raw["environment"] as? String { value.environment = EcrEnvironment.fromName(field) }
|
|
408
|
+
|
|
409
|
+
guard (0 ... 4).contains(value.minorUnitDigits) else {
|
|
410
|
+
throw EcrInvalidArgument("minorUnitDigits must be between 0 and 4")
|
|
411
|
+
}
|
|
412
|
+
guard (1 ... 65_535).contains(value.port) else {
|
|
413
|
+
throw EcrInvalidArgument("port must be between 1 and 65535")
|
|
414
|
+
}
|
|
415
|
+
if let message = value.secureHashKeyError {
|
|
416
|
+
throw EcrInvalidArgument(message)
|
|
417
|
+
}
|
|
418
|
+
return value
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
private extension String {
|
|
423
|
+
var trimmed: String { trimmingCharacters(in: .whitespacesAndNewlines) }
|
|
424
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import AmwalECR
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
enum ReactEcrMapping {
|
|
5
|
+
static let cancelled = "cancelled"
|
|
6
|
+
static let unsupported = "unsupported"
|
|
7
|
+
|
|
8
|
+
static func result(_ result: EcrResult) -> [String: Any] {
|
|
9
|
+
switch result {
|
|
10
|
+
case let .approved(value):
|
|
11
|
+
return [
|
|
12
|
+
"outcome": "approved",
|
|
13
|
+
"merchantReferenceId": value.merchantReference,
|
|
14
|
+
"amount": value.amount,
|
|
15
|
+
"responseCode": value.responseCode,
|
|
16
|
+
"rrn": value.rrn,
|
|
17
|
+
"authCode": value.authCode,
|
|
18
|
+
"maskedPan": value.maskedPan,
|
|
19
|
+
"partialApproval": value.partialApproval,
|
|
20
|
+
"requestedAmount": value.requestedAmount,
|
|
21
|
+
"raw": value.raw,
|
|
22
|
+
]
|
|
23
|
+
case let .declined(value):
|
|
24
|
+
return [
|
|
25
|
+
"outcome": "declined",
|
|
26
|
+
"merchantReferenceId": value.merchantReference,
|
|
27
|
+
"responseCode": value.responseCode,
|
|
28
|
+
"reason": value.reason,
|
|
29
|
+
"nextStep": value.nextStep.rawValue,
|
|
30
|
+
"raw": value.raw,
|
|
31
|
+
]
|
|
32
|
+
case let .failed(reference, failure, recovered):
|
|
33
|
+
var value: [String: Any] = [
|
|
34
|
+
"outcome": "failed",
|
|
35
|
+
"merchantReferenceId": reference,
|
|
36
|
+
"failure": self.failure(failure),
|
|
37
|
+
]
|
|
38
|
+
if let recovered {
|
|
39
|
+
value["recovered"] = inquiry(recovered)
|
|
40
|
+
}
|
|
41
|
+
return value
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static func inquiry(_ inquiry: EcrInquiry) -> [String: Any] {
|
|
46
|
+
switch inquiry {
|
|
47
|
+
case let .found(reference, transaction, raw):
|
|
48
|
+
return [
|
|
49
|
+
"outcome": "found",
|
|
50
|
+
"merchantReferenceId": reference,
|
|
51
|
+
"transaction": self.transaction(transaction),
|
|
52
|
+
"raw": raw,
|
|
53
|
+
]
|
|
54
|
+
case let .notFound(reference, reason, raw):
|
|
55
|
+
return [
|
|
56
|
+
"outcome": "notFound",
|
|
57
|
+
"merchantReferenceId": reference,
|
|
58
|
+
"reason": reason,
|
|
59
|
+
"raw": raw,
|
|
60
|
+
]
|
|
61
|
+
case let .failed(reference, failure):
|
|
62
|
+
return [
|
|
63
|
+
"outcome": "failed",
|
|
64
|
+
"merchantReferenceId": reference,
|
|
65
|
+
"failure": self.failure(failure),
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static func receipt(_ receipt: EcrReceipt) -> [String: Any] {
|
|
71
|
+
switch receipt {
|
|
72
|
+
case let .ready(reference, url, raw):
|
|
73
|
+
return [
|
|
74
|
+
"outcome": "ready",
|
|
75
|
+
"merchantReferenceId": reference,
|
|
76
|
+
"url": url,
|
|
77
|
+
"raw": raw,
|
|
78
|
+
]
|
|
79
|
+
case let .unavailable(reference, reason, raw):
|
|
80
|
+
return [
|
|
81
|
+
"outcome": "unavailable",
|
|
82
|
+
"merchantReferenceId": reference,
|
|
83
|
+
"reason": reason,
|
|
84
|
+
"raw": raw,
|
|
85
|
+
]
|
|
86
|
+
case let .failed(reference, failure):
|
|
87
|
+
return [
|
|
88
|
+
"outcome": "failed",
|
|
89
|
+
"merchantReferenceId": reference,
|
|
90
|
+
"failure": self.failure(failure),
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
static func failedResult(
|
|
96
|
+
merchantReferenceId: String,
|
|
97
|
+
kind: String,
|
|
98
|
+
message: String
|
|
99
|
+
) -> [String: Any] {
|
|
100
|
+
[
|
|
101
|
+
"outcome": "failed",
|
|
102
|
+
"merchantReferenceId": merchantReferenceId,
|
|
103
|
+
"failure": ["kind": kind, "message": message],
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private static func failure(_ failure: EcrFailure) -> [String: Any] {
|
|
108
|
+
let kind: String
|
|
109
|
+
switch failure {
|
|
110
|
+
case .unreachable: kind = "unreachable"
|
|
111
|
+
case .timeout: kind = "timeout"
|
|
112
|
+
case .malformed: kind = "malformed"
|
|
113
|
+
case .connectionLost: kind = "connectionLost"
|
|
114
|
+
case .unauthenticated: kind = "unauthenticated"
|
|
115
|
+
}
|
|
116
|
+
return ["kind": kind, "message": failure.message]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private static func transaction(_ value: EcrTransaction) -> [String: Any] {
|
|
120
|
+
[
|
|
121
|
+
"transactionId": value.transactionId,
|
|
122
|
+
"stan": value.stan,
|
|
123
|
+
"type": value.type,
|
|
124
|
+
"status": value.status,
|
|
125
|
+
"amount": value.amount,
|
|
126
|
+
"totalAmount": value.totalAmount,
|
|
127
|
+
"currency": value.currency,
|
|
128
|
+
"transactionTime": value.transactionTime,
|
|
129
|
+
"maskedPan": value.maskedPan,
|
|
130
|
+
"cardHolderName": value.cardHolderName,
|
|
131
|
+
"rrn": value.rrn,
|
|
132
|
+
"authCode": value.authCode,
|
|
133
|
+
"batchId": value.batchId,
|
|
134
|
+
"terminalId": value.terminalId,
|
|
135
|
+
"isRefunded": value.isRefunded,
|
|
136
|
+
"canVoid": value.canVoid,
|
|
137
|
+
"canRefund": value.canRefund,
|
|
138
|
+
"partialApproval": value.partialApproval,
|
|
139
|
+
"authorizedAmount": value.authorizedAmount,
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
export class EcrOperation {
|
|
4
|
+
cancelRequested = false;
|
|
5
|
+
constructor(operationId, result, onCancel) {
|
|
6
|
+
this.operationId = operationId;
|
|
7
|
+
this.result = result;
|
|
8
|
+
this.onCancel = onCancel;
|
|
9
|
+
}
|
|
10
|
+
get isCancelRequested() {
|
|
11
|
+
return this.cancelRequested;
|
|
12
|
+
}
|
|
13
|
+
cancel() {
|
|
14
|
+
this.cancelRequested = true;
|
|
15
|
+
return this.onCancel(this.operationId);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=EcrOperation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["EcrOperation","cancelRequested","constructor","operationId","result","onCancel","isCancelRequested","cancel"],"sourceRoot":"../../src","sources":["EcrOperation.ts"],"mappings":";;AAAA,OAAO,MAAMA,YAAY,CAAI;EAInBC,eAAe,GAAG,KAAK;EAE/BC,WAAWA,CACTC,WAAmB,EACnBC,MAAkB,EAClBC,QAAmD,EACnD;IACA,IAAI,CAACF,WAAW,GAAGA,WAAW;IAC9B,IAAI,CAACC,MAAM,GAAGA,MAAM;IACpB,IAAI,CAACC,QAAQ,GAAGA,QAAQ;EAC1B;EAEA,IAAIC,iBAAiBA,CAAA,EAAY;IAC/B,OAAO,IAAI,CAACL,eAAe;EAC7B;EAEAM,MAAMA,CAAA,EAAqB;IACzB,IAAI,CAACN,eAAe,GAAG,IAAI;IAC3B,OAAO,IAAI,CAACI,QAAQ,CAAC,IAAI,CAACF,WAAW,CAAC;EACxC;AACF","ignoreList":[]}
|