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.
Files changed (57) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/LICENSE +20 -0
  3. package/README.md +278 -0
  4. package/RNAmwalEcr.podspec +27 -0
  5. package/android/build.gradle +53 -0
  6. package/android/src/main/AndroidManifest.xml +3 -0
  7. package/android/src/main/java/com/amwalecr/AmwalEcrModule.kt +325 -0
  8. package/android/src/main/java/com/amwalecr/AmwalEcrPackage.kt +31 -0
  9. package/android/src/main/java/com/amwalecr/EcrMapping.kt +135 -0
  10. package/docs/compatibility.md +22 -0
  11. package/ios/AmwalEcr.h +4 -0
  12. package/ios/AmwalEcr.mm +121 -0
  13. package/ios/AmwalEcr.swift +424 -0
  14. package/ios/EcrMapping.swift +142 -0
  15. package/lib/module/EcrOperation.js +18 -0
  16. package/lib/module/EcrOperation.js.map +1 -0
  17. package/lib/module/EcrTerminal.js +211 -0
  18. package/lib/module/EcrTerminal.js.map +1 -0
  19. package/lib/module/NativeAmwalEcr.js +5 -0
  20. package/lib/module/NativeAmwalEcr.js.map +1 -0
  21. package/lib/module/index.js +6 -0
  22. package/lib/module/index.js.map +1 -0
  23. package/lib/module/native.js +16 -0
  24. package/lib/module/native.js.map +1 -0
  25. package/lib/module/native.native.js +4 -0
  26. package/lib/module/native.native.js.map +1 -0
  27. package/lib/module/normalize.js +87 -0
  28. package/lib/module/normalize.js.map +1 -0
  29. package/lib/module/package.json +1 -0
  30. package/lib/module/types.js +2 -0
  31. package/lib/module/types.js.map +1 -0
  32. package/lib/typescript/package.json +1 -0
  33. package/lib/typescript/src/EcrOperation.d.ts +10 -0
  34. package/lib/typescript/src/EcrOperation.d.ts.map +1 -0
  35. package/lib/typescript/src/EcrTerminal.d.ts +34 -0
  36. package/lib/typescript/src/EcrTerminal.d.ts.map +1 -0
  37. package/lib/typescript/src/NativeAmwalEcr.d.ts +14 -0
  38. package/lib/typescript/src/NativeAmwalEcr.d.ts.map +1 -0
  39. package/lib/typescript/src/index.d.ts +5 -0
  40. package/lib/typescript/src/index.d.ts.map +1 -0
  41. package/lib/typescript/src/native.d.ts +3 -0
  42. package/lib/typescript/src/native.d.ts.map +1 -0
  43. package/lib/typescript/src/native.native.d.ts +2 -0
  44. package/lib/typescript/src/native.native.d.ts.map +1 -0
  45. package/lib/typescript/src/normalize.d.ts +5 -0
  46. package/lib/typescript/src/normalize.d.ts.map +1 -0
  47. package/lib/typescript/src/types.d.ts +157 -0
  48. package/lib/typescript/src/types.d.ts.map +1 -0
  49. package/package.json +153 -0
  50. package/src/EcrOperation.ts +25 -0
  51. package/src/EcrTerminal.ts +344 -0
  52. package/src/NativeAmwalEcr.ts +28 -0
  53. package/src/index.tsx +34 -0
  54. package/src/native.native.ts +1 -0
  55. package/src/native.ts +18 -0
  56. package/src/normalize.ts +116 -0
  57. package/src/types.ts +189 -0
@@ -0,0 +1,325 @@
1
+ package com.amwalecr
2
+
3
+ import com.amwalpay.ecr.EcrConfig
4
+ import com.amwalpay.ecr.EcrEnvironment
5
+ import com.amwalpay.ecr.EcrLink
6
+ import com.amwalpay.ecr.EcrLogger
7
+ import com.amwalpay.ecr.EcrOpenedSession
8
+ import com.amwalpay.ecr.EcrSessions
9
+ import com.facebook.react.bridge.Promise
10
+ import com.facebook.react.bridge.ReactApplicationContext
11
+ import com.facebook.react.bridge.ReadableMap
12
+ import com.facebook.react.module.annotations.ReactModule
13
+ import java.math.BigDecimal
14
+ import java.util.concurrent.ConcurrentHashMap
15
+ import java.util.concurrent.atomic.AtomicBoolean
16
+ import kotlin.time.Duration.Companion.milliseconds
17
+ import kotlinx.coroutines.CoroutineScope
18
+ import kotlinx.coroutines.CoroutineStart
19
+ import kotlinx.coroutines.Dispatchers
20
+ import kotlinx.coroutines.Job
21
+ import kotlinx.coroutines.SupervisorJob
22
+ import kotlinx.coroutines.cancel
23
+ import kotlinx.coroutines.launch
24
+
25
+ @ReactModule(name = AmwalEcrModule.NAME)
26
+ class AmwalEcrModule(reactContext: ReactApplicationContext) :
27
+ NativeAmwalEcrSpec(reactContext) {
28
+
29
+ private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
30
+ private val running = ConcurrentHashMap<String, RunningOperation>()
31
+
32
+ override fun isReachable(request: ReadableMap, promise: Promise) {
33
+ val call = parse(request, promise) ?: return
34
+ if (!call.isIpTransport) {
35
+ promise.resolve(false)
36
+ return
37
+ }
38
+ scope.launch {
39
+ try {
40
+ promise.resolve(call.session.probeReachability()?.reachable == true)
41
+ } catch (error: IllegalArgumentException) {
42
+ promise.reject(ECR_INVALID_ARGUMENT, error.message ?: "Invalid ECR arguments", error)
43
+ } catch (error: Exception) {
44
+ promise.reject(ECR_INTERNAL, error.message ?: "Reachability probe failed", error)
45
+ }
46
+ }
47
+ }
48
+
49
+ override fun sale(request: ReadableMap, promise: Promise) {
50
+ run(request, promise) { call ->
51
+ EcrMapping.result(
52
+ call.session.sale(
53
+ amount = call.requiredAmount(),
54
+ merchantReference = call.merchantReferenceId,
55
+ ),
56
+ )
57
+ }
58
+ }
59
+
60
+ override fun voidTransaction(request: ReadableMap, promise: Promise) {
61
+ run(request, promise) { call ->
62
+ EcrMapping.result(
63
+ call.session.void(
64
+ receiptNumber = call.requiredReceiptNumber("A void"),
65
+ originalTerminalId = call.originalTerminalId,
66
+ merchantReference = call.merchantReferenceId,
67
+ ),
68
+ )
69
+ }
70
+ }
71
+
72
+ override fun refund(request: ReadableMap, promise: Promise) {
73
+ run(request, promise) { call ->
74
+ EcrMapping.result(
75
+ call.session.refund(
76
+ amount = call.requiredAmount(),
77
+ receiptNumber = call.requiredReceiptNumber("A refund"),
78
+ transactionDate = call.transactionDate,
79
+ originalTerminalId = call.originalTerminalId,
80
+ merchantReference = call.merchantReferenceId,
81
+ ),
82
+ )
83
+ }
84
+ }
85
+
86
+ override fun inquire(request: ReadableMap, promise: Promise) {
87
+ run(request, promise) { call ->
88
+ EcrMapping.inquiry(
89
+ call.session.inquire(
90
+ receiptNumber = call.requiredReceiptNumber("An inquiry"),
91
+ transactionDate = call.transactionDate,
92
+ originalTerminalId = call.originalTerminalId,
93
+ ),
94
+ )
95
+ }
96
+ }
97
+
98
+ override fun inquireByReference(request: ReadableMap, promise: Promise) {
99
+ run(request, promise) { call ->
100
+ EcrMapping.inquiry(
101
+ call.session.inquireByReference(
102
+ originalReference = call.requiredOriginalReference(),
103
+ transactionDate = call.transactionDate,
104
+ originalTerminalId = call.originalTerminalId,
105
+ merchantReference = call.merchantReferenceId,
106
+ ),
107
+ )
108
+ }
109
+ }
110
+
111
+ override fun receipt(request: ReadableMap, promise: Promise) {
112
+ run(request, promise) { call ->
113
+ EcrMapping.receipt(
114
+ call.session.receipt(
115
+ receiptNumber = call.requiredReceiptNumber("A receipt"),
116
+ transactionDate = call.transactionDate,
117
+ originalTerminalId = call.originalTerminalId,
118
+ ),
119
+ )
120
+ }
121
+ }
122
+
123
+ override fun cancel(operationId: String, promise: Promise) {
124
+ val operation = running.remove(operationId)
125
+ if (operation == null) {
126
+ promise.resolve(false)
127
+ return
128
+ }
129
+
130
+ operation.reply.resolve(
131
+ EcrMapping.failedResult(
132
+ merchantReferenceId = operation.merchantReferenceId,
133
+ kind = EcrMapping.CANCELLED,
134
+ message = CANCELLED_MESSAGE,
135
+ ),
136
+ )
137
+ operation.job.cancel()
138
+ promise.resolve(true)
139
+ }
140
+
141
+ override fun invalidate() {
142
+ running.values.forEach { operation ->
143
+ operation.reply.resolve(
144
+ EcrMapping.failedResult(
145
+ merchantReferenceId = operation.merchantReferenceId,
146
+ kind = EcrMapping.CANCELLED,
147
+ message = "The React Native host was invalidated while the terminal had the request. " +
148
+ "The transaction may still have completed — inquire before retrying.",
149
+ ),
150
+ )
151
+ operation.job.cancel()
152
+ }
153
+ running.clear()
154
+ scope.cancel()
155
+ super.invalidate()
156
+ }
157
+
158
+ private fun run(
159
+ request: ReadableMap,
160
+ promise: Promise,
161
+ block: suspend (Call) -> Any,
162
+ ) {
163
+ val call = parse(request, promise) ?: return
164
+ val reply = OneShotPromise(promise)
165
+
166
+ if (!call.isSupportedTransport) {
167
+ reply.resolve(
168
+ EcrMapping.failedResult(
169
+ merchantReferenceId = "",
170
+ kind = EcrMapping.UNSUPPORTED,
171
+ message = "This library drives a terminal over ethernet, wifi, or the Web Service " +
172
+ "REST path. Transport '${call.transport}' is not handled, so nothing was sent.",
173
+ ),
174
+ )
175
+ return
176
+ }
177
+
178
+ lateinit var operation: RunningOperation
179
+ val job = scope.launch(start = CoroutineStart.LAZY) {
180
+ try {
181
+ reply.resolve(block(call))
182
+ } catch (error: IllegalArgumentException) {
183
+ reply.reject(ECR_INVALID_ARGUMENT, error.message ?: "Invalid ECR arguments", error)
184
+ } catch (error: Exception) {
185
+ reply.reject(ECR_INTERNAL, error.message ?: "Native ECR operation failed", error)
186
+ } finally {
187
+ running.remove(call.operationId, operation)
188
+ }
189
+ }
190
+ operation = RunningOperation(job, reply, call.merchantReferenceId)
191
+
192
+ if (running.putIfAbsent(call.operationId, operation) != null) {
193
+ reply.reject(
194
+ ECR_INVALID_ARGUMENT,
195
+ "operationId '${call.operationId}' is already running",
196
+ null,
197
+ )
198
+ return
199
+ }
200
+ job.start()
201
+ }
202
+
203
+ private fun parse(request: ReadableMap, promise: Promise): Call? = try {
204
+ Call(request)
205
+ } catch (error: IllegalArgumentException) {
206
+ promise.reject(ECR_INVALID_ARGUMENT, error.message ?: "Invalid ECR arguments", error)
207
+ null
208
+ } catch (error: Exception) {
209
+ promise.reject(ECR_INTERNAL, error.message ?: "Could not read ECR arguments", error)
210
+ null
211
+ }
212
+
213
+ private data class RunningOperation(
214
+ val job: Job,
215
+ val reply: OneShotPromise,
216
+ val merchantReferenceId: String,
217
+ )
218
+
219
+ private class OneShotPromise(private val promise: Promise) {
220
+ private val answered = AtomicBoolean(false)
221
+
222
+ fun resolve(value: Any?) {
223
+ if (answered.compareAndSet(false, true)) promise.resolve(value)
224
+ }
225
+
226
+ fun reject(code: String, message: String, error: Throwable?) {
227
+ if (answered.compareAndSet(false, true)) promise.reject(code, message, error)
228
+ }
229
+ }
230
+
231
+ private class Call(private val raw: ReadableMap) {
232
+ val operationId = raw.requiredString("operationId")
233
+ val host = raw.optionalString("host")
234
+ val serialNumber = raw.optionalString("serialNumber")
235
+ val transport = raw.optionalString("transport").ifEmpty { "wifi" }
236
+ val transactionDate = raw.optionalString("transactionDate")
237
+ val originalTerminalId = raw.optionalString("originalTerminalId")
238
+ val merchantReferenceId = raw.optionalString("merchantReferenceId")
239
+ private val config = raw.config()
240
+
241
+ val isIpTransport get() = transport == "ethernet" || transport == "wifi"
242
+ val isWebService get() = transport == "webService" || transport == "web_service"
243
+ val isSupportedTransport get() = isIpTransport || isWebService
244
+
245
+ /**
246
+ * The SDK session for this call's transport: TCP for ethernet/wifi, the
247
+ * hosted REST path for webService. Built lazily so an unsupported
248
+ * transport is answered as a typed failure before any session exists.
249
+ */
250
+ val session: EcrOpenedSession by lazy {
251
+ val link = if (isWebService) {
252
+ EcrLink.WebService(
253
+ merchantId = config.merchantId,
254
+ terminalId = config.terminalId,
255
+ )
256
+ } else {
257
+ EcrLink.Lan(host = host, port = config.port)
258
+ }
259
+ val plan = EcrSessions.plan(link = link, config = config)
260
+ require(plan.isReady) { plan.issues.joinToString("; ") }
261
+ EcrSessions.open(
262
+ terminalSerial = serialNumber,
263
+ plan = plan,
264
+ logger = EcrLogger.None,
265
+ )
266
+ }
267
+
268
+ fun requiredAmount(): BigDecimal {
269
+ val text = raw.requiredString("amount")
270
+ return try {
271
+ BigDecimal(text)
272
+ } catch (error: NumberFormatException) {
273
+ throw IllegalArgumentException("'$text' is not a decimal amount", error)
274
+ }
275
+ }
276
+
277
+ fun requiredReceiptNumber(operation: String): String =
278
+ raw.optionalString("receiptNumber").ifBlank {
279
+ throw IllegalArgumentException("$operation needs the original receipt number")
280
+ }
281
+
282
+ fun requiredOriginalReference(): String =
283
+ raw.optionalString("originalMerchantReference").ifBlank {
284
+ throw IllegalArgumentException("An inquiry by reference needs the original reference")
285
+ }
286
+ }
287
+
288
+ companion object {
289
+ const val NAME = NativeAmwalEcrSpec.NAME
290
+ private const val ECR_INVALID_ARGUMENT = "ECR_INVALID_ARGUMENT"
291
+ private const val ECR_INTERNAL = "ECR_INTERNAL"
292
+ private const val CANCELLED_MESSAGE =
293
+ "The caller cancelled while the terminal had the request. " +
294
+ "The transaction may still have completed — inquire before retrying."
295
+ }
296
+ }
297
+
298
+ private fun ReadableMap.requiredString(key: String): String {
299
+ val value = optionalString(key).trim()
300
+ require(value.isNotEmpty()) { "'$key' is required and was missing" }
301
+ return value
302
+ }
303
+
304
+ private fun ReadableMap.optionalString(key: String): String =
305
+ if (hasKey(key) && !isNull(key)) getString(key).orEmpty() else ""
306
+
307
+ private fun ReadableMap.config(): EcrConfig {
308
+ val source = if (hasKey("config") && !isNull("config")) getMap("config") else null
309
+ if (source == null) return EcrConfig()
310
+
311
+ return EcrConfig(
312
+ ecrId = source.optionalString("ecrId").ifEmpty { "ECR01" },
313
+ currencyCode = source.optionalString("currencyCode").ifEmpty { "512" },
314
+ minorUnitDigits = if (source.hasKey("minorUnitDigits")) source.getInt("minorUnitDigits") else 3,
315
+ port = if (source.hasKey("port")) source.getInt("port") else EcrConfig.DEFAULT_PORT,
316
+ connectTimeout = (if (source.hasKey("connectTimeoutMs")) source.getDouble("connectTimeoutMs").toLong() else 10_000L).milliseconds,
317
+ responseTimeout = (if (source.hasKey("responseTimeoutMs")) source.getDouble("responseTimeoutMs").toLong() else 120_000L).milliseconds,
318
+ probeTimeout = (if (source.hasKey("probeTimeoutMs")) source.getDouble("probeTimeoutMs").toLong() else 3_000L).milliseconds,
319
+ secureHashKey = source.optionalString("secureHashKey"),
320
+ autoInquireOnFailure = if (source.hasKey("autoInquireOnFailure")) source.getBoolean("autoInquireOnFailure") else true,
321
+ merchantId = source.optionalString("merchantId"),
322
+ terminalId = source.optionalString("terminalId"),
323
+ environment = EcrEnvironment.fromName(source.optionalString("environment")),
324
+ )
325
+ }
@@ -0,0 +1,31 @@
1
+ package com.amwalecr
2
+
3
+ import com.facebook.react.BaseReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfo
7
+ import com.facebook.react.module.model.ReactModuleInfoProvider
8
+ import java.util.HashMap
9
+
10
+ class AmwalEcrPackage : BaseReactPackage() {
11
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
12
+ return if (name == AmwalEcrModule.NAME) {
13
+ AmwalEcrModule(reactContext)
14
+ } else {
15
+ null
16
+ }
17
+ }
18
+
19
+ override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
20
+ mapOf(
21
+ AmwalEcrModule.NAME to ReactModuleInfo(
22
+ name = AmwalEcrModule.NAME,
23
+ className = AmwalEcrModule.NAME,
24
+ canOverrideExistingModule = false,
25
+ needsEagerInit = false,
26
+ isCxxModule = false,
27
+ isTurboModule = true
28
+ )
29
+ )
30
+ }
31
+ }
@@ -0,0 +1,135 @@
1
+ package com.amwalecr
2
+
3
+ import com.amwalpay.ecr.EcrInquiry
4
+ import com.amwalpay.ecr.EcrReceipt
5
+ import com.amwalpay.ecr.EcrResult
6
+ import com.amwalpay.ecr.EcrTransaction
7
+ import com.amwalpay.ecr.Failure
8
+ import com.facebook.react.bridge.Arguments
9
+ import com.facebook.react.bridge.WritableMap
10
+
11
+ internal object EcrMapping {
12
+ const val CANCELLED = "cancelled"
13
+ const val UNSUPPORTED = "unsupported"
14
+
15
+ fun result(result: EcrResult): WritableMap = when (result) {
16
+ is EcrResult.Approved -> Arguments.createMap().apply {
17
+ putString("outcome", "approved")
18
+ putString("merchantReferenceId", result.merchantReference)
19
+ putString("amount", result.amount)
20
+ putString("responseCode", result.responseCode)
21
+ putString("rrn", result.rrn)
22
+ putString("authCode", result.authCode)
23
+ putString("maskedPan", result.maskedPan)
24
+ putBoolean("partialApproval", result.partialApproval)
25
+ putString("requestedAmount", result.requestedAmount)
26
+ putString("raw", result.raw)
27
+ }
28
+
29
+ is EcrResult.Declined -> Arguments.createMap().apply {
30
+ putString("outcome", "declined")
31
+ putString("merchantReferenceId", result.merchantReference)
32
+ putString("responseCode", result.responseCode)
33
+ putString("reason", result.reason)
34
+ putString("nextStep", result.nextStep.name)
35
+ putString("raw", result.raw)
36
+ }
37
+
38
+ is EcrResult.Failed -> Arguments.createMap().apply {
39
+ putString("outcome", "failed")
40
+ putString("merchantReferenceId", result.merchantReference)
41
+ putMap("failure", failure(result.failure))
42
+ result.recovered?.let { putMap("recovered", inquiry(it)) }
43
+ }
44
+ }
45
+
46
+ fun inquiry(inquiry: EcrInquiry): WritableMap = when (inquiry) {
47
+ is EcrInquiry.Found -> Arguments.createMap().apply {
48
+ putString("outcome", "found")
49
+ putString("merchantReferenceId", inquiry.merchantReference)
50
+ putMap("transaction", transaction(inquiry.transaction))
51
+ putString("raw", inquiry.raw)
52
+ }
53
+
54
+ is EcrInquiry.NotFound -> Arguments.createMap().apply {
55
+ putString("outcome", "notFound")
56
+ putString("merchantReferenceId", inquiry.merchantReference)
57
+ putString("reason", inquiry.reason)
58
+ putString("raw", inquiry.raw)
59
+ }
60
+
61
+ is EcrInquiry.Failed -> Arguments.createMap().apply {
62
+ putString("outcome", "failed")
63
+ putString("merchantReferenceId", inquiry.merchantReference)
64
+ putMap("failure", failure(inquiry.failure))
65
+ }
66
+ }
67
+
68
+ fun receipt(receipt: EcrReceipt): WritableMap = when (receipt) {
69
+ is EcrReceipt.Ready -> Arguments.createMap().apply {
70
+ putString("outcome", "ready")
71
+ putString("merchantReferenceId", receipt.merchantReference)
72
+ putString("url", receipt.url)
73
+ putString("raw", receipt.raw)
74
+ }
75
+
76
+ is EcrReceipt.Unavailable -> Arguments.createMap().apply {
77
+ putString("outcome", "unavailable")
78
+ putString("merchantReferenceId", receipt.merchantReference)
79
+ putString("reason", receipt.reason)
80
+ putString("raw", receipt.raw)
81
+ }
82
+
83
+ is EcrReceipt.Failed -> Arguments.createMap().apply {
84
+ putString("outcome", "failed")
85
+ putString("merchantReferenceId", receipt.merchantReference)
86
+ putMap("failure", failure(receipt.failure))
87
+ }
88
+ }
89
+
90
+ fun failedResult(
91
+ merchantReferenceId: String,
92
+ kind: String,
93
+ message: String,
94
+ ): WritableMap = Arguments.createMap().apply {
95
+ putString("outcome", "failed")
96
+ putString("merchantReferenceId", merchantReferenceId)
97
+ putMap("failure", Arguments.createMap().apply {
98
+ putString("kind", kind)
99
+ putString("message", message)
100
+ })
101
+ }
102
+
103
+ private fun transaction(value: EcrTransaction): WritableMap = Arguments.createMap().apply {
104
+ putString("transactionId", value.transactionId)
105
+ putString("stan", value.stan)
106
+ putString("type", value.type)
107
+ putString("status", value.status)
108
+ putString("amount", value.amount)
109
+ putString("totalAmount", value.totalAmount)
110
+ putString("currency", value.currency)
111
+ putString("transactionTime", value.transactionTime)
112
+ putString("maskedPan", value.maskedPan)
113
+ putString("cardHolderName", value.cardHolderName)
114
+ putString("rrn", value.rrn)
115
+ putString("authCode", value.authCode)
116
+ putString("batchId", value.batchId)
117
+ putString("terminalId", value.terminalId)
118
+ putBoolean("isRefunded", value.isRefunded)
119
+ putBoolean("canVoid", value.canVoid)
120
+ putBoolean("canRefund", value.canRefund)
121
+ putBoolean("partialApproval", value.partialApproval)
122
+ putString("authorizedAmount", value.authorizedAmount)
123
+ }
124
+
125
+ private fun failure(value: Failure): WritableMap = Arguments.createMap().apply {
126
+ putString("kind", when (value) {
127
+ is Failure.Unreachable -> "unreachable"
128
+ is Failure.Timeout -> "timeout"
129
+ is Failure.Malformed -> "malformed"
130
+ is Failure.ConnectionLost -> "connectionLost"
131
+ is Failure.Unauthenticated -> "unauthenticated"
132
+ })
133
+ putString("message", value.message)
134
+ }
135
+ }
@@ -0,0 +1,22 @@
1
+ # Compatibility
2
+
3
+ | Layer | Version or minimum |
4
+ |---|---|
5
+ | `react-native-amwal-ecr` | 0.1.0 |
6
+ | React Native | 0.87.1 development baseline; peer dependency accepts supported host versions |
7
+ | Android provider | `com.amwal-pay:ecr-sdk:1.0.5` exactly |
8
+ | Android | API 24+, Java 17, compile SDK 36 |
9
+ | iOS provider | `AmwalECR ~> 0.2.1` |
10
+ | iOS | 15.1+ with the current React Native baseline |
11
+ | Protocol | ECR protocol version 1 |
12
+
13
+ Android is pinned exactly because changing how a payment outcome is interpreted
14
+ must be deliberate. iOS uses the provider's compatible patch line so CocoaPods
15
+ can resolve a host application that also uses `AmwalECR` directly.
16
+
17
+ The two provider versions must move together and only after their cross-platform
18
+ contract fixtures pass. `ethernet`, `wifi` and `webService` are supported —
19
+ the first two over TCP on the local network, `webService` through the Amwal
20
+ payment host over REST (`merchantId`, `terminalId` and `environment` in the
21
+ config select the terminal; receipt fetch stays local-only). `bluetooth` and
22
+ `usbCable` are reported as unsupported before anything is sent.
package/ios/AmwalEcr.h ADDED
@@ -0,0 +1,4 @@
1
+ #import <AmwalEcrSpec/AmwalEcrSpec.h>
2
+
3
+ @interface AmwalEcr : NSObject <NativeAmwalEcrSpec>
4
+ @end
@@ -0,0 +1,121 @@
1
+ #import "AmwalEcr.h"
2
+
3
+ @interface AmwalEcrImpl : NSObject
4
+ - (void)isReachable:(NSDictionary *)request
5
+ resolver:(RCTPromiseResolveBlock)resolve
6
+ rejecter:(RCTPromiseRejectBlock)reject;
7
+ - (void)sale:(NSDictionary *)request
8
+ resolver:(RCTPromiseResolveBlock)resolve
9
+ rejecter:(RCTPromiseRejectBlock)reject;
10
+ - (void)voidTransaction:(NSDictionary *)request
11
+ resolver:(RCTPromiseResolveBlock)resolve
12
+ rejecter:(RCTPromiseRejectBlock)reject;
13
+ - (void)refund:(NSDictionary *)request
14
+ resolver:(RCTPromiseResolveBlock)resolve
15
+ rejecter:(RCTPromiseRejectBlock)reject;
16
+ - (void)inquire:(NSDictionary *)request
17
+ resolver:(RCTPromiseResolveBlock)resolve
18
+ rejecter:(RCTPromiseRejectBlock)reject;
19
+ - (void)inquireByReference:(NSDictionary *)request
20
+ resolver:(RCTPromiseResolveBlock)resolve
21
+ rejecter:(RCTPromiseRejectBlock)reject;
22
+ - (void)receipt:(NSDictionary *)request
23
+ resolver:(RCTPromiseResolveBlock)resolve
24
+ rejecter:(RCTPromiseRejectBlock)reject;
25
+ - (void)cancel:(NSString *)operationId
26
+ resolver:(RCTPromiseResolveBlock)resolve
27
+ rejecter:(RCTPromiseRejectBlock)reject;
28
+ - (void)invalidate;
29
+ @end
30
+
31
+ @implementation AmwalEcr {
32
+ AmwalEcrImpl *_implementation;
33
+ }
34
+
35
+ - (instancetype)init
36
+ {
37
+ self = [super init];
38
+ if (self) {
39
+ _implementation = [AmwalEcrImpl new];
40
+ }
41
+ return self;
42
+ }
43
+
44
+ - (void)isReachable:(NSDictionary *)request
45
+ resolve:(RCTPromiseResolveBlock)resolve
46
+ reject:(RCTPromiseRejectBlock)reject
47
+ {
48
+ [_implementation isReachable:request resolver:resolve rejecter:reject];
49
+ }
50
+
51
+ - (void)sale:(NSDictionary *)request
52
+ resolve:(RCTPromiseResolveBlock)resolve
53
+ reject:(RCTPromiseRejectBlock)reject
54
+ {
55
+ [_implementation sale:request resolver:resolve rejecter:reject];
56
+ }
57
+
58
+ - (void)voidTransaction:(NSDictionary *)request
59
+ resolve:(RCTPromiseResolveBlock)resolve
60
+ reject:(RCTPromiseRejectBlock)reject
61
+ {
62
+ [_implementation voidTransaction:request resolver:resolve rejecter:reject];
63
+ }
64
+
65
+ - (void)refund:(NSDictionary *)request
66
+ resolve:(RCTPromiseResolveBlock)resolve
67
+ reject:(RCTPromiseRejectBlock)reject
68
+ {
69
+ [_implementation refund:request resolver:resolve rejecter:reject];
70
+ }
71
+
72
+ - (void)inquire:(NSDictionary *)request
73
+ resolve:(RCTPromiseResolveBlock)resolve
74
+ reject:(RCTPromiseRejectBlock)reject
75
+ {
76
+ [_implementation inquire:request resolver:resolve rejecter:reject];
77
+ }
78
+
79
+ - (void)inquireByReference:(NSDictionary *)request
80
+ resolve:(RCTPromiseResolveBlock)resolve
81
+ reject:(RCTPromiseRejectBlock)reject
82
+ {
83
+ [_implementation inquireByReference:request resolver:resolve rejecter:reject];
84
+ }
85
+
86
+ - (void)receipt:(NSDictionary *)request
87
+ resolve:(RCTPromiseResolveBlock)resolve
88
+ reject:(RCTPromiseRejectBlock)reject
89
+ {
90
+ [_implementation receipt:request resolver:resolve rejecter:reject];
91
+ }
92
+
93
+ - (void)cancel:(NSString *)operationId
94
+ resolve:(RCTPromiseResolveBlock)resolve
95
+ reject:(RCTPromiseRejectBlock)reject
96
+ {
97
+ [_implementation cancel:operationId resolver:resolve rejecter:reject];
98
+ }
99
+
100
+ - (void)invalidate
101
+ {
102
+ [_implementation invalidate];
103
+ }
104
+
105
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
106
+ (const facebook::react::ObjCTurboModule::InitParams &)params
107
+ {
108
+ return std::make_shared<facebook::react::NativeAmwalEcrSpecJSI>(params);
109
+ }
110
+
111
+ + (NSString *)moduleName
112
+ {
113
+ return @"AmwalEcr";
114
+ }
115
+
116
+ + (BOOL)requiresMainQueueSetup
117
+ {
118
+ return NO;
119
+ }
120
+
121
+ @end