@tapayadot/accept-react-native 1.0.0 → 1.2.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.
@@ -1,18 +1,33 @@
1
1
  package com.tapaya.acceptsdkreactnative
2
2
 
3
+ import android.util.Base64
3
4
  import com.google.gson.Gson
4
- import com.tapaya.accept.AcceptSDK
5
+ import com.tapaya.accept.Accept
5
6
  import com.tapaya.accept.external.IdentityData
7
+ import com.tapaya.core.model.external.AcceptEnvironment
8
+ import com.tapaya.core.model.external.AcceptUiBehavior
6
9
  import com.tapaya.core.model.external.BusinessType
7
10
  import com.tapaya.core.model.external.Currency
8
- import com.tapaya.core.model.external.exception.AcceptSDKException
11
+ import com.tapaya.core.model.external.NfcIndicator
12
+ import com.tapaya.core.model.external.NfcPosition
13
+ import com.tapaya.core.model.external.NfcZone
14
+ import com.tapaya.core.model.external.PaymentMethod
15
+ import com.tapaya.core.model.external.RefundReason
16
+ import com.tapaya.core.model.external.exception.AcceptException
9
17
  import com.tapaya.core.model.external.intent.CardChargeIntent
18
+ import com.tapaya.core.model.external.intent.CardRefundIntent
10
19
  import com.tapaya.core.model.external.intent.CertisInstantPaymentIntent
11
20
  import com.tapaya.core.model.external.intent.SepaInstantPaymentIntent
12
21
  import com.tapaya.core.model.external.result.AbstractPaymentResult
13
22
  import com.tapaya.core.model.external.result.CardPaymentResult
14
23
  import com.tapaya.core.model.external.result.CertisInstantPaymentResult
15
24
  import com.tapaya.core.model.external.result.SepaInstantPaymentResult
25
+ import com.tapaya.core.ui.theme.AcceptColorTheme
26
+ import com.tapaya.core.ui.theme.AcceptImageSource
27
+ import com.tapaya.core.ui.theme.AcceptImageTheme
28
+ import com.tapaya.core.ui.theme.AcceptThemeConfiguration
29
+ import com.tapaya.core.ui.theme.AcceptThemeSource
30
+ import com.tapaya.core.ui.theme.GradientColors
16
31
  import expo.modules.interfaces.permissions.PermissionsStatus
17
32
  import expo.modules.kotlin.functions.Coroutine
18
33
  import expo.modules.kotlin.modules.Module
@@ -29,7 +44,7 @@ class CardPaymentIntentReactNative : Record {
29
44
  var paymentIntentId: String = ""
30
45
 
31
46
  @Field
32
- var amount: Int = 0
47
+ var amount: Long = 0L
33
48
 
34
49
  @Field
35
50
  var requestedCurrency: String = ""
@@ -64,7 +79,7 @@ class CardPaymentResultReactNative(
64
79
  @Field
65
80
  var companyCity: String = "",
66
81
  @Field
67
- var companyCountry: Int = 0,
82
+ var companyCountry: String = "",
68
83
  @Field
69
84
  var terminalId: String = "",
70
85
  @Field
@@ -74,6 +89,10 @@ class CardPaymentResultReactNative(
74
89
  @Field
75
90
  var reason: String = "",
76
91
  @Field
92
+ var receiptUrl: String? = null,
93
+ @Field
94
+ var refundToken: String? = null,
95
+ @Field
77
96
  var type: String = "",
78
97
  ) : Record
79
98
 
@@ -86,11 +105,13 @@ fun CardPaymentResult.toReactNative(): CardPaymentResultReactNative {
86
105
  companyAddress1 = companyAddress1 ?: "",
87
106
  companyAddress2 = companyAddress2 ?: "",
88
107
  companyCity = companyCity ?: "",
89
- companyCountry = companyCountry ?: 0,
108
+ companyCountry = companyCountry.toCountryAlpha2(),
90
109
  terminalId = terminalId ?: "",
91
110
  authorizationCode = authorizationCode ?: "",
92
111
  brand = brand?.name ?: "",
93
112
  reason = reason?.name ?: "",
113
+ receiptUrl = receiptUrl,
114
+ refundToken = refundToken,
94
115
  type = "card"
95
116
  )
96
117
  }
@@ -100,7 +121,7 @@ class SepaPaymentIntentReactNative : Record {
100
121
  var paymentIntentId: String = ""
101
122
 
102
123
  @Field
103
- var amount: Int = 0
124
+ var amount: Long = 0L
104
125
 
105
126
  @Field
106
127
  var requestedCurrency: String = ""
@@ -129,6 +150,8 @@ class SepaPaymentInstantResultReactNative(
129
150
  @Field
130
151
  var receivedFromIban: String = "",
131
152
  @Field
153
+ var receiptUrl: String? = null,
154
+ @Field
132
155
  var type: String = "",
133
156
  ) : Record
134
157
 
@@ -138,6 +161,7 @@ fun SepaInstantPaymentResult.toReactNative(): SepaPaymentInstantResultReactNativ
138
161
  postponed = postponed,
139
162
  receivedAt = receivedAt.toString(),
140
163
  receivedFromIban = receivedFromIban ?: "",
164
+ receiptUrl = receiptUrl,
141
165
  type = "sepa"
142
166
  )
143
167
  }
@@ -147,7 +171,7 @@ class CertisPaymentIntentReactNative : Record {
147
171
  var paymentIntentId: String = ""
148
172
 
149
173
  @Field
150
- var amount: Int = 0
174
+ var amount: Long = 0L
151
175
 
152
176
  @Field
153
177
  var requestedCurrency: String = ""
@@ -176,6 +200,8 @@ class CertisPaymentInstantResultReactNative(
176
200
  @Field
177
201
  var receivedFromIban: String = "",
178
202
  @Field
203
+ var receiptUrl: String? = null,
204
+ @Field
179
205
  var type: String = "",
180
206
  ) : Record
181
207
 
@@ -185,10 +211,202 @@ fun CertisInstantPaymentResult.toReactNative(): CertisPaymentInstantResultReactN
185
211
  postponed = postponed,
186
212
  receivedAt = receivedAt.toString(),
187
213
  receivedFromIban = receivedFromIban ?: "",
214
+ receiptUrl = receiptUrl,
188
215
  type = "certis"
189
216
  )
190
217
  }
191
218
 
219
+ class RefundIntentReactNative : Record {
220
+ @Field
221
+ var paymentToken: String = ""
222
+
223
+ @Field
224
+ var refundToken: String? = null
225
+
226
+ @Field
227
+ var reason: String = "customerRequest"
228
+ }
229
+
230
+ class RefundResultReactNative(
231
+ @Field
232
+ var refundToken: String? = null,
233
+ @Field
234
+ var status: String = "",
235
+ @Field
236
+ var refundReason: String = "",
237
+ ) : Record
238
+
239
+
240
+ class GradientColorsRecord : Record {
241
+ @Field var start: String = ""
242
+ @Field var end: String = ""
243
+ }
244
+
245
+ class AcceptColorThemeRecord : Record {
246
+ @Field var accent: String = ""
247
+ @Field var error: String = ""
248
+ @Field var warning: String = ""
249
+ @Field var success: String = ""
250
+ @Field var brandGradientColors: GradientColorsRecord? = null
251
+ @Field var brandSubtleGradientColors: GradientColorsRecord? = null
252
+
253
+ fun toAcceptColorTheme(): AcceptColorTheme = AcceptColorTheme(
254
+ accent = accent.toComposeColor(),
255
+ error = error.toComposeColor(),
256
+ warning = warning.toComposeColor(),
257
+ success = success.toComposeColor(),
258
+ brandGradientColors = brandGradientColors?.let { GradientColors(it.start.toComposeColor(), it.end.toComposeColor()) },
259
+ brandSubtleGradientColors = brandSubtleGradientColors?.let { GradientColors(it.start.toComposeColor(), it.end.toComposeColor()) },
260
+ )
261
+ }
262
+
263
+ class AcceptImageThemeRecord : Record {
264
+ @Field var brandLogo: String = ""
265
+ @Field var toolbarLogo: String = ""
266
+ @Field var solidLogo: String = ""
267
+
268
+ fun toAcceptImageTheme(): AcceptImageTheme = try {
269
+ AcceptImageTheme(
270
+ brandLogo = AcceptImageSource.Data(Base64.decode(brandLogo, Base64.DEFAULT)),
271
+ toolbarLogo = AcceptImageSource.Data(Base64.decode(toolbarLogo, Base64.DEFAULT)),
272
+ solidLogo = AcceptImageSource.Data(Base64.decode(solidLogo, Base64.DEFAULT)),
273
+ )
274
+ } catch (e: IllegalArgumentException) {
275
+ throw Exception("ThemeError: invalid base64 image data — ${e.message}")
276
+ }
277
+ }
278
+
279
+ class AcceptThemeConfigurationRecord : Record {
280
+ @Field var colors: AcceptColorThemeRecord? = null
281
+ @Field var images: AcceptImageThemeRecord? = null
282
+
283
+ fun toAcceptThemeConfiguration(): AcceptThemeConfiguration = AcceptThemeConfiguration(
284
+ colors = colors?.let { AcceptThemeSource.Local(it.toAcceptColorTheme()) } ?: AcceptThemeSource.Default,
285
+ images = images?.let { AcceptThemeSource.Local(it.toAcceptImageTheme()) } ?: AcceptThemeSource.Default,
286
+ )
287
+ }
288
+
289
+ private fun String.toComposeColor(): androidx.compose.ui.graphics.Color = try {
290
+ androidx.compose.ui.graphics.Color(android.graphics.Color.parseColor(this))
291
+ } catch (e: IllegalArgumentException) {
292
+ throw Exception("ThemeError: invalid color '$this'. Expected #RRGGBB or #AARRGGBB — ${e.message}")
293
+ }
294
+
295
+ private fun Int?.toCountryAlpha2(): String = when (this) {
296
+ 8 -> "AL"
297
+ 20 -> "AD"
298
+ 40 -> "AT"
299
+ 51 -> "AM"
300
+ 56 -> "BE"
301
+ 70 -> "BA"
302
+ 100 -> "BG"
303
+ 112 -> "BY"
304
+ 191 -> "HR"
305
+ 196 -> "CY"
306
+ 203 -> "CZ"
307
+ 208 -> "DK"
308
+ 233 -> "EE"
309
+ 234 -> "FO"
310
+ 246 -> "FI"
311
+ 250 -> "FR"
312
+ 268 -> "GE"
313
+ 276 -> "DE"
314
+ 292 -> "GI"
315
+ 300 -> "GR"
316
+ 304 -> "GL"
317
+ 336 -> "VA"
318
+ 348 -> "HU"
319
+ 352 -> "IS"
320
+ 372 -> "IE"
321
+ 380 -> "IT"
322
+ 398 -> "KZ"
323
+ 417 -> "KG"
324
+ 428 -> "LV"
325
+ 438 -> "LI"
326
+ 440 -> "LT"
327
+ 442 -> "LU"
328
+ 470 -> "MT"
329
+ 492 -> "MC"
330
+ 498 -> "MD"
331
+ 499 -> "ME"
332
+ 528 -> "NL"
333
+ 578 -> "NO"
334
+ 616 -> "PL"
335
+ 620 -> "PT"
336
+ 642 -> "RO"
337
+ 643 -> "RU"
338
+ 674 -> "SM"
339
+ 688 -> "RS"
340
+ 703 -> "SK"
341
+ 705 -> "SI"
342
+ 724 -> "ES"
343
+ 752 -> "SE"
344
+ 756 -> "CH"
345
+ 792 -> "TR"
346
+ 804 -> "UA"
347
+ 807 -> "MK"
348
+ 826 -> "GB"
349
+ else -> ""
350
+ }
351
+
352
+ private fun String.toPaymentMethod(): PaymentMethod = when (this) {
353
+ "CARD" -> PaymentMethod.CARD
354
+ "SEPA" -> PaymentMethod.SEPA_INST
355
+ "CERTIS" -> PaymentMethod.CERTIS
356
+ "CRYPTO" -> PaymentMethod.CRYPTO
357
+ else -> throw IllegalArgumentException("Unknown payment method: $this")
358
+ }
359
+
360
+ private fun PaymentMethod.toJsString(): String = when (this) {
361
+ PaymentMethod.CARD -> "CARD"
362
+ PaymentMethod.SEPA_INST -> "SEPA"
363
+ PaymentMethod.CERTIS -> "CERTIS"
364
+ PaymentMethod.CRYPTO -> "CRYPTO"
365
+ }
366
+
367
+ private fun String.toRefundReason(): RefundReason = when (this) {
368
+ "customerRequest" -> RefundReason.CUSTOMER_REQUEST
369
+ "duplicate" -> RefundReason.DUPLICATE
370
+ "fraudulent" -> RefundReason.FRAUDULENT
371
+ "expiredCharge" -> RefundReason.EXPIRED_CHARGE
372
+ else -> throw IllegalArgumentException("Unknown refund reason: $this")
373
+ }
374
+
375
+ class NfcPositionRecord : Record {
376
+ @Field var xBias: Float = 0f
377
+ @Field var yBias: Float = 0f
378
+ }
379
+
380
+ class NfcZoneRecord : Record {
381
+ @Field var indicator: String = "DEFAULT"
382
+ @Field var position: NfcPositionRecord? = null
383
+
384
+ fun toNfcZone(): NfcZone {
385
+ val pos = position?.let { NfcPosition.Custom(it.xBias, it.yBias) } ?: NfcPosition.Default
386
+ return NfcZone(indicator = indicator.toNfcIndicator(), position = pos)
387
+ }
388
+ }
389
+
390
+ class AcceptUiBehaviorRecord : Record {
391
+ @Field var resultScreenDurationSeconds: Int = 10
392
+ @Field var showResultScreen: Boolean = true
393
+ @Field var nfcZone: NfcZoneRecord? = null
394
+
395
+ fun toAcceptUiBehavior(): AcceptUiBehavior = AcceptUiBehavior(
396
+ resultScreenDurationSeconds = resultScreenDurationSeconds,
397
+ showResultScreen = showResultScreen,
398
+ nfcZone = nfcZone?.toNfcZone(),
399
+ )
400
+ }
401
+
402
+ private fun String.toNfcIndicator(): NfcIndicator = when (this) {
403
+ "DEFAULT" -> NfcIndicator.DEFAULT
404
+ "ABOVE" -> NfcIndicator.ABOVE
405
+ "BELOW" -> NfcIndicator.BELOW
406
+ "FRONT" -> NfcIndicator.FRONT
407
+ "BEHIND" -> NfcIndicator.BEHIND
408
+ else -> throw IllegalArgumentException("Unknown NFC indicator: $this")
409
+ }
192
410
 
193
411
  class KybPrefillDataRecord : Record {
194
412
  @Field var businessType: String? = null
@@ -241,15 +459,24 @@ class AcceptSdkReactNativeModule : Module() {
241
459
  "onCertisPaymentException",
242
460
  )
243
461
 
244
- AsyncFunction("initialize") Coroutine { demo: Boolean ->
462
+ AsyncFunction("initialize") Coroutine { demo: Boolean, theme: AcceptThemeConfigurationRecord? ->
245
463
  val context = appContext.reactContext ?: throw Exception("Context not available")
246
- AcceptSDK.initialize(context, demo)
464
+ val environment = if (demo) AcceptEnvironment.SANDBOX else AcceptEnvironment.PRODUCTION
465
+ Accept.initialize(context, environment, theme?.toAcceptThemeConfiguration() ?: AcceptThemeConfiguration.Default)
466
+ }
467
+
468
+ AsyncFunction("setTheme") Coroutine { theme: AcceptThemeConfigurationRecord ->
469
+ Accept.setThemeConfiguration(theme.toAcceptThemeConfiguration())
470
+ }
471
+
472
+ AsyncFunction("setUiBehavior") { behavior: AcceptUiBehaviorRecord ->
473
+ Accept.setUiBehavior(behavior.toAcceptUiBehavior())
247
474
  }
248
475
 
249
476
  AsyncFunction("authenticate") Coroutine { token: String ->
250
477
  return@Coroutine suspendCancellableCoroutine<Boolean> { continuation ->
251
478
  try {
252
- AcceptSDK.authenticate(
479
+ Accept.authenticate(
253
480
  token,
254
481
  onSuccess = { continuation.resume(true) },
255
482
  onError = { error -> (error as? Throwable)?.let { continuation.resumeWithException(it) } }
@@ -262,7 +489,7 @@ class AcceptSdkReactNativeModule : Module() {
262
489
 
263
490
  Function("getStatus") { ->
264
491
  try {
265
- return@Function AcceptSDK.status.name
492
+ return@Function Accept.status.name
266
493
  } catch (e: Exception) {
267
494
  throw e
268
495
  }
@@ -271,7 +498,7 @@ class AcceptSdkReactNativeModule : Module() {
271
498
  AsyncFunction("getPaymentStatus") Coroutine { paymentIntentId: String ->
272
499
  return@Coroutine suspendCancellableCoroutine<String> { continuation ->
273
500
  try {
274
- AcceptSDK.getPaymentStatus(
501
+ Accept.getPaymentStatus(
275
502
  paymentIntentId,
276
503
  { result: AbstractPaymentResult ->
277
504
  val mapped = when (result) {
@@ -283,7 +510,7 @@ class AcceptSdkReactNativeModule : Module() {
283
510
 
284
511
  continuation.resume(Gson().toJson(mapped))
285
512
  },
286
- { error: AcceptSDKException ->
513
+ { error: AcceptException ->
287
514
  (error as? Throwable)?.let { continuation.resumeWithException(it) }
288
515
  }
289
516
  )
@@ -311,7 +538,7 @@ class AcceptSdkReactNativeModule : Module() {
311
538
  AsyncFunction("logOut") Coroutine { ->
312
539
  return@Coroutine suspendCancellableCoroutine<Boolean> { continuation ->
313
540
  try {
314
- AcceptSDK.logOut {
541
+ Accept.logOut {
315
542
  continuation.resume(true)
316
543
  }
317
544
  } catch (e: Exception) {
@@ -322,20 +549,38 @@ class AcceptSdkReactNativeModule : Module() {
322
549
 
323
550
  AsyncFunction("isPaymentPending") { ->
324
551
  try {
325
- return@AsyncFunction AcceptSDK.isPaymentPending()
552
+ return@AsyncFunction Accept.isPaymentPending()
326
553
  } catch (e: Exception) {
327
554
  throw e
328
555
  }
329
556
  }
330
557
 
331
558
  AsyncFunction("presentKyb") Coroutine { prefilling: KybPrefillDataRecord? ->
332
- AcceptSDK.identity.presentKyb(prefilling?.toIdentityData())
559
+ Accept.identity.presentKyb(prefilling?.toIdentityData())
560
+ }
561
+
562
+ AsyncFunction("getOnboardingStatus") Coroutine { ->
563
+ val status = Accept.identity.getOnboardingStatus()
564
+ return@Coroutine mapOf(
565
+ "isOnboarded" to status.isOnboarded,
566
+ "paymentMethods" to status.paymentMethods.map { pm ->
567
+ mapOf(
568
+ "method" to pm.method.toJsString(),
569
+ "status" to pm.status.name,
570
+ )
571
+ },
572
+ )
573
+ }
574
+
575
+ AsyncFunction("getPaymentMethodStatus") Coroutine { method: String ->
576
+ val status = Accept.identity.getPaymentMethodStatus(method.toPaymentMethod())
577
+ return@Coroutine status.name
333
578
  }
334
579
 
335
580
  AsyncFunction("startCardPayment") Coroutine { paymentIntent: CardPaymentIntentReactNative ->
336
581
  return@Coroutine suspendCancellableCoroutine<CardPaymentResultReactNative> { continuation ->
337
582
  try {
338
- AcceptSDK.cardTerminal.process(
583
+ Accept.cardTerminal.process(
339
584
  paymentIntent.toCardInstantPaymentIntent(),
340
585
  onResult = {
341
586
  continuation.resume(it.toReactNative())
@@ -356,7 +601,7 @@ class AcceptSdkReactNativeModule : Module() {
356
601
  AsyncFunction("startSepaPayment") Coroutine { paymentIntent: SepaPaymentIntentReactNative ->
357
602
  return@Coroutine suspendCancellableCoroutine<SepaPaymentInstantResultReactNative> { continuation ->
358
603
  try {
359
- AcceptSDK.sepaTerminal.process(
604
+ Accept.sepaTerminal.process(
360
605
  paymentIntent.toSepaInstantPaymentIntent(),
361
606
  onResult = {
362
607
  continuation.resume(it.toReactNative())
@@ -377,7 +622,7 @@ class AcceptSdkReactNativeModule : Module() {
377
622
  AsyncFunction("startCertisPayment") Coroutine { paymentIntent: CertisPaymentIntentReactNative ->
378
623
  return@Coroutine suspendCancellableCoroutine<CertisPaymentInstantResultReactNative> { continuation ->
379
624
  try {
380
- AcceptSDK.certisTerminal.process(
625
+ Accept.certisTerminal.process(
381
626
  paymentIntent.toCertisInstantPaymentIntent(),
382
627
  onResult = {
383
628
  continuation.resume(it.toReactNative())
@@ -395,6 +640,57 @@ class AcceptSdkReactNativeModule : Module() {
395
640
  }
396
641
  }
397
642
 
643
+ AsyncFunction("refund") Coroutine { intent: RefundIntentReactNative ->
644
+ return@Coroutine suspendCancellableCoroutine<RefundResultReactNative> { continuation ->
645
+ try {
646
+ val refundIntent = CardRefundIntent(
647
+ sourcePaymentToken = intent.paymentToken,
648
+ refundReason = intent.reason.toRefundReason(),
649
+ refundToken = intent.refundToken,
650
+ )
651
+ Accept.cardTerminal.process(
652
+ refundIntent,
653
+ onResult = { result ->
654
+ continuation.resume(
655
+ RefundResultReactNative(
656
+ refundToken = result.refundToken,
657
+ status = result.status.name.lowercase(),
658
+ refundReason = intent.reason,
659
+ )
660
+ )
661
+ },
662
+ onStatus = { /* refund flow emits no UI status */ },
663
+ onError = { error ->
664
+ continuation.resumeWithException(error as Throwable)
665
+ }
666
+ )
667
+ } catch (e: Exception) {
668
+ continuation.resumeWithException(e)
669
+ }
670
+ }
671
+ }
672
+
673
+ AsyncFunction("cancelActivePayment") { ->
674
+ if (!Accept.isPaymentPending()) {
675
+ throw Exception("NoActivePayment")
676
+ }
677
+ Accept.cardTerminal.reset()
678
+ Accept.sepaTerminal.reset()
679
+ Accept.certisTerminal.reset()
680
+ }
681
+
682
+ AsyncFunction("enableDemoMode") { ->
683
+ Accept.enableDemoMode()
684
+ }
685
+
686
+ AsyncFunction("disableDemoMode") { ->
687
+ Accept.disableDemoMode()
688
+ }
689
+
690
+ AsyncFunction("isDemoMode") { ->
691
+ Accept.isDemoMode()
692
+ }
693
+
398
694
  AsyncFunction("pairWithCompanionApp") Coroutine { ->
399
695
  // Not available on Android
400
696
  }
@@ -409,7 +705,7 @@ class AcceptSdkReactNativeModule : Module() {
409
705
 
410
706
  }
411
707
 
412
- private fun AcceptSDKException.toReactNative(): Map<String, String> {
708
+ private fun AcceptException.toReactNative(): Map<String, String> {
413
709
  return mapOf(
414
710
  "message" to ((this as Throwable).message ?: ""),
415
711
  "exception" to ((this as Throwable).cause?.stackTraceToString() ?: "")