@capacitor-community/stripe-terminal 6.4.0 → 6.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.
@@ -0,0 +1,910 @@
1
+ package com.getcapacitor.community.stripe.terminal
2
+
3
+ import android.Manifest
4
+ import android.app.Activity
5
+ import android.app.Application
6
+ import android.bluetooth.BluetoothAdapter
7
+ import android.content.Context
8
+ import android.content.pm.PackageManager
9
+ import android.os.Build
10
+ import android.util.Log
11
+ import androidx.core.app.ActivityCompat
12
+ import androidx.core.util.Supplier
13
+ import com.getcapacitor.JSArray
14
+ import com.getcapacitor.JSObject
15
+ import com.getcapacitor.PluginCall
16
+ import com.getcapacitor.community.stripe.terminal.helper.TerminalMappers
17
+ import com.getcapacitor.community.stripe.terminal.models.Executor
18
+ import com.google.android.gms.common.util.BiConsumer
19
+ import com.stripe.stripeterminal.Terminal
20
+ import com.stripe.stripeterminal.Terminal.Companion.initTerminal
21
+ import com.stripe.stripeterminal.Terminal.Companion.isInitialized
22
+ import com.stripe.stripeterminal.TerminalApplicationDelegate.onCreate
23
+ import com.stripe.stripeterminal.external.callable.Callback
24
+ import com.stripe.stripeterminal.external.callable.Cancelable
25
+ import com.stripe.stripeterminal.external.callable.DiscoveryListener
26
+ import com.stripe.stripeterminal.external.callable.InternetReaderListener
27
+ import com.stripe.stripeterminal.external.callable.MobileReaderListener
28
+ import com.stripe.stripeterminal.external.callable.PaymentIntentCallback
29
+ import com.stripe.stripeterminal.external.callable.ReaderCallback
30
+ import com.stripe.stripeterminal.external.callable.TapToPayReaderListener
31
+ import com.stripe.stripeterminal.external.callable.TerminalListener
32
+ import com.stripe.stripeterminal.external.models.BatteryStatus
33
+ import com.stripe.stripeterminal.external.models.CardPresentDetails
34
+ import com.stripe.stripeterminal.external.models.Cart
35
+ import com.stripe.stripeterminal.external.models.CartLineItem
36
+ import com.stripe.stripeterminal.external.models.CollectConfiguration
37
+ import com.stripe.stripeterminal.external.models.ConnectionConfiguration
38
+ import com.stripe.stripeterminal.external.models.ConnectionStatus
39
+ import com.stripe.stripeterminal.external.models.DisconnectReason
40
+ import com.stripe.stripeterminal.external.models.DiscoveryConfiguration
41
+ import com.stripe.stripeterminal.external.models.PaymentIntent
42
+ import com.stripe.stripeterminal.external.models.PaymentStatus
43
+ import com.stripe.stripeterminal.external.models.Reader
44
+ import com.stripe.stripeterminal.external.models.ReaderDisplayMessage
45
+ import com.stripe.stripeterminal.external.models.ReaderEvent
46
+ import com.stripe.stripeterminal.external.models.ReaderInputOptions
47
+ import com.stripe.stripeterminal.external.models.ReaderSoftwareUpdate
48
+ import com.stripe.stripeterminal.external.models.SimulateReaderUpdate
49
+ import com.stripe.stripeterminal.external.models.SimulatedCard
50
+ import com.stripe.stripeterminal.external.models.SimulatorConfiguration
51
+ import com.stripe.stripeterminal.external.models.TerminalException
52
+ import com.stripe.stripeterminal.log.LogLevel
53
+ import org.json.JSONException
54
+ import org.json.JSONObject
55
+ import java.util.Objects
56
+
57
+ //import com.stripe.stripeterminal.external.callable.ReaderReconnectionListener;
58
+ class StripeTerminal(
59
+ contextSupplier: Supplier<Context>,
60
+ activitySupplier: Supplier<Activity>,
61
+ notifyListenersFunction: BiConsumer<String, JSObject>,
62
+ pluginLogTag: String
63
+ ) : Executor(
64
+ contextSupplier,
65
+ activitySupplier,
66
+ notifyListenersFunction,
67
+ pluginLogTag,
68
+ "StripeTerminalExecutor"
69
+ ) {
70
+ private var tokenProvider: TokenProvider? = null
71
+ private var discoveryCancelable: Cancelable? = null
72
+ private var collectCancelable: Cancelable? = null
73
+ private var installUpdateCancelable: Cancelable? = null
74
+ private var cancelReaderConnectionCancellable: Cancelable? = null
75
+ private var discoveredReadersList: List<Reader?>
76
+ private var locationId: String? = null
77
+ private var collectCall: PluginCall? = null
78
+ private var confirmPaymentIntentCall: PluginCall? = null
79
+ private val emptyObject = JSObject()
80
+ private var isTest: Boolean? = null
81
+ private var terminalConnectType: TerminalConnectTypes? = null
82
+ private var paymentIntentInstance: PaymentIntent? = null
83
+
84
+ private val terminalMappers = TerminalMappers()
85
+
86
+ @Throws(TerminalException::class)
87
+ fun initialize(call: PluginCall) {
88
+ this.isTest = call.getBoolean("isTest", true)
89
+
90
+ val bluetooth = BluetoothAdapter.getDefaultAdapter()
91
+ if (!bluetooth.isEnabled) {
92
+ if (ActivityCompat.checkSelfPermission(
93
+ contextSupplier.get(),
94
+ Manifest.permission.BLUETOOTH_CONNECT
95
+ ) ==
96
+ PackageManager.PERMISSION_GRANTED
97
+ ) {
98
+ bluetooth.enable()
99
+ }
100
+ }
101
+
102
+ activitySupplier.get()
103
+ .runOnUiThread {
104
+ onCreate((contextSupplier.get().applicationContext as Application))
105
+ notifyListeners(TerminalEnumEvent.Loaded.webEventName, emptyObject)
106
+ call.resolve()
107
+ }
108
+ val listener: TerminalListener = object : TerminalListener {
109
+ override fun onConnectionStatusChange(status: ConnectionStatus) {
110
+ notifyListeners(
111
+ TerminalEnumEvent.ConnectionStatusChange.webEventName,
112
+ JSObject().put("status", status.toString())
113
+ )
114
+ }
115
+
116
+ override fun onPaymentStatusChange(status: PaymentStatus) {
117
+ notifyListeners(
118
+ TerminalEnumEvent.PaymentStatusChange.webEventName,
119
+ JSObject().put("status", status.toString())
120
+ )
121
+ }
122
+ }
123
+ val logLevel = LogLevel.VERBOSE
124
+ this.tokenProvider = TokenProvider(
125
+ this.contextSupplier,
126
+ call.getString("tokenProviderEndpoint", "")!!,
127
+ this.notifyListenersFunction
128
+ )
129
+ if (!isInitialized()) {
130
+ initTerminal(
131
+ contextSupplier.get().applicationContext,
132
+ logLevel,
133
+ this.tokenProvider!!,
134
+ listener
135
+ )
136
+ }
137
+ Terminal.getInstance()
138
+ }
139
+
140
+ fun setConnectionToken(call: PluginCall) {
141
+ tokenProvider!!.setConnectionToken(call)
142
+ }
143
+
144
+ fun setSimulatorConfiguration(call: PluginCall) {
145
+ try {
146
+ val updateString = call.getString("update", "UPDATE_AVAILABLE")
147
+ val simulateReaderUpdate = SimulateReaderUpdate.entries.find { it.name == updateString }
148
+
149
+ Terminal.getInstance()
150
+ .simulatorConfiguration = SimulatorConfiguration(
151
+ simulateReaderUpdate!!,
152
+ SimulatedCard(call.getString("simulatedCard", "VISA")!!),
153
+ call.getLong("simulatedTipAmount", null),
154
+ false
155
+ )
156
+
157
+
158
+ call.resolve()
159
+ } catch (ex: Exception) {
160
+ call.reject(ex.message)
161
+ }
162
+ }
163
+
164
+ fun onDiscoverReaders(call: PluginCall) {
165
+ if (ActivityCompat.checkSelfPermission(
166
+ contextSupplier.get(),
167
+ Manifest.permission.ACCESS_FINE_LOCATION
168
+ ) !=
169
+ PackageManager.PERMISSION_GRANTED
170
+ ) {
171
+ Log.d(this.logTag, "android.permission.ACCESS_FINE_LOCATION permission is not granted.")
172
+ call.reject("android.permission.ACCESS_FINE_LOCATION permission is not granted.")
173
+ return
174
+ }
175
+
176
+ this.locationId = call.getString("locationId")
177
+ val config: DiscoveryConfiguration
178
+ if (call.getString("type") == TerminalConnectTypes.TapToPay.webEventName) {
179
+ config = DiscoveryConfiguration.TapToPayDiscoveryConfiguration(this.isTest!!)
180
+ this.terminalConnectType = TerminalConnectTypes.TapToPay
181
+ } else if (call.getString("type") == TerminalConnectTypes.Internet.webEventName) {
182
+ config = DiscoveryConfiguration.InternetDiscoveryConfiguration(
183
+ 0,
184
+ this.locationId,
185
+ this.isTest!!
186
+ )
187
+ this.terminalConnectType = TerminalConnectTypes.Internet
188
+ } else if (call.getString("type") == TerminalConnectTypes.Usb.webEventName) {
189
+ config = DiscoveryConfiguration.UsbDiscoveryConfiguration(0, this.isTest!!)
190
+ this.terminalConnectType = TerminalConnectTypes.Usb
191
+ } else if (call.getString("type") == TerminalConnectTypes.Bluetooth.webEventName || call.getString(
192
+ "type"
193
+ ) == TerminalConnectTypes.Simulated.webEventName
194
+ ) {
195
+ config = DiscoveryConfiguration.BluetoothDiscoveryConfiguration(0, this.isTest!!)
196
+ this.terminalConnectType = TerminalConnectTypes.Bluetooth
197
+ } else {
198
+ call.unimplemented(call.getString("type") + " is not support now")
199
+ return
200
+ }
201
+
202
+ discoveryCancelable = Terminal.getInstance()
203
+ .discoverReaders(
204
+ config,
205
+ object : DiscoveryListener {
206
+ override fun onUpdateDiscoveredReaders(readers: List<Reader>) {
207
+ Log.d(logTag, readers[0].serialNumber.toString())
208
+ discoveredReadersList = readers
209
+ val readersJSObject = JSArray()
210
+
211
+ val i = 0
212
+ for (reader in discoveredReadersList) {
213
+ readersJSObject.put(convertReaderInterface(reader).put("index", i.toString()))
214
+ }
215
+ notifyListeners(
216
+ TerminalEnumEvent.DiscoveredReaders.webEventName,
217
+ JSObject().put("readers", readersJSObject)
218
+ )
219
+ call.resolve(JSObject().put("readers", readersJSObject))
220
+ }
221
+ },
222
+ object : Callback {
223
+ override fun onSuccess() {
224
+ Log.d(logTag, "Finished discovering readers")
225
+ }
226
+
227
+ override fun onFailure(e: TerminalException) {
228
+ Log.d(logTag, e.localizedMessage)
229
+ }
230
+ }
231
+ )
232
+ }
233
+
234
+ fun connectReader(call: PluginCall) {
235
+ if (this.terminalConnectType == TerminalConnectTypes.TapToPay) {
236
+ this.connectTapToPayReader(call)
237
+ } else if (this.terminalConnectType == TerminalConnectTypes.Internet) {
238
+ this.connectInternetReader(call)
239
+ } else if (this.terminalConnectType == TerminalConnectTypes.Usb) {
240
+ this.connectUsbReader(call)
241
+ } else if (this.terminalConnectType == TerminalConnectTypes.Bluetooth) {
242
+ this.connectBluetoothReader(call)
243
+ } else {
244
+ call.reject("type is not defined.")
245
+ }
246
+ }
247
+
248
+ fun getConnectedReader(call: PluginCall) {
249
+ val reader: Reader? = Terminal.getInstance().connectedReader
250
+ if (reader == null) {
251
+ call.resolve(JSObject().put("reader", JSObject.NULL))
252
+ } else {
253
+ call.resolve(JSObject().put("reader", convertReaderInterface(reader)))
254
+ }
255
+ }
256
+
257
+ fun disconnectReader(call: PluginCall) {
258
+ if (Terminal.getInstance().connectedReader == null) {
259
+ call.resolve()
260
+ return
261
+ }
262
+
263
+ Terminal.getInstance()
264
+ .disconnectReader(
265
+ object : Callback {
266
+ override fun onSuccess() {
267
+ notifyListeners(
268
+ TerminalEnumEvent.DisconnectedReader.webEventName,
269
+ emptyObject
270
+ )
271
+ call.resolve()
272
+ }
273
+
274
+ override fun onFailure(e: TerminalException) {
275
+ call.reject(e.localizedMessage, e)
276
+ }
277
+ }
278
+ )
279
+ }
280
+
281
+ private fun connectTapToPayReader(call: PluginCall) {
282
+ val reader = call.getObject("reader")
283
+ val serialNumber = reader.getString("serialNumber")
284
+ this.locationId = call.getString("locationId", this.locationId)
285
+
286
+ val foundReader = this.findReader(this.discoveredReadersList, serialNumber)
287
+
288
+ if (serialNumber == null || foundReader == null) {
289
+ call.reject("The reader value is not set correctly.")
290
+ return
291
+ }
292
+
293
+ val autoReconnectOnUnexpectedDisconnect: Boolean = Objects.requireNonNullElse(
294
+ call.getBoolean("autoReconnectOnUnexpectedDisconnect", false),
295
+ false
296
+ )
297
+
298
+ val config: ConnectionConfiguration.TapToPayConnectionConfiguration = ConnectionConfiguration.TapToPayConnectionConfiguration(
299
+ this.locationId!!,
300
+ autoReconnectOnUnexpectedDisconnect,
301
+ this.tapToPayReaderListener
302
+ )
303
+ Terminal.getInstance().connectReader(foundReader, config, this.readerCallback(call))
304
+ }
305
+
306
+ var tapToPayReaderListener: TapToPayReaderListener = object : TapToPayReaderListener {
307
+ override fun onReaderReconnectFailed(reader: Reader) {
308
+ notifyListeners(
309
+ TerminalEnumEvent.ReaderReconnectFailed.webEventName,
310
+ JSObject().put("reader", convertReaderInterface(reader))
311
+ )
312
+ }
313
+
314
+ override fun onReaderReconnectStarted(
315
+ reader: Reader,
316
+ cancelReconnect: Cancelable,
317
+ reason: DisconnectReason
318
+ ) {
319
+ cancelReaderConnectionCancellable = cancelReconnect
320
+ notifyListeners(
321
+ TerminalEnumEvent.ReaderReconnectStarted.webEventName,
322
+ JSObject().put("reason", reason.toString())
323
+ .put("reader", convertReaderInterface(reader))
324
+ )
325
+ }
326
+
327
+ override fun onReaderReconnectSucceeded(reader: Reader) {
328
+ notifyListeners(
329
+ TerminalEnumEvent.ReaderReconnectSucceeded.webEventName,
330
+ JSObject().put("reader", convertReaderInterface(reader))
331
+ )
332
+ }
333
+
334
+ override fun onDisconnect(reason: DisconnectReason) {
335
+ notifyListeners(
336
+ TerminalEnumEvent.DisconnectedReader.webEventName,
337
+ JSObject().put("reason", reason.toString())
338
+ )
339
+ }
340
+ }
341
+
342
+ var internetReaderListener: InternetReaderListener = object : InternetReaderListener {
343
+ override fun onDisconnect(reason: DisconnectReason) {
344
+ notifyListeners(
345
+ TerminalEnumEvent.DisconnectedReader.webEventName,
346
+ JSObject().put("reason", reason.toString())
347
+ )
348
+ }
349
+ }
350
+
351
+ // ReaderReconnectionListener readerReconnectionListener = new ReaderReconnectionListener() {
352
+ // @Override
353
+ // public void onReaderReconnectStarted(@NonNull Reader reader, @NonNull Cancelable cancelable, @NonNull DisconnectReason reason) {
354
+ // cancelReaderConnectionCancellable = cancelable;
355
+ // notifyListeners(
356
+ // TerminalEnumEvent.ReaderReconnectStarted.getWebEventName(),
357
+ // new JSObject().put("reason", reason.toString()).put("reader", convertReaderInterface(reader))
358
+ // );
359
+ // }
360
+ //
361
+ // @Override
362
+ // public void onReaderReconnectSucceeded(@NonNull Reader reader) {
363
+ // notifyListeners(
364
+ // TerminalEnumEvent.ReaderReconnectSucceeded.getWebEventName(),
365
+ // new JSObject().put("reader", convertReaderInterface(reader))
366
+ // );
367
+ // }
368
+ //
369
+ // @Override
370
+ // public void onReaderReconnectFailed(@NonNull Reader reader) {
371
+ // notifyListeners(
372
+ // TerminalEnumEvent.ReaderReconnectFailed.getWebEventName(),
373
+ // new JSObject().put("reader", convertReaderInterface(reader))
374
+ // );
375
+ // }
376
+ // };
377
+ private fun connectInternetReader(call: PluginCall) {
378
+ val reader = call.getObject("reader")
379
+ val serialNumber = reader.getString("serialNumber")
380
+ this.locationId = call.getString("locationId", this.locationId)
381
+
382
+ val foundReader = this.findReader(this.discoveredReadersList, serialNumber)
383
+
384
+ if (serialNumber == null || foundReader == null) {
385
+ call.reject("The reader value is not set correctly.")
386
+ return
387
+ }
388
+
389
+ val config: ConnectionConfiguration.InternetConnectionConfiguration =
390
+ ConnectionConfiguration.InternetConnectionConfiguration(
391
+ true,
392
+ this.internetReaderListener
393
+ )
394
+ Terminal.getInstance().connectReader(foundReader, config, this.readerCallback(call))
395
+ }
396
+
397
+ private fun connectUsbReader(call: PluginCall) {
398
+ val reader = call.getObject("reader")
399
+ val serialNumber = reader.getString("serialNumber")
400
+ this.locationId = call.getString("locationId", this.locationId)
401
+
402
+ val foundReader = this.findReader(this.discoveredReadersList, serialNumber)
403
+
404
+ if (serialNumber == null || foundReader == null) {
405
+ call.reject("The reader value is not set correctly.")
406
+ return
407
+ }
408
+
409
+ val config: ConnectionConfiguration.UsbConnectionConfiguration =
410
+ ConnectionConfiguration.UsbConnectionConfiguration(
411
+ this.locationId!!,
412
+ true,
413
+ this.readerListener()
414
+ )
415
+ Terminal.getInstance().connectReader(foundReader, config, this.readerCallback(call))
416
+ }
417
+
418
+ private fun connectBluetoothReader(call: PluginCall) {
419
+ val reader = call.getObject("reader")
420
+ val serialNumber = reader.getString("serialNumber")
421
+ this.locationId = call.getString("locationId", this.locationId)
422
+
423
+ val foundReader = this.findReader(this.discoveredReadersList, serialNumber)
424
+
425
+ if (serialNumber == null || foundReader == null) {
426
+ call.reject("The reader value is not set correctly.")
427
+ return
428
+ }
429
+ val autoReconnectOnUnexpectedDisconnect: Boolean = Objects.requireNonNullElse(
430
+ call.getBoolean("autoReconnectOnUnexpectedDisconnect", false),
431
+ false
432
+ )
433
+
434
+ val config: ConnectionConfiguration.BluetoothConnectionConfiguration =
435
+ ConnectionConfiguration.BluetoothConnectionConfiguration(
436
+ this.locationId!!,
437
+ autoReconnectOnUnexpectedDisconnect,
438
+ this.readerListener()
439
+ )
440
+ Terminal.getInstance().connectReader(foundReader, config, this.readerCallback(call))
441
+ }
442
+
443
+ fun cancelDiscoverReaders(call: PluginCall) {
444
+ if (discoveryCancelable == null || discoveryCancelable!!.isCompleted) {
445
+ call.resolve()
446
+ return
447
+ }
448
+ discoveryCancelable!!.cancel(
449
+ object : Callback {
450
+ override fun onSuccess() {
451
+ notifyListeners(
452
+ TerminalEnumEvent.CancelDiscoveredReaders.webEventName,
453
+ emptyObject
454
+ )
455
+ call.resolve()
456
+ }
457
+
458
+ override fun onFailure(e: TerminalException) {
459
+ call.reject(e.localizedMessage, e)
460
+ }
461
+ }
462
+ )
463
+ }
464
+
465
+ fun collectPaymentMethod(call: PluginCall) {
466
+ val paymentIntent = call.getString("paymentIntent")
467
+ if (paymentIntent == null) {
468
+ call.reject("The value of paymentIntent is not set correctly.")
469
+ return
470
+ }
471
+ this.collectCall = call
472
+ Terminal.getInstance().retrievePaymentIntent(paymentIntent, createPaymentIntentCallback)
473
+ }
474
+
475
+ fun cancelCollectPaymentMethod(call: PluginCall) {
476
+ if (this.collectCancelable == null || collectCancelable!!.isCompleted) {
477
+ call.resolve()
478
+ return
479
+ }
480
+
481
+ collectCancelable!!.cancel(
482
+ object : Callback {
483
+ override fun onSuccess() {
484
+ notifyListeners(TerminalEnumEvent.Canceled.webEventName, emptyObject)
485
+ call.resolve()
486
+ }
487
+
488
+ override fun onFailure(e: TerminalException) {
489
+ call.reject(e.localizedMessage)
490
+ }
491
+ }
492
+ )
493
+ }
494
+
495
+ private val createPaymentIntentCallback: PaymentIntentCallback =
496
+ object : PaymentIntentCallback {
497
+ override fun onSuccess(paymentIntent: PaymentIntent) {
498
+ val collectConfig: CollectConfiguration =
499
+ CollectConfiguration.Builder().updatePaymentIntent(true).build()
500
+ collectCancelable = Terminal.getInstance().collectPaymentMethod(
501
+ paymentIntent,
502
+ collectPaymentMethodCallback,
503
+ collectConfig
504
+ )
505
+ }
506
+
507
+ override fun onFailure(exception: TerminalException) {
508
+ notifyListeners(TerminalEnumEvent.Failed.webEventName, emptyObject)
509
+ val returnObject = JSObject()
510
+ returnObject.put("message", exception.localizedMessage)
511
+ if (exception.apiError != null) {
512
+ returnObject.put("code", exception.apiError!!.code)
513
+ returnObject.put("declineCode", exception.apiError!!.declineCode)
514
+ }
515
+ collectCall!!.reject(exception.localizedMessage, null as String?, returnObject)
516
+ }
517
+ }
518
+
519
+ private val collectPaymentMethodCallback: PaymentIntentCallback =
520
+ object : PaymentIntentCallback {
521
+ override fun onSuccess(paymentIntent: PaymentIntent) {
522
+ paymentIntentInstance = paymentIntent
523
+ notifyListeners(TerminalEnumEvent.CollectedPaymentIntent.webEventName, emptyObject)
524
+
525
+ val pm = paymentIntent.paymentMethod
526
+ var card: CardPresentDetails? = null
527
+
528
+ if (pm != null) {
529
+ card =
530
+ if (pm.cardPresentDetails != null) pm.cardPresentDetails else pm.interacPresentDetails
531
+ }
532
+
533
+ if (card != null) {
534
+ collectCall!!.resolve(
535
+ JSObject()
536
+ .put("brand", card.brand)
537
+ .put("cardholderName", card.cardholderName)
538
+ .put("country", card.country)
539
+ .put("emvAuthData", card.emvAuthData)
540
+ .put("expMonth", card.expMonth)
541
+ .put("expYear", card.expYear)
542
+ .put("funding", card.funding)
543
+ .put("generatedCard", card.generatedCard)
544
+ .put(
545
+ "incrementalAuthorizationStatus",
546
+ card.incrementalAuthorizationStatus
547
+ )
548
+ .put("last4", card.last4)
549
+ .put("networks", card.networks)
550
+ .put("readMethod", card.readMethod)
551
+ )
552
+ } else {
553
+ collectCall!!.resolve()
554
+ }
555
+ }
556
+
557
+ override fun onFailure(e: TerminalException) {
558
+ notifyListeners(TerminalEnumEvent.Failed.webEventName, emptyObject)
559
+ var errorCode: String? = "generic_error"
560
+ if (e.apiError != null && e.apiError!!.code != null) {
561
+ errorCode = e.apiError!!.code
562
+ }
563
+ val returnObject = JSObject()
564
+ returnObject.put("message", e.localizedMessage)
565
+ if (e.apiError != null) {
566
+ returnObject.put("code", e.apiError!!.code)
567
+ returnObject.put("declineCode", e.apiError!!.declineCode)
568
+ }
569
+ collectCall!!.reject(e.localizedMessage, errorCode, returnObject)
570
+ }
571
+ }
572
+
573
+ fun confirmPaymentIntent(call: PluginCall) {
574
+ if (this.paymentIntentInstance == null) {
575
+ call.reject("PaymentIntent not found for confirmPaymentIntent. Use collect method first and try again.")
576
+ return
577
+ }
578
+
579
+ this.confirmPaymentIntentCall = call
580
+ Terminal.getInstance()
581
+ .confirmPaymentIntent(this.paymentIntentInstance!!, confirmPaymentMethodCallback)
582
+ }
583
+
584
+ fun installAvailableUpdate(call: PluginCall) {
585
+ Terminal.getInstance().installAvailableUpdate()
586
+ call.resolve(emptyObject)
587
+ }
588
+
589
+ fun cancelInstallUpdate(call: PluginCall) {
590
+ if (this.installUpdateCancelable == null || installUpdateCancelable!!.isCompleted) {
591
+ call.resolve()
592
+ return
593
+ }
594
+
595
+ installUpdateCancelable!!.cancel(
596
+ object : Callback {
597
+ override fun onSuccess() {
598
+ call.resolve()
599
+ }
600
+
601
+ override fun onFailure(e: TerminalException) {
602
+ call.reject(e.localizedMessage)
603
+ }
604
+ }
605
+ )
606
+ }
607
+
608
+ fun setReaderDisplay(call: PluginCall) {
609
+ val currency = call.getString("currency", null)
610
+ if (currency == null) {
611
+ call.reject("You must provide a currency value")
612
+ return
613
+ }
614
+
615
+ val tax: Int = call.getInt("tax", 0)!!
616
+ val total: Int = call.getInt("total", 0)!!
617
+ if (total == 0) {
618
+ call.reject("You must provide a total value")
619
+ return
620
+ }
621
+
622
+ val lineItems = call.getArray("lineItems")
623
+ val lineItemsList: List<JSONObject>
624
+ try {
625
+ lineItemsList = lineItems.toList()
626
+ } catch (e: JSONException) {
627
+ call.reject(e.localizedMessage)
628
+ return
629
+ }
630
+
631
+ val cartLineItems: MutableList<CartLineItem> = ArrayList()
632
+ for (item in lineItemsList) {
633
+ try {
634
+ val itemObj = JSObject.fromJSONObject(item)
635
+ cartLineItems.add(
636
+ CartLineItem.Builder(
637
+ Objects.requireNonNull(itemObj.getString("displayName")!!),
638
+ Objects.requireNonNull(itemObj.getInteger("quantity")!!),
639
+ Objects.requireNonNull(itemObj.getInteger("amount")!!).toLong()
640
+ ).build()
641
+ )
642
+ } catch (e: JSONException) {
643
+ call.reject(e.localizedMessage)
644
+ return
645
+ }
646
+ }
647
+
648
+ val cart: Cart = Cart.Builder(currency, tax.toLong(), total.toLong(), cartLineItems).build()
649
+
650
+ Terminal.getInstance()
651
+ .setReaderDisplay(
652
+ cart,
653
+ object : Callback {
654
+ override fun onSuccess() {
655
+ call.resolve()
656
+ }
657
+
658
+ override fun onFailure(e: TerminalException) {
659
+ call.reject(e.errorMessage)
660
+ }
661
+ }
662
+ )
663
+ }
664
+
665
+ fun clearReaderDisplay(call: PluginCall) {
666
+ Terminal.getInstance()
667
+ .clearReaderDisplay(
668
+ object : Callback {
669
+ override fun onSuccess() {
670
+ call.resolve()
671
+ }
672
+
673
+ override fun onFailure(e: TerminalException) {
674
+ call.reject(e.errorMessage)
675
+ }
676
+ }
677
+ )
678
+ }
679
+
680
+ fun rebootReader(call: PluginCall) {
681
+ Terminal.getInstance()
682
+ .rebootReader(
683
+ object : Callback {
684
+ override fun onSuccess() {
685
+ paymentIntentInstance = null
686
+ call.resolve(emptyObject)
687
+ }
688
+
689
+ override fun onFailure(e: TerminalException) {
690
+ call.reject(e.localizedMessage)
691
+ }
692
+ }
693
+ )
694
+ }
695
+
696
+ fun cancelReaderReconnection(call: PluginCall) {
697
+ if (cancelReaderConnectionCancellable == null || cancelReaderConnectionCancellable!!.isCompleted) {
698
+ call.resolve()
699
+ return
700
+ }
701
+ cancelReaderConnectionCancellable!!.cancel(
702
+ object : Callback {
703
+ override fun onSuccess() {
704
+ call.resolve()
705
+ }
706
+
707
+ override fun onFailure(e: TerminalException) {
708
+ call.reject(e.localizedMessage, e)
709
+ }
710
+ }
711
+ )
712
+ }
713
+
714
+ private val confirmPaymentMethodCallback: PaymentIntentCallback =
715
+ object : PaymentIntentCallback {
716
+ override fun onSuccess(paymentIntent: PaymentIntent) {
717
+ notifyListeners(TerminalEnumEvent.ConfirmedPaymentIntent.webEventName, emptyObject)
718
+ paymentIntentInstance = null
719
+ confirmPaymentIntentCall!!.resolve()
720
+ }
721
+
722
+ override fun onFailure(e: TerminalException) {
723
+ notifyListeners(TerminalEnumEvent.Failed.webEventName, emptyObject)
724
+ val returnObject = JSObject()
725
+ returnObject.put("message", e.localizedMessage)
726
+ if (e.apiError != null) {
727
+ returnObject.put("code", e.apiError!!.code)
728
+ returnObject.put("declineCode", e.apiError!!.declineCode)
729
+ }
730
+ confirmPaymentIntentCall!!.reject(
731
+ e.localizedMessage,
732
+ null as String?,
733
+ returnObject
734
+ )
735
+ }
736
+ }
737
+
738
+ init {
739
+ this.contextSupplier = contextSupplier
740
+ this.discoveredReadersList = ArrayList()
741
+ }
742
+
743
+ private fun readerCallback(call: PluginCall): ReaderCallback {
744
+ return object : ReaderCallback {
745
+ override fun onSuccess(reader: Reader) {
746
+ notifyListeners(TerminalEnumEvent.ConnectedReader.webEventName, emptyObject)
747
+ call.resolve()
748
+ }
749
+
750
+ override fun onFailure(e: TerminalException) {
751
+ e.printStackTrace()
752
+ call.reject(e.localizedMessage, e)
753
+ }
754
+ }
755
+ }
756
+
757
+ private fun readerListener(): MobileReaderListener {
758
+ return object : MobileReaderListener {
759
+ override fun onStartInstallingUpdate(
760
+ update: ReaderSoftwareUpdate,
761
+ cancelable: Cancelable?
762
+ ) {
763
+ // Show UI communicating that a required update has started installing
764
+ installUpdateCancelable = cancelable
765
+ notifyListeners(
766
+ TerminalEnumEvent.StartInstallingUpdate.webEventName,
767
+ JSObject().put("update", convertReaderSoftwareUpdate(update))
768
+ )
769
+ }
770
+
771
+ override fun onReportReaderSoftwareUpdateProgress(progress: Float) {
772
+ // Update the progress of the install
773
+ notifyListeners(
774
+ TerminalEnumEvent.ReaderSoftwareUpdateProgress.webEventName,
775
+ JSObject().put("progress", progress.toDouble())
776
+ )
777
+ }
778
+
779
+ override fun onFinishInstallingUpdate(
780
+ update: ReaderSoftwareUpdate?,
781
+ e: TerminalException?
782
+ ) {
783
+ val eventObject = JSObject()
784
+
785
+ if (e != null) {
786
+ // note: Since errorCode cannot be obtained in iOS, use errorMessage for unification.
787
+ eventObject.put("error", e.localizedMessage)
788
+ notifyListeners(
789
+ TerminalEnumEvent.FinishInstallingUpdate.webEventName,
790
+ eventObject
791
+ )
792
+ return
793
+ }
794
+
795
+ eventObject.put(
796
+ "update",
797
+ if (update == null) null else convertReaderSoftwareUpdate(update)
798
+ )
799
+ notifyListeners(TerminalEnumEvent.FinishInstallingUpdate.webEventName, eventObject)
800
+ }
801
+
802
+ override fun onBatteryLevelUpdate(
803
+ batteryLevel: Float,
804
+ batteryStatus: BatteryStatus,
805
+ isCharging: Boolean
806
+ ) {
807
+ notifyListeners(
808
+ TerminalEnumEvent.BatteryLevel.webEventName,
809
+ JSObject().put("level", batteryLevel.toDouble()).put("charging", isCharging)
810
+ .put("status", batteryStatus.toString())
811
+ )
812
+ }
813
+
814
+ override fun onReportLowBatteryWarning() {}
815
+
816
+ override fun onReportAvailableUpdate(update: ReaderSoftwareUpdate) {
817
+ // An update is available for the connected reader. Show this update in your application.
818
+ // This update can be installed using `Terminal.getInstance().installAvailableUpdate`.
819
+ notifyListeners(
820
+ TerminalEnumEvent.ReportAvailableUpdate.webEventName,
821
+ JSObject().put("update", convertReaderSoftwareUpdate(update))
822
+ )
823
+ }
824
+
825
+ override fun onReportReaderEvent(event: ReaderEvent) {
826
+ notifyListeners(
827
+ TerminalEnumEvent.ReaderEvent.webEventName,
828
+ JSObject().put("event", event.toString())
829
+ )
830
+ }
831
+
832
+ override fun onRequestReaderDisplayMessage(message: ReaderDisplayMessage) {
833
+ notifyListeners(
834
+ TerminalEnumEvent.RequestDisplayMessage.webEventName,
835
+ JSObject().put("messageType", message.name).put("message", message.toString())
836
+ )
837
+ }
838
+
839
+ override fun onRequestReaderInput(options: ReaderInputOptions) {
840
+ val optionsList: List<ReaderInputOptions.ReaderInputOption> = options.options
841
+ val jsOptions = JSArray()
842
+ for (optionType in optionsList) {
843
+ jsOptions.put(optionType.name)
844
+ }
845
+
846
+ notifyListeners(
847
+ TerminalEnumEvent.RequestReaderInput.webEventName,
848
+ JSObject().put("options", jsOptions).put("message", options.toString())
849
+ )
850
+ }
851
+
852
+ override fun onDisconnect(reason: DisconnectReason) {
853
+ notifyListeners(
854
+ TerminalEnumEvent.DisconnectedReader.webEventName,
855
+ JSObject().put("reason", reason.toString())
856
+ )
857
+ }
858
+ }
859
+ }
860
+
861
+ private fun convertReaderInterface(reader: Reader?): JSObject {
862
+ return JSObject()
863
+ .put("label", reader!!.label)
864
+ .put("serialNumber", reader.serialNumber)
865
+ .put("id", reader.id)
866
+ .put("locationId", if (reader.location != null) reader.location!!.id else null)
867
+ .put("deviceSoftwareVersion", reader.softwareVersion)
868
+ .put("simulated", reader.isSimulated)
869
+ .put("serialNumber", reader.serialNumber)
870
+ .put("ipAddress", reader.ipAddress)
871
+ .put("baseUrl", reader.baseUrl)
872
+ .put("bootloaderVersion", reader.bootloaderVersion)
873
+ .put("configVersion", reader.configVersion)
874
+ .put("emvKeyProfileId", reader.emvKeyProfileId)
875
+ .put("firmwareVersion", reader.firmwareVersion)
876
+ .put("hardwareVersion", reader.hardwareVersion)
877
+ .put("macKeyProfileId", reader.macKeyProfileId)
878
+ .put("pinKeyProfileId", reader.pinKeyProfileId)
879
+ .put("trackKeyProfileId", reader.trackKeyProfileId)
880
+ .put("settingsVersion", reader.settingsVersion)
881
+ .put("pinKeysetId", reader.pinKeysetId)
882
+ .put("deviceType", terminalMappers.mapFromDeviceType(reader.deviceType))
883
+ .put("status", terminalMappers.mapFromNetworkStatus(reader.networkStatus))
884
+ .put("locationStatus", terminalMappers.mapFromLocationStatus(reader.locationStatus))
885
+ .put(
886
+ "batteryLevel",
887
+ if (reader.batteryLevel != null) reader.batteryLevel!!.toDouble() else null
888
+ )
889
+ .put(
890
+ "availableUpdate",
891
+ terminalMappers.mapFromReaderSoftwareUpdate(reader.availableUpdate)
892
+ )
893
+ .put("location", terminalMappers.mapFromLocation(reader.location))
894
+ }
895
+
896
+ private fun convertReaderSoftwareUpdate(update: ReaderSoftwareUpdate): JSObject? {
897
+ return terminalMappers.mapFromReaderSoftwareUpdate(update)
898
+ }
899
+
900
+ private fun findReader(discoveredReadersList: List<Reader?>, serialNumber: String?): Reader? {
901
+ var foundReader: Reader? = null
902
+ foundReader = discoveredReadersList
903
+ .stream()
904
+ .filter { device: Reader? -> serialNumber != null && serialNumber == device!!.serialNumber }
905
+ .findFirst()
906
+ .orElse(null)
907
+
908
+ return foundReader
909
+ }
910
+ }