judokit-react-native 3.4.9 → 4.0.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.
@@ -5,6 +5,11 @@ import android.os.Bundle
5
5
  import com.facebook.react.bridge.Arguments
6
6
  import com.facebook.react.bridge.ReadableMap
7
7
  import com.facebook.react.bridge.WritableMap
8
+ import com.judopay.judo3ds2.customization.ButtonCustomization
9
+ import com.judopay.judo3ds2.customization.LabelCustomization
10
+ import com.judopay.judo3ds2.customization.TextBoxCustomization
11
+ import com.judopay.judo3ds2.customization.ToolbarCustomization
12
+ import com.judopay.judo3ds2.customization.UiCustomization
8
13
  import com.judopay.judokit.android.Judo
9
14
  import com.judopay.judokit.android.api.model.Authorization
10
15
  import com.judopay.judokit.android.api.model.BasicAuthorization
@@ -16,6 +21,41 @@ import com.judopay.judokit.android.model.googlepay.GooglePayBillingAddressParame
16
21
  import com.judopay.judokit.android.model.googlepay.GooglePayEnvironment
17
22
  import com.judopay.judokit.android.model.googlepay.GooglePayShippingAddressParameters
18
23
 
24
+ // For consistency with:
25
+ // https://github.com/Judopay/JudoKit-iOS/blob/master/Source/Models/Response/JPResponse.m#L36
26
+ private const val TRANSACTION_TYPE_PAYMENT = "payment"
27
+ private const val TRANSACTION_TYPE_PRE_AUTH = "preauth"
28
+ private const val TRANSACTION_TYPE_REGISTER = "register"
29
+ private const val TRANSACTION_TYPE_REGISTER_CARD = "registercard"
30
+ private const val TRANSACTION_TYPE_SAVE_CARD = "save"
31
+ private const val TRANSACTION_TYPE_CHECK_CARD = "checkcard"
32
+
33
+ // For consistency with:
34
+ // https://github.com/Judopay/JudoKit-iOS/blob/master/Source/Models/Transaction/JPTransactionType.h#L30
35
+ private enum class TransactionType(val value: Int, val typeAsStrings: List<String>? = null) {
36
+ PAYMENT(1, listOf(TRANSACTION_TYPE_PAYMENT)),
37
+ PRE_AUTH(2, listOf(TRANSACTION_TYPE_PRE_AUTH)),
38
+ REGISTER_CARD(3, listOf(TRANSACTION_TYPE_REGISTER, TRANSACTION_TYPE_REGISTER_CARD)),
39
+ CHECK_CARD(4, listOf(TRANSACTION_TYPE_CHECK_CARD)),
40
+ SAVE_CARD(5, listOf(TRANSACTION_TYPE_SAVE_CARD)),
41
+ UNKNOWN(-1)
42
+ }
43
+
44
+ // For consistency with:
45
+ // https://github.com/Judopay/JudoKit-iOS/blob/master/Source/Models/Response/JPResponse.m#L32
46
+ private const val STATUS_DECLINED = "declined"
47
+ private const val STATUS_SUCCESS = "success"
48
+ private const val STATUS_ERROR = "error"
49
+
50
+ // For consistency with:
51
+ // https://github.com/Judopay/JudoKit-iOS/blob/abd34bbfe4784fb5f074ed30f93d6743ba295622/Source/Models/Transaction/JPTransactionResult.h#L27
52
+ private enum class TransactionResult(val value: Int, val status: String? = null) {
53
+ ERROR(0, STATUS_ERROR),
54
+ SUCCESS(1, STATUS_SUCCESS),
55
+ DECLINED(2, STATUS_DECLINED),
56
+ UNKNOWN(-1)
57
+ }
58
+
19
59
  internal fun getTransactionConfiguration(options: ReadableMap): Judo {
20
60
  val widgetType = getTransactionTypeWidget(options)
21
61
  return getJudoConfiguration(widgetType, options)
@@ -45,20 +85,19 @@ internal fun getPaymentMethodsConfiguration(options: ReadableMap): Judo {
45
85
  }
46
86
 
47
87
  internal fun getMappedType(type: String?): Int {
48
- return when (type) {
49
- "PreAuth" -> 1
50
- "RegisterCard" -> 2
51
- "CheckCard" -> 3
52
- "Save" -> 4
53
- else -> 0
54
- }
88
+ val typeInLowercase = type?.lowercase()
89
+ val typeValue = TransactionType.values().firstOrNull { it.typeAsStrings?.contains(typeInLowercase) ?: false }
90
+
91
+ return typeValue?.value ?: TransactionType.UNKNOWN.value
55
92
  }
56
93
 
94
+ // consistent with:
95
+ // https://github.com/Judopay/JudoKit-iOS/blob/master/Source/Models/Response/JPResponse.m#L125
57
96
  internal fun getMappedResult(result: String?): Int {
58
- return when (result) {
59
- "Declined" -> 1
60
- else -> 0
61
- }
97
+ val resultInLowercase = result?.lowercase()
98
+ val resultValue = TransactionResult.values().firstOrNull { it.status == resultInLowercase }
99
+
100
+ return resultValue?.value ?: TransactionResult.UNKNOWN.value
62
101
  }
63
102
 
64
103
  internal fun getMappedResult(result: JudoResult?): WritableMap {
@@ -87,6 +126,7 @@ internal fun getMappedResult(result: JudoResult?): WritableMap {
87
126
  cardDetailsMap.putString("cardCountry", result?.cardDetails?.country)
88
127
  cardDetailsMap.putString("cardFunding", result?.cardDetails?.funding)
89
128
  cardDetailsMap.putString("cardScheme", result?.cardDetails?.scheme)
129
+ cardDetailsMap.putString("cardHolderName", result?.cardDetails?.cardHolderName)
90
130
 
91
131
  map.putMap("cardDetails", cardDetailsMap)
92
132
 
@@ -99,6 +139,27 @@ internal fun getMappedResult(result: JudoResult?): WritableMap {
99
139
  return map
100
140
  }
101
141
 
142
+ internal fun getJudoConfigurationForApiService(options: ReadableMap): Judo {
143
+ val authorization = getAuthorization(options)
144
+
145
+ val amount = Amount.Builder()
146
+ .setAmount("0.00")
147
+ .setCurrency(Currency.GBP)
148
+ .build()
149
+
150
+ val reference = Reference.Builder()
151
+ .setConsumerReference("reference")
152
+ .build()
153
+
154
+ return Judo.Builder(PaymentWidgetType.CARD_PAYMENT)
155
+ .setIsSandboxed(options.isSandboxed)
156
+ .setJudoId("000000")
157
+ .setAuthorization(authorization)
158
+ .setAmount(amount)
159
+ .setReference(reference)
160
+ .build()
161
+ }
162
+
102
163
  internal fun getJudoConfiguration(type: PaymentWidgetType, options: ReadableMap): Judo {
103
164
  val authorization = getAuthorization(options)
104
165
  val amount = getAmount(options)
@@ -127,6 +188,7 @@ internal fun getJudoConfiguration(type: PaymentWidgetType, options: ReadableMap)
127
188
  .setGooglePayConfiguration(googlePayConfiguration)
128
189
  .setPBBAConfiguration(pbbaConfiguration)
129
190
  .setInitialRecurringPayment(options.isInitialRecurringPayment)
191
+ .setDelayedAuthorisation(options.isDelayedAuthorisation)
130
192
  .setNetworkTimeout(timeouts)
131
193
  .setChallengeRequestIndicator(challengeRequestIndicator)
132
194
  .setScaExemption(scaExemption)
@@ -191,11 +253,12 @@ internal fun getAuthorization(options: ReadableMap): Authorization {
191
253
  }
192
254
 
193
255
  internal fun getTransactionTypeWidget(options: ReadableMap) = when (options.getInt("transactionType")) {
194
- 1 -> PaymentWidgetType.PRE_AUTH
195
- 2 -> PaymentWidgetType.REGISTER_CARD
196
- 3 -> PaymentWidgetType.CHECK_CARD
197
- 4 -> PaymentWidgetType.CREATE_CARD_TOKEN
198
- else -> PaymentWidgetType.CARD_PAYMENT
256
+ 1 -> PaymentWidgetType.CARD_PAYMENT
257
+ 2 -> PaymentWidgetType.PRE_AUTH
258
+ 3 -> PaymentWidgetType.REGISTER_CARD
259
+ 4 -> PaymentWidgetType.CHECK_CARD
260
+ 5 -> PaymentWidgetType.CREATE_CARD_TOKEN
261
+ else -> throw IllegalArgumentException("Unknown transaction type")
199
262
  }
200
263
 
201
264
  internal fun getTransactionModeWidget(options: ReadableMap) = when (options.getInt("transactionMode")) {
@@ -331,12 +394,110 @@ internal fun getUIConfiguration(options: ReadableMap): UiConfiguration? {
331
394
  .setShouldPaymentButtonDisplayAmount(options.shouldPaymentButtonDisplayAmount)
332
395
  .setShouldPaymentMethodsVerifySecurityCode(options.shouldPaymentMethodsVerifySecurityCode)
333
396
  .setShouldAskForBillingInformation(options.shouldAskForBillingInformation)
397
+ .setThreeDSUiCustomization(getThreeDSUiCustomization(options))
334
398
  .build()
335
399
  } else {
336
400
  null
337
401
  }
338
402
  }
339
403
 
404
+ internal fun getThreeDSUiCustomization(options: ReadableMap): UiCustomization? {
405
+ return if (options.threeDSUIConfiguration != null) {
406
+
407
+ val uiCustomization = UiCustomization()
408
+
409
+ options.threeDSUIToolbarCustomization?.let {
410
+ uiCustomization.setToolbarCustomization(ToolbarCustomization().apply {
411
+ setTextFontName(it.textFontName)
412
+ setTextColor(it.textColor)
413
+ setTextFontSize(it.textFontSize)
414
+ setBackgroundColor(it.backgroundColor)
415
+ setHeaderText(it.headerText)
416
+ setButtonText(it.buttonText)
417
+ })
418
+ }
419
+
420
+ options.threeDSUILabelCustomization?.let {
421
+ uiCustomization.setLabelCustomization(LabelCustomization().apply {
422
+ setTextFontName(it.textFontName)
423
+ setTextColor(it.textColor)
424
+ setTextFontSize(it.textFontSize)
425
+ setHeadingTextFontName(it.headingTextFontName)
426
+ setHeadingTextColor(it.headingTextColor)
427
+ setHeadingTextFontSize(it.headingTextFontSize)
428
+ })
429
+ }
430
+
431
+ options.threeDSUITextBoxCustomization?.let {
432
+ uiCustomization.setTextBoxCustomization(TextBoxCustomization().apply {
433
+ setTextFontName(it.textFontName)
434
+ setTextColor(it.textColor)
435
+ setTextFontSize(it.textFontSize)
436
+ setBorderWidth(it.borderWidth)
437
+ setBorderColor(it.borderColor)
438
+ setCornerRadius(it.cornerRadius)
439
+ })
440
+ }
441
+
442
+ if (options.threeDSUIButtonCustomizations != null) {
443
+
444
+ options.threeDSUISubmitButtonCustomization?.let {
445
+ uiCustomization.setButtonCustomization(ButtonCustomization().apply {
446
+ setTextFontName(it.textFontName)
447
+ setTextColor(it.textColor)
448
+ setTextFontSize(it.textFontSize)
449
+ setBackgroundColor(it.backgroundColor)
450
+ setCornerRadius(it.cornerRadius)
451
+ }, UiCustomization.ButtonType.SUBMIT)
452
+ }
453
+
454
+ options.threeDSUINextButtonCustomization?.let {
455
+ uiCustomization.setButtonCustomization(ButtonCustomization().apply {
456
+ setTextFontName(it.textFontName)
457
+ setTextColor(it.textColor)
458
+ setTextFontSize(it.textFontSize)
459
+ setBackgroundColor(it.backgroundColor)
460
+ setCornerRadius(it.cornerRadius)
461
+ }, UiCustomization.ButtonType.NEXT)
462
+ }
463
+
464
+ options.threeDSUIContinueButtonCustomization?.let {
465
+ uiCustomization.setButtonCustomization(ButtonCustomization().apply {
466
+ setTextFontName(it.textFontName)
467
+ setTextColor(it.textColor)
468
+ setTextFontSize(it.textFontSize)
469
+ setBackgroundColor(it.backgroundColor)
470
+ setCornerRadius(it.cornerRadius)
471
+ }, UiCustomization.ButtonType.CONTINUE)
472
+ }
473
+
474
+ options.threeDSUICancelButtonCustomization?.let {
475
+ uiCustomization.setButtonCustomization(ButtonCustomization().apply {
476
+ setTextFontName(it.textFontName)
477
+ setTextColor(it.textColor)
478
+ setTextFontSize(it.textFontSize)
479
+ setBackgroundColor(it.backgroundColor)
480
+ setCornerRadius(it.cornerRadius)
481
+ }, UiCustomization.ButtonType.CANCEL)
482
+ }
483
+
484
+ options.threeDSUIResendButtonCustomization?.let {
485
+ uiCustomization.setButtonCustomization(ButtonCustomization().apply {
486
+ setTextFontName(it.textFontName)
487
+ setTextColor(it.textColor)
488
+ setTextFontSize(it.textFontSize)
489
+ setBackgroundColor(it.backgroundColor)
490
+ setCornerRadius(it.cornerRadius)
491
+ }, UiCustomization.ButtonType.RESEND)
492
+ }
493
+ }
494
+
495
+ uiCustomization
496
+ } else {
497
+ null
498
+ }
499
+ }
500
+
340
501
  internal fun getPrimaryAccountDetails(options: ReadableMap): PrimaryAccountDetails? {
341
502
  return if (options.primaryAccountDetails != null) {
342
503
  PrimaryAccountDetails.Builder()
@@ -9,6 +9,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager
9
9
  import com.facebook.react.bridge.*
10
10
  import com.judopay.judokit.android.Judo
11
11
  import com.judopay.judokit.android.api.JudoApiService
12
+ import com.judopay.judokit.android.api.error.toJudoError
12
13
  import com.judopay.judokit.android.api.factory.JudoApiServiceFactory
13
14
  import com.judopay.judokit.android.api.model.response.JudoApiCallResult
14
15
  import com.judopay.judokit.android.api.model.response.Receipt
@@ -31,6 +32,7 @@ import retrofit2.Response
31
32
 
32
33
  const val JUDO_PAYMENT_WIDGET_REQUEST_CODE = 65520
33
34
  const val JUDO_PROMISE_REJECTION_CODE = "JUDO_ERROR"
35
+ const val REQUEST_FAILED_MESSAGE = "The request was unsuccessful."
34
36
 
35
37
  class JudoReactNativeModule internal constructor(val context: ReactApplicationContext) :
36
38
  ReactContextBaseJavaModule(context), CardTransactionManagerResultListener {
@@ -114,7 +116,6 @@ class JudoReactNativeModule internal constructor(val context: ReactApplicationCo
114
116
  val manager = CardTransactionManager.getInstance(activity)
115
117
 
116
118
  val judo = getTokenTransactionConfiguration(options)
117
-
118
119
  val cardToken = options.cardToken
119
120
 
120
121
  if (cardToken == null) {
@@ -122,6 +123,8 @@ class JudoReactNativeModule internal constructor(val context: ReactApplicationCo
122
123
  return
123
124
  }
124
125
 
126
+ manager.configureWith(judo)
127
+
125
128
  val details = TransactionDetails.Builder()
126
129
  .setCardHolderName(options.cardholderName)
127
130
  .setSecurityNumber(options.securityCode)
@@ -150,6 +153,56 @@ class JudoReactNativeModule internal constructor(val context: ReactApplicationCo
150
153
  }
151
154
  }
152
155
 
156
+ @ReactMethod
157
+ fun fetchTransactionDetails(options: ReadableMap, promise: Promise) {
158
+ try {
159
+ val judo = getJudoConfigurationForApiService(options)
160
+ val receiptId = options.receiptId ?: ""
161
+
162
+ val service = JudoApiServiceFactory.createApiService(context, judo)
163
+
164
+ val fetchTransactionDetailsCallback = object : Callback<JudoApiCallResult<Receipt>> {
165
+ override fun onResponse(
166
+ call: Call<JudoApiCallResult<Receipt>>,
167
+ response: Response<JudoApiCallResult<Receipt>>
168
+ ) {
169
+ when (val result = response.body()) {
170
+ is JudoApiCallResult.Success -> {
171
+ val receipt = result.data
172
+ if (receipt != null) {
173
+ val judoResult = receipt.toJudoResult()
174
+ promise.resolve(getMappedResult(judoResult))
175
+ } else {
176
+ promise.reject(JUDO_PROMISE_REJECTION_CODE, REQUEST_FAILED_MESSAGE)
177
+ }
178
+ }
179
+ is JudoApiCallResult.Failure -> {
180
+ val message = result.error?.message ?: REQUEST_FAILED_MESSAGE
181
+ promise.reject(JUDO_PROMISE_REJECTION_CODE, message)
182
+ }
183
+ else -> {
184
+ promise.reject(JUDO_PROMISE_REJECTION_CODE, REQUEST_FAILED_MESSAGE)
185
+ }
186
+ }
187
+ }
188
+
189
+ override fun onFailure(
190
+ call: Call<JudoApiCallResult<Receipt>>,
191
+ throwable: Throwable
192
+ ) {
193
+ promise.reject(JUDO_PROMISE_REJECTION_CODE, throwable.localizedMessage, throwable)
194
+ }
195
+ }
196
+
197
+ service
198
+ .fetchTransactionWithReceiptId(receiptId)
199
+ .enqueue(fetchTransactionDetailsCallback)
200
+
201
+ } catch (error: Exception) {
202
+ promise.reject(JUDO_PROMISE_REJECTION_CODE, error.localizedMessage, error)
203
+ }
204
+ }
205
+
153
206
  private fun ensureIsSubscribedToCardTransactionResults() {
154
207
  if (isSubscribedToCardTransactionResults) {
155
208
  return
package/ios/.xcode.env ADDED
@@ -0,0 +1,11 @@
1
+ # This `.xcode.env` file is versioned and is used to source the environment
2
+ # used when running script phases inside Xcode.
3
+ # To customize your local environment, you can create an `.xcode.env.local`
4
+ # file that is not versioned.
5
+
6
+ # NODE_BINARY variable contains the PATH to the node executable.
7
+ #
8
+ # Customize the NODE_BINARY variable here.
9
+ # For example, to use nvm with brew, add the following line
10
+ # . "$(brew --prefix nvm)/nvm.sh" --no-use
11
+ export NODE_BINARY=$(command -v node)
@@ -128,13 +128,19 @@ RCT_REMAP_METHOD(fetchTransactionDetails,
128
128
  properties:(NSDictionary *)properties
129
129
  fetchTransactionDetailsWithResolver:(RCTPromiseResolveBlock)resolve
130
130
  rejecter:(RCTPromiseRejectBlock)reject) {
131
+ @try {
132
+ self.apiService = [RNWrappers apiServiceFromProperties:properties];
133
+ self.completionBlock = [self completionBlockWithResolve:resolve andReject:reject];
131
134
 
132
- self.apiService = [RNWrappers apiServiceFromProperties:properties];
133
- self.completionBlock = [self completionBlockWithResolve:resolve andReject:reject];
134
-
135
- NSString *receiptId = [RNWrappers receiptIdFromProperties:properties];
135
+ NSString *receiptId = [RNWrappers receiptIdFromProperties:properties];
136
136
 
137
- [self.apiService fetchTransactionWithReceiptId:receiptId completion:self.completionBlock];
137
+ [self.apiService fetchTransactionWithReceiptId:receiptId completion:self.completionBlock];
138
+ } @catch (NSException *exception) {
139
+ NSError *error = [[NSError alloc] initWithDomain:RNJudoErrorDomain
140
+ code:0
141
+ userInfo:exception.userInfo];
142
+ reject(kJudoPromiseRejectionCode, exception.reason, error);
143
+ }
138
144
  }
139
145
 
140
146
  //----------------------------------------------
@@ -204,7 +210,17 @@ RCT_REMAP_METHOD(fetchTransactionDetails,
204
210
  message = description;
205
211
  }
206
212
 
207
- reject(kJudoPromiseRejectionCode, message, error);
213
+ // TODO: RCTJSErrorFromCodeMessageAndNSError expects an NSError instane in userInfo[NSUnderlyingErrorKey]
214
+ // which in case of a 3DS SDK error is a NSString ('JP3DSSDKRuntimeException') - so it crashes
215
+ // ! this should be fixed in JudoKit-iOS, and the folowing workaround removed ASAP
216
+ NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:error.userInfo];
217
+ [userInfo removeObjectForKey:NSUnderlyingErrorKey];
218
+ NSError *myError = [[NSError alloc] initWithDomain:error.domain
219
+ code:error.code
220
+ userInfo:[NSDictionary dictionaryWithDictionary:userInfo]];
221
+
222
+
223
+ reject(kJudoPromiseRejectionCode, message, myError);
208
224
  } else {
209
225
  NSDictionary *mappedResponse = [RNWrappers dictionaryFromResponse:response];
210
226
  resolve(mappedResponse);