From bf2f153ad9da36fb2475a10f4d882b182c425620 Mon Sep 17 00:00:00 2001 From: josephj Date: Fri, 20 Oct 2023 13:04:15 +0200 Subject: [PATCH 01/22] Update preview image in README.md --- README.md | 2 +- config/docs/dropin-android.jpg | Bin 372489 -> 0 bytes 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 config/docs/dropin-android.jpg diff --git a/README.md b/README.md index ca66e3a561..7c221a98bb 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ This repository is available under the [MIT license](LICENSE). [shield.license.image]: https://img.shields.io/github/license/Adyen/adyen-android [shield.license.link]: LICENSE [docs.android]: https://docs.adyen.com/online-payments/build-your-integration/?platform=Android -[header.preview]: config/docs/dropin-android.jpg +[header.preview]: https://github.com/Adyen/adyen-android/assets/9079915/e6e18a07-b30f-41f0-b7ef-701b20e2e339 [adyen.testAccount]: https://www.adyen.com/signup [docs.apiKey]: https://docs.adyen.com/development-resources/how-to-get-the-api-key [docs.clientKey]: https://docs.adyen.com/development-resources/client-side-authentication#get-your-client-key diff --git a/config/docs/dropin-android.jpg b/config/docs/dropin-android.jpg deleted file mode 100644 index 45e7a15bc1954f54a7c050870873f1da5d3a3820..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 372489 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/g*~rG33py0OGf)TetDDn-x9kf_Lob6< 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 Date: Fri, 20 Oct 2023 13:47:11 +0200 Subject: [PATCH 02/22] Configure renovate to reduce noise and improve stability By only updating releases more than 30 days old and grouping all updates at the start of the month --- renovate.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index 86df06294f..6d814dd7d1 100644 --- a/renovate.json +++ b/renovate.json @@ -3,5 +3,12 @@ "extends": [ "config:base" ], - "labels": ["Dependencies"] + "labels": ["Dependencies"], + "packageRules" : [ + { + "matchPackagePatterns" : ["*"], + "minimumReleaseAge" : "30 days", + "schedule" : ["on the first day of the month"] + } + ] } From 285079383678481bbf982b1d13ec40f122f7e509 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 20 Oct 2023 19:54:14 +0000 Subject: [PATCH 03/22] Update dependency androidx.compose:compose-bom to v2023.09.01 --- dependencies.gradle | 2 +- gradle/verification-metadata.xml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 2152c041d1..6794fcedaf 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -43,7 +43,7 @@ ext { // Compose Dependencies compose_activity_version = '1.7.2' - compose_bom_version = '2023.09.00' + compose_bom_version = '2023.09.01' compose_viewmodel_version = '2.6.2' // Adyen Dependencies diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 82c569f038..1f2344ff5d 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -430,6 +430,11 @@ + + + + + From 309857301e98288c3c07e9f513e4d740908ca5da Mon Sep 17 00:00:00 2001 From: josephj Date: Wed, 25 Oct 2023 16:57:55 +0200 Subject: [PATCH 04/22] Remove wrong restrict annotations COAND-803 --- .../action/core/internal/ActionHandlingComponent.kt | 2 -- .../components/core/ComponentAvailableCallback.kt | 3 --- .../checkout/components/core/internal/ActionComponent.kt | 2 -- .../core/internal/ActivityResultHandlingComponent.kt | 2 -- .../checkout/components/core/internal/ButtonComponent.kt | 3 --- .../core/internal/ButtonConfigurationBuilder.kt | 3 --- .../components/core/internal/IntentHandlingComponent.kt | 2 -- .../components/core/internal/PaymentComponent.kt | 2 -- .../core/internal/PaymentMethodAvailabilityCheck.kt | 2 -- .../core/internal/provider/ActionComponentProvider.kt | 1 - .../dropin/internal/provider/ComponentParsingProvider.kt | 2 +- .../wechatpay/{internal => }/WeChatPayProvider.kt | 9 +++------ 12 files changed, 4 insertions(+), 29 deletions(-) rename wechatpay/src/main/java/com/adyen/checkout/wechatpay/{internal => }/WeChatPayProvider.kt (85%) diff --git a/action-core/src/main/java/com/adyen/checkout/action/core/internal/ActionHandlingComponent.kt b/action-core/src/main/java/com/adyen/checkout/action/core/internal/ActionHandlingComponent.kt index 457cd30f81..c8e2a87fad 100644 --- a/action-core/src/main/java/com/adyen/checkout/action/core/internal/ActionHandlingComponent.kt +++ b/action-core/src/main/java/com/adyen/checkout/action/core/internal/ActionHandlingComponent.kt @@ -10,10 +10,8 @@ package com.adyen.checkout.action.core.internal import android.app.Activity import android.content.Intent -import androidx.annotation.RestrictTo import com.adyen.checkout.components.core.action.Action -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) interface ActionHandlingComponent { /** diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/ComponentAvailableCallback.kt b/components-core/src/main/java/com/adyen/checkout/components/core/ComponentAvailableCallback.kt index 2ca339fa45..f5f2c00f36 100644 --- a/components-core/src/main/java/com/adyen/checkout/components/core/ComponentAvailableCallback.kt +++ b/components-core/src/main/java/com/adyen/checkout/components/core/ComponentAvailableCallback.kt @@ -7,9 +7,6 @@ */ package com.adyen.checkout.components.core -import androidx.annotation.RestrictTo - -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) interface ComponentAvailableCallback { fun onAvailabilityResult(isAvailable: Boolean, paymentMethod: PaymentMethod) } diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/internal/ActionComponent.kt b/components-core/src/main/java/com/adyen/checkout/components/core/internal/ActionComponent.kt index c97e6a504f..efdd494ef4 100644 --- a/components-core/src/main/java/com/adyen/checkout/components/core/internal/ActionComponent.kt +++ b/components-core/src/main/java/com/adyen/checkout/components/core/internal/ActionComponent.kt @@ -8,7 +8,6 @@ package com.adyen.checkout.components.core.internal import android.app.Activity -import androidx.annotation.RestrictTo import com.adyen.checkout.components.core.ActionComponentData import com.adyen.checkout.components.core.action.Action @@ -18,7 +17,6 @@ import com.adyen.checkout.components.core.action.Action * If an [ActionComponentData] is emitted from this component, it should be sent back through the /payments/details API * call. */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) interface ActionComponent : Component { /** diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/internal/ActivityResultHandlingComponent.kt b/components-core/src/main/java/com/adyen/checkout/components/core/internal/ActivityResultHandlingComponent.kt index 9d558006ff..957b72236b 100644 --- a/components-core/src/main/java/com/adyen/checkout/components/core/internal/ActivityResultHandlingComponent.kt +++ b/components-core/src/main/java/com/adyen/checkout/components/core/internal/ActivityResultHandlingComponent.kt @@ -10,12 +10,10 @@ package com.adyen.checkout.components.core.internal import android.app.Activity import android.content.Intent -import androidx.annotation.RestrictTo /** * A component that expects to receive and handle an activity result. */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) interface ActivityResultHandlingComponent : ResultHandlingComponent { /** diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/internal/ButtonComponent.kt b/components-core/src/main/java/com/adyen/checkout/components/core/internal/ButtonComponent.kt index 5baf7ce5f4..c73e7c74b5 100644 --- a/components-core/src/main/java/com/adyen/checkout/components/core/internal/ButtonComponent.kt +++ b/components-core/src/main/java/com/adyen/checkout/components/core/internal/ButtonComponent.kt @@ -8,13 +8,10 @@ package com.adyen.checkout.components.core.internal -import androidx.annotation.RestrictTo - /** * A component that requires a button to be clicked so that it can be submitted. This button might be visible during all * or part of the payment flow. */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) interface ButtonComponent { /** diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/internal/ButtonConfigurationBuilder.kt b/components-core/src/main/java/com/adyen/checkout/components/core/internal/ButtonConfigurationBuilder.kt index d3de26819f..f55265c97a 100644 --- a/components-core/src/main/java/com/adyen/checkout/components/core/internal/ButtonConfigurationBuilder.kt +++ b/components-core/src/main/java/com/adyen/checkout/components/core/internal/ButtonConfigurationBuilder.kt @@ -1,8 +1,5 @@ package com.adyen.checkout.components.core.internal -import androidx.annotation.RestrictTo - -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) interface ButtonConfigurationBuilder { fun setSubmitButtonVisible(isSubmitButtonVisible: Boolean): ButtonConfigurationBuilder diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/internal/IntentHandlingComponent.kt b/components-core/src/main/java/com/adyen/checkout/components/core/internal/IntentHandlingComponent.kt index 180b6ce24f..a9432c4ad7 100644 --- a/components-core/src/main/java/com/adyen/checkout/components/core/internal/IntentHandlingComponent.kt +++ b/components-core/src/main/java/com/adyen/checkout/components/core/internal/IntentHandlingComponent.kt @@ -10,12 +10,10 @@ package com.adyen.checkout.components.core.internal import android.app.Activity import android.content.Intent -import androidx.annotation.RestrictTo /** * A component that expects to receive and handle an external result in the form of an [Intent]. */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) interface IntentHandlingComponent : ResultHandlingComponent { /** diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/internal/PaymentComponent.kt b/components-core/src/main/java/com/adyen/checkout/components/core/internal/PaymentComponent.kt index 1d0b2c0571..35eafcb9de 100644 --- a/components-core/src/main/java/com/adyen/checkout/components/core/internal/PaymentComponent.kt +++ b/components-core/src/main/java/com/adyen/checkout/components/core/internal/PaymentComponent.kt @@ -7,7 +7,6 @@ */ package com.adyen.checkout.components.core.internal -import androidx.annotation.RestrictTo import com.adyen.checkout.components.core.PaymentComponentState import com.adyen.checkout.components.core.paymentmethod.PaymentMethodDetails @@ -20,7 +19,6 @@ import com.adyen.checkout.components.core.paymentmethod.PaymentMethodDetails * * Can be attached to [AdyenComponentView] to present a view to the user. */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) interface PaymentComponent : Component { /** diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/internal/PaymentMethodAvailabilityCheck.kt b/components-core/src/main/java/com/adyen/checkout/components/core/internal/PaymentMethodAvailabilityCheck.kt index e84c20c017..285b133a3f 100644 --- a/components-core/src/main/java/com/adyen/checkout/components/core/internal/PaymentMethodAvailabilityCheck.kt +++ b/components-core/src/main/java/com/adyen/checkout/components/core/internal/PaymentMethodAvailabilityCheck.kt @@ -8,7 +8,6 @@ package com.adyen.checkout.components.core.internal import android.app.Application -import androidx.annotation.RestrictTo import com.adyen.checkout.components.core.ComponentAvailableCallback import com.adyen.checkout.components.core.PaymentMethod @@ -18,7 +17,6 @@ import com.adyen.checkout.components.core.PaymentMethod * @param ConfigurationT The Configuration for the Component corresponding to this payment method. Simply use * [Configuration] if not applicable. */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) interface PaymentMethodAvailabilityCheck { fun isAvailable( applicationContext: Application, diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/internal/provider/ActionComponentProvider.kt b/components-core/src/main/java/com/adyen/checkout/components/core/internal/provider/ActionComponentProvider.kt index b2f9420de2..1460e9a9b7 100644 --- a/components-core/src/main/java/com/adyen/checkout/components/core/internal/provider/ActionComponentProvider.kt +++ b/components-core/src/main/java/com/adyen/checkout/components/core/internal/provider/ActionComponentProvider.kt @@ -22,7 +22,6 @@ import com.adyen.checkout.components.core.internal.Configuration import com.adyen.checkout.components.core.internal.ui.ActionDelegate import com.adyen.checkout.components.core.internal.util.requireApplication -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) interface ActionComponentProvider< ComponentT : ActionComponent, ConfigurationT : Configuration, diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/provider/ComponentParsingProvider.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/provider/ComponentParsingProvider.kt index 310e307287..841f078ad4 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/provider/ComponentParsingProvider.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/provider/ComponentParsingProvider.kt @@ -150,7 +150,7 @@ import com.adyen.checkout.upi.UPIComponent import com.adyen.checkout.upi.UPIComponentState import com.adyen.checkout.upi.UPIConfiguration import com.adyen.checkout.upi.internal.provider.UPIComponentProvider -import com.adyen.checkout.wechatpay.internal.WeChatPayProvider +import com.adyen.checkout.wechatpay.WeChatPayProvider private val TAG = LogUtil.getTag() diff --git a/wechatpay/src/main/java/com/adyen/checkout/wechatpay/internal/WeChatPayProvider.kt b/wechatpay/src/main/java/com/adyen/checkout/wechatpay/WeChatPayProvider.kt similarity index 85% rename from wechatpay/src/main/java/com/adyen/checkout/wechatpay/internal/WeChatPayProvider.kt rename to wechatpay/src/main/java/com/adyen/checkout/wechatpay/WeChatPayProvider.kt index 7cc5532eec..195d988805 100644 --- a/wechatpay/src/main/java/com/adyen/checkout/wechatpay/internal/WeChatPayProvider.kt +++ b/wechatpay/src/main/java/com/adyen/checkout/wechatpay/WeChatPayProvider.kt @@ -1,20 +1,18 @@ /* - * Copyright (c) 2021 Adyen N.V. + * Copyright (c) 2023 Adyen N.V. * * This file is open source and available under the MIT license. See the LICENSE file for more info. * - * Created by josephj on 17/5/2021. + * Created by josephj on 27/10/2023. */ -package com.adyen.checkout.wechatpay.internal +package com.adyen.checkout.wechatpay import android.app.Application -import androidx.annotation.RestrictTo import com.adyen.checkout.components.core.ComponentAvailableCallback import com.adyen.checkout.components.core.PaymentMethod import com.adyen.checkout.components.core.internal.Configuration import com.adyen.checkout.components.core.internal.PaymentMethodAvailabilityCheck -import com.adyen.checkout.wechatpay.WeChatPayActionComponent import com.tencent.mm.opensdk.constants.Build import com.tencent.mm.opensdk.openapi.WXAPIFactory @@ -24,7 +22,6 @@ import com.tencent.mm.opensdk.openapi.WXAPIFactory * You can directly call /payments after you receive a callback from [isAvailable]. * You can use [WeChatPayActionComponent] to handle the returned action. */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) class WeChatPayProvider : PaymentMethodAvailabilityCheck { override fun isAvailable( From 5f9debbf86e7435fedc92438d6a152c6d7672620 Mon Sep 17 00:00:00 2001 From: josephj Date: Wed, 25 Oct 2023 17:07:20 +0200 Subject: [PATCH 05/22] Remove unnecessary generic in provider.get method --- .../core/internal/provider/ActionComponentProvider.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/internal/provider/ActionComponentProvider.kt b/components-core/src/main/java/com/adyen/checkout/components/core/internal/provider/ActionComponentProvider.kt index 1460e9a9b7..3e7cae34e0 100644 --- a/components-core/src/main/java/com/adyen/checkout/components/core/internal/provider/ActionComponentProvider.kt +++ b/components-core/src/main/java/com/adyen/checkout/components/core/internal/provider/ActionComponentProvider.kt @@ -71,7 +71,7 @@ interface ActionComponentProvider< * * @return The Component */ - fun get( + fun get( activity: ComponentActivity, configuration: ConfigurationT, callback: ActionComponentCallback, From 617480cc44551835fda586605e7a848ba2287d12 Mon Sep 17 00:00:00 2001 From: josephj Date: Fri, 27 Oct 2023 13:29:33 +0200 Subject: [PATCH 06/22] Update release notes COAND-803 --- RELEASE_NOTES.md | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 399a9e5e18..4ebae3c15c 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -8,22 +8,5 @@ [//]: # ( # Deprecated) [//]: # ( - Configurations public constructor are deprecated, please use each Configuration's builder to make a Configuration object) -For guidance on integrating with this version, have a look at the [integration guide](https://docs.adyen.com/online-payments/build-your-integration/?platform=Android). - -If your integration uses Android v4.13.3 and earlier, and you're upgrading it to use v5.0.0, you can follow the [migration guide](https://docs.adyen.com/online-payments/build-your-integration/migrate-to-android-5-0-0/). - -These are the changes between the beta and stable release. For the full release notes that include all the changes from v4.13.3, see the [release notes in our Docs](https://docs.adyen.com/online-payments/release-notes/?version=5.0.0&integration_type=android). - -## Breaking changes -- `Amount.EMPTY` is removed. Make sure you pass amounts with a valid value and currency. - ## Fixed -- `@RestrictTo` annotations no longer cause false errors with Android Studio Hedgehog (Beta). -- The Drop-in bottom sheet will no longer shift position on the screen when launching some flows like redirect and 3D Secure 2. - -## Changed -- Dependency versions: - | Name | Version | - |--------------------------------------------------------------------------------------------------------|-------------------------------| - | [Google Pay](https://developers.google.com/pay/api/android/support/release-notes#sept-14) | **19.2.1** | - | [AndroidX Compose BoM](https://developer.android.com/jetpack/compose/bom/bom-mapping) | **2023.09.00** | +- `@RestrictTo` annotations no longer cause false errors with Android Studio and Lint. From cae4d473ab95c0288aab0e87a3429b7f842a6f13 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Fri, 27 Oct 2023 11:01:18 +0200 Subject: [PATCH 07/22] Instantiate views with context only Passing the attrs and defStyleAttr should only be used by the system when creating the view from XML. Because we were passing the attrs and when you enable the layout inspector it would result in a crash, because the system tries to load some XML from the attrs which is actually null. COAND-809 --- .../adyen3ds2/internal/ui/Adyen3DS2ViewProvider.kt | 5 +---- .../ach/internal/ui/ACHDirectDebitViewProvider.kt | 5 +---- .../checkout/await/internal/ui/AwaitViewProvider.kt | 5 +---- .../bacs/internal/ui/BacsDirectDebitViewProvider.kt | 7 ++----- .../checkout/bcmc/internal/ui/BcmcViewProvider.kt | 5 +---- .../checkout/blik/internal/ui/BlikViewProvider.kt | 5 +---- .../checkout/boleto/internal/ui/BoletoViewProvider.kt | 5 +---- .../checkout/card/internal/ui/CardViewProvider.kt | 7 ++----- .../cashapppay/internal/ui/CashAppPayViewProvider.kt | 11 ++++------- .../econtext/internal/ui/EContextViewProvider.kt | 5 +---- .../giftcard/internal/ui/GiftCardViewProvider.kt | 5 +---- .../issuerlist/internal/ui/IssuerListViewProvider.kt | 7 ++----- .../checkout/mbway/internal/ui/MbWayViewProvider.kt | 5 +---- .../internal/ui/OnlineBankingViewProvider.kt | 5 +---- .../paybybank/internal/ui/PayByBankViewProvider.kt | 5 +---- .../checkout/qrcode/internal/ui/QrCodeViewProvider.kt | 9 +++------ .../redirect/internal/ui/RedirectViewProvider.kt | 5 +---- .../checkout/sepa/internal/ui/SepaViewProvider.kt | 5 +---- .../com/adyen/checkout/ui/core/AdyenComponentView.kt | 8 ++++---- .../ui/core/internal/ui/ButtonViewProvider.kt | 6 +----- .../checkout/ui/core/internal/ui/ViewProvider.kt | 3 --- .../adyen/checkout/upi/internal/ui/UPIViewProvider.kt | 5 +---- .../voucher/internal/ui/VoucherViewProvider.kt | 7 ++----- .../wechatpay/internal/ui/WeChatViewProvider.kt | 5 +---- 24 files changed, 35 insertions(+), 105 deletions(-) diff --git a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/Adyen3DS2ViewProvider.kt b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/Adyen3DS2ViewProvider.kt index 60a200a1b3..a37989143f 100644 --- a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/Adyen3DS2ViewProvider.kt +++ b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/Adyen3DS2ViewProvider.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.adyen3ds2.internal.ui import android.content.Context -import android.util.AttributeSet import com.adyen.checkout.ui.core.internal.ui.ComponentView import com.adyen.checkout.ui.core.internal.ui.ComponentViewType import com.adyen.checkout.ui.core.internal.ui.ViewProvider @@ -20,10 +19,8 @@ internal object Adyen3DS2ViewProvider : ViewProvider { override fun getView( viewType: ComponentViewType, context: Context, - attrs: AttributeSet?, - defStyleAttr: Int ): ComponentView = when (viewType) { - Adyen3DS2ComponentViewType -> PaymentInProgressView(context, attrs, defStyleAttr) + Adyen3DS2ComponentViewType -> PaymentInProgressView(context) else -> error("Unsupported view type") } } diff --git a/ach/src/main/java/com/adyen/checkout/ach/internal/ui/ACHDirectDebitViewProvider.kt b/ach/src/main/java/com/adyen/checkout/ach/internal/ui/ACHDirectDebitViewProvider.kt index 1d6b34b835..9b4280b263 100644 --- a/ach/src/main/java/com/adyen/checkout/ach/internal/ui/ACHDirectDebitViewProvider.kt +++ b/ach/src/main/java/com/adyen/checkout/ach/internal/ui/ACHDirectDebitViewProvider.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.ach.internal.ui import android.content.Context -import android.util.AttributeSet import com.adyen.checkout.ach.internal.ui.view.ACHDirectDebitView import com.adyen.checkout.ui.core.internal.ui.AmountButtonComponentViewType import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType @@ -21,11 +20,9 @@ internal object ACHDirectDebitViewProvider : ViewProvider { override fun getView( viewType: ComponentViewType, context: Context, - attrs: AttributeSet?, - defStyleAttr: Int ): ComponentView { return when (viewType) { - ACHDirectDebitComponentViewType -> ACHDirectDebitView(context, attrs, defStyleAttr) + ACHDirectDebitComponentViewType -> ACHDirectDebitView(context) else -> throw IllegalArgumentException("Unsupported view type") } } diff --git a/await/src/main/java/com/adyen/checkout/await/internal/ui/AwaitViewProvider.kt b/await/src/main/java/com/adyen/checkout/await/internal/ui/AwaitViewProvider.kt index adc8dedf3f..ec34cc6985 100644 --- a/await/src/main/java/com/adyen/checkout/await/internal/ui/AwaitViewProvider.kt +++ b/await/src/main/java/com/adyen/checkout/await/internal/ui/AwaitViewProvider.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.await.internal.ui import android.content.Context -import android.util.AttributeSet import com.adyen.checkout.await.internal.ui.view.AwaitView import com.adyen.checkout.ui.core.internal.ui.ComponentView import com.adyen.checkout.ui.core.internal.ui.ComponentViewType @@ -20,11 +19,9 @@ internal object AwaitViewProvider : ViewProvider { override fun getView( viewType: ComponentViewType, context: Context, - attrs: AttributeSet?, - defStyleAttr: Int ): ComponentView { return when (viewType) { - AwaitComponentViewType -> AwaitView(context, attrs, defStyleAttr) + AwaitComponentViewType -> AwaitView(context) else -> throw IllegalArgumentException("Unsupported view type") } } diff --git a/bacs/src/main/java/com/adyen/checkout/bacs/internal/ui/BacsDirectDebitViewProvider.kt b/bacs/src/main/java/com/adyen/checkout/bacs/internal/ui/BacsDirectDebitViewProvider.kt index abbffebd07..430700c089 100644 --- a/bacs/src/main/java/com/adyen/checkout/bacs/internal/ui/BacsDirectDebitViewProvider.kt +++ b/bacs/src/main/java/com/adyen/checkout/bacs/internal/ui/BacsDirectDebitViewProvider.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.bacs.internal.ui import android.content.Context -import android.util.AttributeSet import com.adyen.checkout.bacs.R import com.adyen.checkout.bacs.internal.ui.view.BacsDirectDebitConfirmationView import com.adyen.checkout.bacs.internal.ui.view.BacsDirectDebitInputView @@ -22,12 +21,10 @@ internal object BacsDirectDebitViewProvider : ViewProvider { override fun getView( viewType: ComponentViewType, context: Context, - attrs: AttributeSet?, - defStyleAttr: Int ): ComponentView { return when (viewType) { - BacsComponentViewType.INPUT -> BacsDirectDebitInputView(context, attrs, defStyleAttr) - BacsComponentViewType.CONFIRMATION -> BacsDirectDebitConfirmationView(context, attrs, defStyleAttr) + BacsComponentViewType.INPUT -> BacsDirectDebitInputView(context) + BacsComponentViewType.CONFIRMATION -> BacsDirectDebitConfirmationView(context) else -> throw IllegalArgumentException("Unsupported view type") } } diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/BcmcViewProvider.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/BcmcViewProvider.kt index 3a928fdf57..ad42c0230f 100644 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/BcmcViewProvider.kt +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/BcmcViewProvider.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.bcmc.internal.ui import android.content.Context -import android.util.AttributeSet import com.adyen.checkout.bcmc.internal.ui.view.BcmcView import com.adyen.checkout.ui.core.internal.ui.AmountButtonComponentViewType import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType @@ -22,10 +21,8 @@ internal object BcmcViewProvider : ViewProvider { override fun getView( viewType: ComponentViewType, context: Context, - attrs: AttributeSet?, - defStyleAttr: Int ): ComponentView = when (viewType) { - BcmcComponentViewType -> BcmcView(context, attrs, defStyleAttr) + BcmcComponentViewType -> BcmcView(context) else -> throw IllegalArgumentException("Unsupported view type") } } diff --git a/blik/src/main/java/com/adyen/checkout/blik/internal/ui/BlikViewProvider.kt b/blik/src/main/java/com/adyen/checkout/blik/internal/ui/BlikViewProvider.kt index 41a275c785..8f127d1547 100644 --- a/blik/src/main/java/com/adyen/checkout/blik/internal/ui/BlikViewProvider.kt +++ b/blik/src/main/java/com/adyen/checkout/blik/internal/ui/BlikViewProvider.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.blik.internal.ui import android.content.Context -import android.util.AttributeSet import com.adyen.checkout.blik.internal.ui.view.BlikView import com.adyen.checkout.ui.core.internal.ui.AmountButtonComponentViewType import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType @@ -21,11 +20,9 @@ internal object BlikViewProvider : ViewProvider { override fun getView( viewType: ComponentViewType, context: Context, - attrs: AttributeSet?, - defStyleAttr: Int ): ComponentView { return when (viewType) { - BlikComponentViewType -> BlikView(context, attrs, defStyleAttr) + BlikComponentViewType -> BlikView(context) else -> throw IllegalArgumentException("Unsupported view type") } } diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/BoletoViewProvider.kt b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/BoletoViewProvider.kt index 5bad258611..f8f0ab0d7a 100644 --- a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/BoletoViewProvider.kt +++ b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/BoletoViewProvider.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.boleto.internal.ui import android.content.Context -import android.util.AttributeSet import com.adyen.checkout.boleto.R import com.adyen.checkout.boleto.internal.ui.view.BoletoView import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType @@ -21,10 +20,8 @@ internal object BoletoViewProvider : ViewProvider { override fun getView( viewType: ComponentViewType, context: Context, - attrs: AttributeSet?, - defStyleAttr: Int ): ComponentView = when (viewType) { - BoletoComponentViewType -> BoletoView(context, attrs, defStyleAttr) + BoletoComponentViewType -> BoletoView(context) else -> throw IllegalArgumentException("Unsupported view type") } } diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/CardViewProvider.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/CardViewProvider.kt index 2aa784189c..db82d8cf7b 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/CardViewProvider.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/CardViewProvider.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.card.internal.ui import android.content.Context -import android.util.AttributeSet import com.adyen.checkout.card.internal.ui.view.CardView import com.adyen.checkout.card.internal.ui.view.StoredCardView import com.adyen.checkout.ui.core.internal.ui.AmountButtonComponentViewType @@ -23,12 +22,10 @@ internal object CardViewProvider : ViewProvider { override fun getView( viewType: ComponentViewType, context: Context, - attrs: AttributeSet?, - defStyleAttr: Int ): ComponentView { return when (viewType) { - CardComponentViewType.DefaultCardView -> CardView(context, attrs, defStyleAttr) - CardComponentViewType.StoredCardView -> StoredCardView(context, attrs, defStyleAttr) + CardComponentViewType.DefaultCardView -> CardView(context) + CardComponentViewType.StoredCardView -> StoredCardView(context) else -> throw IllegalArgumentException("Unsupported view type") } } diff --git a/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/CashAppPayViewProvider.kt b/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/CashAppPayViewProvider.kt index ff42afd501..de259f016b 100644 --- a/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/CashAppPayViewProvider.kt +++ b/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/CashAppPayViewProvider.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.cashapppay.internal.ui import android.content.Context -import android.util.AttributeSet import com.adyen.checkout.cashapppay.internal.ui.view.CashAppPayButtonView import com.adyen.checkout.cashapppay.internal.ui.view.CashAppPayView import com.adyen.checkout.cashapppay.internal.ui.view.CashAppPayWaitingView @@ -25,18 +24,16 @@ internal object CashAppPayViewProvider : ViewProvider { override fun getView( viewType: ComponentViewType, context: Context, - attrs: AttributeSet?, - defStyleAttr: Int ): ComponentView = when (viewType) { - CashAppPayComponentViewType -> CashAppPayView(context, attrs, defStyleAttr) - PaymentInProgressViewType -> CashAppPayWaitingView(context, attrs, defStyleAttr) + CashAppPayComponentViewType -> CashAppPayView(context) + PaymentInProgressViewType -> CashAppPayWaitingView(context) else -> throw IllegalArgumentException("Unsupported view type") } } internal class CashAppPayButtonViewProvider : ButtonViewProvider { - override fun getButton(context: Context, attrs: AttributeSet?, defStyleAttr: Int): PayButton = - CashAppPayButtonView(context, attrs, defStyleAttr) + override fun getButton(context: Context): PayButton = + CashAppPayButtonView(context) } internal object CashAppPayComponentViewType : ButtonComponentViewType { diff --git a/econtext/src/main/java/com/adyen/checkout/econtext/internal/ui/EContextViewProvider.kt b/econtext/src/main/java/com/adyen/checkout/econtext/internal/ui/EContextViewProvider.kt index ca0b8ff52e..6fec8a4c88 100644 --- a/econtext/src/main/java/com/adyen/checkout/econtext/internal/ui/EContextViewProvider.kt +++ b/econtext/src/main/java/com/adyen/checkout/econtext/internal/ui/EContextViewProvider.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.econtext.internal.ui import android.content.Context -import android.util.AttributeSet import com.adyen.checkout.econtext.internal.ui.view.EContextView import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType import com.adyen.checkout.ui.core.internal.ui.ComponentView @@ -21,11 +20,9 @@ internal object EContextViewProvider : ViewProvider { override fun getView( viewType: ComponentViewType, context: Context, - attrs: AttributeSet?, - defStyleAttr: Int ): ComponentView { return when (viewType) { - EContextComponentViewType -> EContextView(context, attrs, defStyleAttr) + EContextComponentViewType -> EContextView(context) else -> throw IllegalArgumentException("Unsupported view type") } } diff --git a/giftcard/src/main/java/com/adyen/checkout/giftcard/internal/ui/GiftCardViewProvider.kt b/giftcard/src/main/java/com/adyen/checkout/giftcard/internal/ui/GiftCardViewProvider.kt index 4fa1c7e18d..640e238629 100644 --- a/giftcard/src/main/java/com/adyen/checkout/giftcard/internal/ui/GiftCardViewProvider.kt +++ b/giftcard/src/main/java/com/adyen/checkout/giftcard/internal/ui/GiftCardViewProvider.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.giftcard.internal.ui import android.content.Context -import android.util.AttributeSet import com.adyen.checkout.giftcard.R import com.adyen.checkout.giftcard.internal.ui.view.GiftCardView import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType @@ -22,11 +21,9 @@ internal object GiftCardViewProvider : ViewProvider { override fun getView( viewType: ComponentViewType, context: Context, - attrs: AttributeSet?, - defStyleAttr: Int ): ComponentView { return when (viewType) { - GiftCardComponentViewType -> GiftCardView(context, attrs, defStyleAttr) + GiftCardComponentViewType -> GiftCardView(context) else -> throw IllegalArgumentException("Unsupported view type") } } diff --git a/issuer-list/src/main/java/com/adyen/checkout/issuerlist/internal/ui/IssuerListViewProvider.kt b/issuer-list/src/main/java/com/adyen/checkout/issuerlist/internal/ui/IssuerListViewProvider.kt index 1793018c4c..74d5d74fc9 100644 --- a/issuer-list/src/main/java/com/adyen/checkout/issuerlist/internal/ui/IssuerListViewProvider.kt +++ b/issuer-list/src/main/java/com/adyen/checkout/issuerlist/internal/ui/IssuerListViewProvider.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.issuerlist.internal.ui import android.content.Context -import android.util.AttributeSet import com.adyen.checkout.issuerlist.internal.ui.view.IssuerListRecyclerView import com.adyen.checkout.issuerlist.internal.ui.view.IssuerListSpinnerView import com.adyen.checkout.ui.core.internal.ui.AmountButtonComponentViewType @@ -23,12 +22,10 @@ internal object IssuerListViewProvider : ViewProvider { override fun getView( viewType: ComponentViewType, context: Context, - attrs: AttributeSet?, - defStyleAttr: Int ): ComponentView { return when (viewType) { - IssuerListComponentViewType.RecyclerView -> IssuerListRecyclerView(context, attrs, defStyleAttr) - IssuerListComponentViewType.SpinnerView -> IssuerListSpinnerView(context, attrs, defStyleAttr) + IssuerListComponentViewType.RecyclerView -> IssuerListRecyclerView(context) + IssuerListComponentViewType.SpinnerView -> IssuerListSpinnerView(context) else -> throw IllegalArgumentException("Unsupported view type") } } diff --git a/mbway/src/main/java/com/adyen/checkout/mbway/internal/ui/MbWayViewProvider.kt b/mbway/src/main/java/com/adyen/checkout/mbway/internal/ui/MbWayViewProvider.kt index 77de37e746..fdd5cdd39c 100644 --- a/mbway/src/main/java/com/adyen/checkout/mbway/internal/ui/MbWayViewProvider.kt +++ b/mbway/src/main/java/com/adyen/checkout/mbway/internal/ui/MbWayViewProvider.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.mbway.internal.ui import android.content.Context -import android.util.AttributeSet import com.adyen.checkout.mbway.internal.ui.view.MbWayView import com.adyen.checkout.ui.core.internal.ui.AmountButtonComponentViewType import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType @@ -22,10 +21,8 @@ internal object MbWayViewProvider : ViewProvider { override fun getView( viewType: ComponentViewType, context: Context, - attrs: AttributeSet?, - defStyleAttr: Int ): ComponentView = when (viewType) { - MbWayComponentViewType -> MbWayView(context, attrs, defStyleAttr) + MbWayComponentViewType -> MbWayView(context) else -> throw IllegalArgumentException("Unsupported view type") } } diff --git a/online-banking-core/src/main/java/com/adyen/checkout/onlinebankingcore/internal/ui/OnlineBankingViewProvider.kt b/online-banking-core/src/main/java/com/adyen/checkout/onlinebankingcore/internal/ui/OnlineBankingViewProvider.kt index f166364a12..343baabc86 100644 --- a/online-banking-core/src/main/java/com/adyen/checkout/onlinebankingcore/internal/ui/OnlineBankingViewProvider.kt +++ b/online-banking-core/src/main/java/com/adyen/checkout/onlinebankingcore/internal/ui/OnlineBankingViewProvider.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.onlinebankingcore.internal.ui import android.content.Context -import android.util.AttributeSet import com.adyen.checkout.ui.core.internal.ui.AmountButtonComponentViewType import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType import com.adyen.checkout.ui.core.internal.ui.ComponentView @@ -21,11 +20,9 @@ internal object OnlineBankingViewProvider : ViewProvider { override fun getView( viewType: ComponentViewType, context: Context, - attrs: AttributeSet?, - defStyleAttr: Int ): ComponentView { return when (viewType) { - OnlineBankingComponentViewType -> OnlineBankingView(context, attrs, defStyleAttr) + OnlineBankingComponentViewType -> OnlineBankingView(context) else -> throw IllegalArgumentException("Unsupported view type") } } diff --git a/paybybank/src/main/java/com/adyen/checkout/paybybank/internal/ui/PayByBankViewProvider.kt b/paybybank/src/main/java/com/adyen/checkout/paybybank/internal/ui/PayByBankViewProvider.kt index 440a5bc278..80446f7c3c 100644 --- a/paybybank/src/main/java/com/adyen/checkout/paybybank/internal/ui/PayByBankViewProvider.kt +++ b/paybybank/src/main/java/com/adyen/checkout/paybybank/internal/ui/PayByBankViewProvider.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.paybybank.internal.ui import android.content.Context -import android.util.AttributeSet import com.adyen.checkout.paybybank.internal.ui.view.PayByBankView import com.adyen.checkout.ui.core.internal.ui.ComponentView import com.adyen.checkout.ui.core.internal.ui.ComponentViewType @@ -20,11 +19,9 @@ internal object PayByBankViewProvider : ViewProvider { override fun getView( viewType: ComponentViewType, context: Context, - attrs: AttributeSet?, - defStyleAttr: Int ): ComponentView { return when (viewType) { - PayByBankComponentViewType -> PayByBankView(context, attrs, defStyleAttr) + PayByBankComponentViewType -> PayByBankView(context) else -> throw IllegalArgumentException("Unsupported view type") } } diff --git a/qr-code/src/main/java/com/adyen/checkout/qrcode/internal/ui/QrCodeViewProvider.kt b/qr-code/src/main/java/com/adyen/checkout/qrcode/internal/ui/QrCodeViewProvider.kt index da69bda3d6..c1ee33ad32 100644 --- a/qr-code/src/main/java/com/adyen/checkout/qrcode/internal/ui/QrCodeViewProvider.kt +++ b/qr-code/src/main/java/com/adyen/checkout/qrcode/internal/ui/QrCodeViewProvider.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.qrcode.internal.ui import android.content.Context -import android.util.AttributeSet import com.adyen.checkout.qrcode.internal.ui.view.FullQRCodeView import com.adyen.checkout.qrcode.internal.ui.view.SimpleQRCodeView import com.adyen.checkout.ui.core.internal.ui.ComponentView @@ -22,12 +21,10 @@ internal object QrCodeViewProvider : ViewProvider { override fun getView( viewType: ComponentViewType, context: Context, - attrs: AttributeSet?, - defStyleAttr: Int ): ComponentView = when (viewType) { - QrCodeComponentViewType.SIMPLE_QR_CODE -> SimpleQRCodeView(context, attrs, defStyleAttr) - QrCodeComponentViewType.FULL_QR_CODE -> FullQRCodeView(context, attrs, defStyleAttr) - QrCodeComponentViewType.REDIRECT -> PaymentInProgressView(context, attrs, defStyleAttr) + QrCodeComponentViewType.SIMPLE_QR_CODE -> SimpleQRCodeView(context) + QrCodeComponentViewType.FULL_QR_CODE -> FullQRCodeView(context) + QrCodeComponentViewType.REDIRECT -> PaymentInProgressView(context) else -> throw IllegalArgumentException("Unsupported view type") } } diff --git a/redirect/src/main/java/com/adyen/checkout/redirect/internal/ui/RedirectViewProvider.kt b/redirect/src/main/java/com/adyen/checkout/redirect/internal/ui/RedirectViewProvider.kt index 27f1cbfa2f..ca69e50260 100644 --- a/redirect/src/main/java/com/adyen/checkout/redirect/internal/ui/RedirectViewProvider.kt +++ b/redirect/src/main/java/com/adyen/checkout/redirect/internal/ui/RedirectViewProvider.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.redirect.internal.ui import android.content.Context -import android.util.AttributeSet import com.adyen.checkout.ui.core.internal.ui.ComponentView import com.adyen.checkout.ui.core.internal.ui.ComponentViewType import com.adyen.checkout.ui.core.internal.ui.ViewProvider @@ -20,10 +19,8 @@ internal object RedirectViewProvider : ViewProvider { override fun getView( viewType: ComponentViewType, context: Context, - attrs: AttributeSet?, - defStyleAttr: Int ): ComponentView = when (viewType) { - RedirectComponentViewType -> PaymentInProgressView(context, attrs, defStyleAttr) + RedirectComponentViewType -> PaymentInProgressView(context) else -> throw IllegalArgumentException("Unsupported view type") } } diff --git a/sepa/src/main/java/com/adyen/checkout/sepa/internal/ui/SepaViewProvider.kt b/sepa/src/main/java/com/adyen/checkout/sepa/internal/ui/SepaViewProvider.kt index dc3045fe59..3670324d94 100644 --- a/sepa/src/main/java/com/adyen/checkout/sepa/internal/ui/SepaViewProvider.kt +++ b/sepa/src/main/java/com/adyen/checkout/sepa/internal/ui/SepaViewProvider.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.sepa.internal.ui import android.content.Context -import android.util.AttributeSet import com.adyen.checkout.sepa.internal.ui.view.SepaView import com.adyen.checkout.ui.core.internal.ui.AmountButtonComponentViewType import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType @@ -22,11 +21,9 @@ internal object SepaViewProvider : ViewProvider { override fun getView( viewType: ComponentViewType, context: Context, - attrs: AttributeSet?, - defStyleAttr: Int ): ComponentView { return when (viewType) { - SepaComponentViewType -> SepaView(context, attrs, defStyleAttr) + SepaComponentViewType -> SepaView(context) else -> throw IllegalArgumentException("Unsupported view type") } } diff --git a/ui-core/src/main/java/com/adyen/checkout/ui/core/AdyenComponentView.kt b/ui-core/src/main/java/com/adyen/checkout/ui/core/AdyenComponentView.kt index b38a9e8a48..94545790b7 100644 --- a/ui-core/src/main/java/com/adyen/checkout/ui/core/AdyenComponentView.kt +++ b/ui-core/src/main/java/com/adyen/checkout/ui/core/AdyenComponentView.kt @@ -49,8 +49,8 @@ import java.lang.ref.WeakReference */ class AdyenComponentView @JvmOverloads constructor( context: Context, - private val attrs: AttributeSet? = null, - private val defStyleAttr: Int = 0 + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 ) : LinearLayout(context, attrs, defStyleAttr) { @@ -120,7 +120,7 @@ class AdyenComponentView @JvmOverloads constructor( componentParams: ComponentParams, coroutineScope: CoroutineScope, ) { - val componentView = viewType.viewProvider.getView(viewType, context, attrs, defStyleAttr) + val componentView = viewType.viewProvider.getView(viewType, context) this.componentView = componentView val localizedContext = context.createLocalizedContext(componentParams.shopperLocale) @@ -148,7 +148,7 @@ class AdyenComponentView @JvmOverloads constructor( binding.frameLayoutButtonContainer.isVisible = buttonDelegate.shouldShowSubmitButton() val buttonView = (viewType as ButtonComponentViewType) - .buttonViewProvider.getButton(context, attrs, defStyleAttr) + .buttonViewProvider.getButton(context) buttonView.setText(viewType, componentParams, localizedContext) buttonView.setOnClickListener { buttonDelegate.onSubmit() diff --git a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/ButtonViewProvider.kt b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/ButtonViewProvider.kt index a9ab1f17c7..5b3a9620ee 100644 --- a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/ButtonViewProvider.kt +++ b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/ButtonViewProvider.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.ui.core.internal.ui import android.content.Context -import android.util.AttributeSet import androidx.annotation.RestrictTo import com.adyen.checkout.ui.core.internal.ui.view.DefaultPayButton import com.adyen.checkout.ui.core.internal.ui.view.PayButton @@ -18,13 +17,10 @@ import com.adyen.checkout.ui.core.internal.ui.view.PayButton interface ButtonViewProvider { fun getButton( context: Context, - attrs: AttributeSet?, - defStyleAttr: Int, ): PayButton } internal class DefaultButtonViewProvider : ButtonViewProvider { - override fun getButton(context: Context, attrs: AttributeSet?, defStyleAttr: Int): PayButton = - DefaultPayButton(context, attrs, defStyleAttr) + override fun getButton(context: Context): PayButton = DefaultPayButton(context) } diff --git a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/ViewProvider.kt b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/ViewProvider.kt index fc83875735..1c4a592f52 100644 --- a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/ViewProvider.kt +++ b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/ViewProvider.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.ui.core.internal.ui import android.content.Context -import android.util.AttributeSet import androidx.annotation.RestrictTo @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @@ -17,7 +16,5 @@ interface ViewProvider { fun getView( viewType: ComponentViewType, context: Context, - attrs: AttributeSet?, - defStyleAttr: Int ): ComponentView } diff --git a/upi/src/main/java/com/adyen/checkout/upi/internal/ui/UPIViewProvider.kt b/upi/src/main/java/com/adyen/checkout/upi/internal/ui/UPIViewProvider.kt index 5163320683..7f2f2563aa 100644 --- a/upi/src/main/java/com/adyen/checkout/upi/internal/ui/UPIViewProvider.kt +++ b/upi/src/main/java/com/adyen/checkout/upi/internal/ui/UPIViewProvider.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.upi.internal.ui import android.content.Context -import android.util.AttributeSet import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType import com.adyen.checkout.ui.core.internal.ui.ComponentView import com.adyen.checkout.ui.core.internal.ui.ComponentViewType @@ -22,10 +21,8 @@ internal object UPIViewProvider : ViewProvider { override fun getView( viewType: ComponentViewType, context: Context, - attrs: AttributeSet?, - defStyleAttr: Int ): ComponentView = when (viewType) { - UPIComponentViewType -> UPIView(context, attrs, defStyleAttr) + UPIComponentViewType -> UPIView(context) else -> throw IllegalArgumentException("Unsupported view type") } } diff --git a/voucher/src/main/java/com/adyen/checkout/voucher/internal/ui/VoucherViewProvider.kt b/voucher/src/main/java/com/adyen/checkout/voucher/internal/ui/VoucherViewProvider.kt index 7dae701da0..ef939226bb 100644 --- a/voucher/src/main/java/com/adyen/checkout/voucher/internal/ui/VoucherViewProvider.kt +++ b/voucher/src/main/java/com/adyen/checkout/voucher/internal/ui/VoucherViewProvider.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.voucher.internal.ui import android.content.Context -import android.util.AttributeSet import com.adyen.checkout.ui.core.internal.ui.ComponentView import com.adyen.checkout.ui.core.internal.ui.ComponentViewType import com.adyen.checkout.ui.core.internal.ui.ViewProvider @@ -21,12 +20,10 @@ internal object VoucherViewProvider : ViewProvider { override fun getView( viewType: ComponentViewType, context: Context, - attrs: AttributeSet?, - defStyleAttr: Int ): ComponentView { return when (viewType) { - VoucherComponentViewType.SIMPLE_VOUCHER -> VoucherView(context, attrs, defStyleAttr) - VoucherComponentViewType.FULL_VOUCHER -> FullVoucherView(context, attrs, defStyleAttr) + VoucherComponentViewType.SIMPLE_VOUCHER -> VoucherView(context) + VoucherComponentViewType.FULL_VOUCHER -> FullVoucherView(context) else -> throw IllegalArgumentException("Unsupported view type") } } diff --git a/wechatpay/src/main/java/com/adyen/checkout/wechatpay/internal/ui/WeChatViewProvider.kt b/wechatpay/src/main/java/com/adyen/checkout/wechatpay/internal/ui/WeChatViewProvider.kt index 469e41d121..e20e6c62f8 100644 --- a/wechatpay/src/main/java/com/adyen/checkout/wechatpay/internal/ui/WeChatViewProvider.kt +++ b/wechatpay/src/main/java/com/adyen/checkout/wechatpay/internal/ui/WeChatViewProvider.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.wechatpay.internal.ui import android.content.Context -import android.util.AttributeSet import com.adyen.checkout.ui.core.internal.ui.ComponentView import com.adyen.checkout.ui.core.internal.ui.ComponentViewType import com.adyen.checkout.ui.core.internal.ui.ViewProvider @@ -20,10 +19,8 @@ internal object WeChatViewProvider : ViewProvider { override fun getView( viewType: ComponentViewType, context: Context, - attrs: AttributeSet?, - defStyleAttr: Int ): ComponentView = when (viewType) { - WeChatComponentViewType -> PaymentInProgressView(context, attrs, defStyleAttr) + WeChatComponentViewType -> PaymentInProgressView(context) else -> throw IllegalArgumentException("Unsupported view type") } } From 97cead63acddb3bdee9107790975da60e7678c90 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 11:23:20 +0000 Subject: [PATCH 08/22] Update hilt_version to v2.48.1 --- dependencies.gradle | 2 +- gradle/verification-metadata.xml | 77 ++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 6794fcedaf..14503f6cd9 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -23,7 +23,7 @@ ext { kotlin_version = '1.9.10' detekt_gradle_plugin_version = "1.23.1" dokka_version = "1.9.0" - hilt_version = "2.48" + hilt_version = "2.48.1" compose_compiler_version = '1.5.3' // Code quality diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 1f2344ff5d..68e710e873 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -4133,6 +4133,14 @@ + + + + + + + + @@ -4157,6 +4165,14 @@ + + + + + + + + @@ -4181,6 +4197,14 @@ + + + + + + + + @@ -4205,6 +4229,14 @@ + + + + + + + + @@ -4229,6 +4261,14 @@ + + + + + + + + @@ -4253,6 +4293,14 @@ + + + + + + + + @@ -4277,6 +4325,14 @@ + + + + + + + + @@ -4301,6 +4357,14 @@ + + + + + + + + @@ -4325,6 +4389,14 @@ + + + + + + + + @@ -4340,6 +4412,11 @@ + + + + + From f60f850abc7d5db286ca8e77d3a314ca10023cc1 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 1 Nov 2023 17:51:38 +0100 Subject: [PATCH 09/22] Override onCancel for gift card fragment This fixes an issue where drop-in is not terminated after the user swipes the bottom sheet down. Causing them to be sort of stuck. COAND-817 --- .../dropin/internal/ui/GiftCardComponentDialogFragment.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GiftCardComponentDialogFragment.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GiftCardComponentDialogFragment.kt index 3b6af93bb2..7a5c0351ed 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GiftCardComponentDialogFragment.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GiftCardComponentDialogFragment.kt @@ -8,6 +8,7 @@ package com.adyen.checkout.dropin.internal.ui +import android.content.DialogInterface import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -129,6 +130,12 @@ internal class GiftCardComponentDialogFragment : DropInBottomSheetDialogFragment protocol.showError(null, getString(R.string.component_error), componentError.errorMessage, true) } + override fun onCancel(dialog: DialogInterface) { + super.onCancel(dialog) + Logger.d(TAG, "onCancel") + protocol.terminateDropIn() + } + override fun onBackPressed(): Boolean { return performBackAction() } From 6938ec910c9775d93a86711ce79de713afab1e48 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Tue, 7 Nov 2023 16:48:43 +0100 Subject: [PATCH 10/22] Move onCancel to base fragment The onCancel implementation was duplicated in multiple fragments, so it's now generalized in one place. Never thought I would do this with base fragments, but I guess it makes sense. COAND-817 --- .../dropin/internal/ui/ActionComponentDialogFragment.kt | 1 - .../dropin/internal/ui/BaseComponentDialogFragment.kt | 7 ------- .../dropin/internal/ui/DropInBottomSheetDialogFragment.kt | 6 ++++++ .../dropin/internal/ui/GiftCardComponentDialogFragment.kt | 7 ------- .../ui/GiftCardPaymentConfirmationDialogFragment.kt | 7 ------- .../dropin/internal/ui/GooglePayComponentDialogFragment.kt | 7 ------- .../dropin/internal/ui/PaymentMethodListDialogFragment.kt | 7 ------- .../internal/ui/PreselectedStoredPaymentMethodFragment.kt | 7 ------- 8 files changed, 6 insertions(+), 43 deletions(-) diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/ActionComponentDialogFragment.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/ActionComponentDialogFragment.kt index 591db6cd23..a83b0ffa44 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/ActionComponentDialogFragment.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/ActionComponentDialogFragment.kt @@ -143,7 +143,6 @@ internal class ActionComponentDialogFragment : } override fun onCancel(dialog: DialogInterface) { - super.onCancel(dialog) Logger.d(TAG, "onCancel") if (shouldFinishWithAction()) { protocol.finishWithAction() diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/BaseComponentDialogFragment.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/BaseComponentDialogFragment.kt index 940bd88e30..fa9297085c 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/BaseComponentDialogFragment.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/BaseComponentDialogFragment.kt @@ -8,7 +8,6 @@ package com.adyen.checkout.dropin.internal.ui -import android.content.DialogInterface import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -135,12 +134,6 @@ internal abstract class BaseComponentDialogFragment : } } - override fun onCancel(dialog: DialogInterface) { - super.onCancel(dialog) - Logger.d(TAG, "onCancel") - protocol.terminateDropIn() - } - override fun onSubmit(state: PaymentComponentState<*>) { startPayment(state) } diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInBottomSheetDialogFragment.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInBottomSheetDialogFragment.kt index 69af3e5066..e1ce1d29a3 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInBottomSheetDialogFragment.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInBottomSheetDialogFragment.kt @@ -10,6 +10,7 @@ package com.adyen.checkout.dropin.internal.ui import android.app.Dialog import android.content.Context +import android.content.DialogInterface import android.os.Bundle import android.view.KeyEvent import android.widget.FrameLayout @@ -81,6 +82,11 @@ internal abstract class DropInBottomSheetDialogFragment : BottomSheetDialogFragm return false } + override fun onCancel(dialog: DialogInterface) { + Logger.d(TAG, "onCancel") + protocol.terminateDropIn() + } + companion object { private val TAG = LogUtil.getTag() } diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GiftCardComponentDialogFragment.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GiftCardComponentDialogFragment.kt index 7a5c0351ed..3b6af93bb2 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GiftCardComponentDialogFragment.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GiftCardComponentDialogFragment.kt @@ -8,7 +8,6 @@ package com.adyen.checkout.dropin.internal.ui -import android.content.DialogInterface import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -130,12 +129,6 @@ internal class GiftCardComponentDialogFragment : DropInBottomSheetDialogFragment protocol.showError(null, getString(R.string.component_error), componentError.errorMessage, true) } - override fun onCancel(dialog: DialogInterface) { - super.onCancel(dialog) - Logger.d(TAG, "onCancel") - protocol.terminateDropIn() - } - override fun onBackPressed(): Boolean { return performBackAction() } diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GiftCardPaymentConfirmationDialogFragment.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GiftCardPaymentConfirmationDialogFragment.kt index 71b0a5b5d0..87d0bcb6b2 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GiftCardPaymentConfirmationDialogFragment.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GiftCardPaymentConfirmationDialogFragment.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.dropin.internal.ui import android.content.Context -import android.content.DialogInterface import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -100,12 +99,6 @@ internal class GiftCardPaymentConfirmationDialogFragment : DropInBottomSheetDial } } - override fun onCancel(dialog: DialogInterface) { - super.onCancel(dialog) - Logger.d(TAG, "onCancel") - protocol.terminateDropIn() - } - override fun onBackPressed(): Boolean { return performBackAction() } diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GooglePayComponentDialogFragment.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GooglePayComponentDialogFragment.kt index 73269b9eb8..ce94336c0d 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GooglePayComponentDialogFragment.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GooglePayComponentDialogFragment.kt @@ -8,7 +8,6 @@ package com.adyen.checkout.dropin.internal.ui -import android.content.DialogInterface import android.content.Intent import android.os.Bundle import android.view.LayoutInflater @@ -114,12 +113,6 @@ internal class GooglePayComponentDialogFragment : return performBackAction() } - override fun onCancel(dialog: DialogInterface) { - super.onCancel(dialog) - Logger.d(TAG, "onCancel") - protocol.terminateDropIn() - } - private fun handleError(componentError: ComponentError) { Logger.e(TAG, componentError.errorMessage) // TODO find a way to show an error dialog unless the payment is cancelled by the user diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/PaymentMethodListDialogFragment.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/PaymentMethodListDialogFragment.kt index 789c3ccd13..de7ce56339 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/PaymentMethodListDialogFragment.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/PaymentMethodListDialogFragment.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.dropin.internal.ui import android.content.Context -import android.content.DialogInterface import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -143,12 +142,6 @@ internal class PaymentMethodListDialogFragment : _binding = null } - override fun onCancel(dialog: DialogInterface) { - super.onCancel(dialog) - Logger.d(TAG, "onCancel") - protocol.terminateDropIn() - } - override fun onBackPressed(): Boolean { if (dropInViewModel.shouldShowPreselectedStored()) { protocol.showPreselectedDialog() diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/PreselectedStoredPaymentMethodFragment.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/PreselectedStoredPaymentMethodFragment.kt index 4981502526..1bcc7bbf13 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/PreselectedStoredPaymentMethodFragment.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/PreselectedStoredPaymentMethodFragment.kt @@ -8,7 +8,6 @@ package com.adyen.checkout.dropin.internal.ui -import android.content.DialogInterface import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -226,12 +225,6 @@ internal class PreselectedStoredPaymentMethodFragment : DropInBottomSheetDialogF .show() } - override fun onCancel(dialog: DialogInterface) { - super.onCancel(dialog) - Logger.d(TAG, "onCancel") - protocol.terminateDropIn() - } - override fun onDestroyView() { _binding = null super.onDestroyView() From 1f7789e55c77353850fa7cccc42b1304177b3ec8 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Thu, 9 Nov 2023 16:25:32 +0100 Subject: [PATCH 11/22] Give action-core another namespace It used the same namespace as action, so it would generate a duplicate BuildConfig class. Ultimately failing the build when implementing the action module. COAND-820 --- action-core/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action-core/build.gradle b/action-core/build.gradle index a668c0c9e5..eff8347065 100644 --- a/action-core/build.gradle +++ b/action-core/build.gradle @@ -19,7 +19,7 @@ ext.mavenArtifactDescription = "Adyen Checkout Action Core module." apply from: "${rootDir}/config/gradle/sharedTasks.gradle" android { - namespace 'com.adyen.checkout.action' + namespace 'com.adyen.checkout.action.core' compileSdkVersion compile_sdk_version defaultConfig { From 53f08f1867aa3d78a778957952d342ad2ac5445b Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 8 Nov 2023 12:59:44 +0100 Subject: [PATCH 12/22] Replace version number --- README.md | 10 +++++----- .../core/internal/data/api/AnalyticsMapperTest.kt | 2 +- dependencies.gradle | 2 +- example-app/build.gradle | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 7c221a98bb..ce91bc2ff9 100644 --- a/README.md +++ b/README.md @@ -31,23 +31,23 @@ Import the corresponding module in your `build.gradle` file. For Drop-in: ```groovy -implementation "com.adyen.checkout:drop-in-compose:5.0.0" +implementation "com.adyen.checkout:drop-in-compose:5.0.1" ``` For the Credit Card component: ```groovy -implementation "com.adyen.checkout:card:5.0.0" -implementation "com.adyen.checkout:components-compose:5.0.0" +implementation "com.adyen.checkout:card:5.0.1" +implementation "com.adyen.checkout:components-compose:5.0.1" ``` ### Without Jetpack Compose For Drop-in: ```groovy -implementation "com.adyen.checkout:drop-in:5.0.0" +implementation "com.adyen.checkout:drop-in:5.0.1" ``` For the Credit Card component: ```groovy -implementation "com.adyen.checkout:card:5.0.0" +implementation "com.adyen.checkout:card:5.0.1" ``` The library is available on [Maven Central][mavenRepo]. diff --git a/components-core/src/test/java/com/adyen/checkout/components/core/internal/data/api/AnalyticsMapperTest.kt b/components-core/src/test/java/com/adyen/checkout/components/core/internal/data/api/AnalyticsMapperTest.kt index e5ec977831..2a9670e933 100644 --- a/components-core/src/test/java/com/adyen/checkout/components/core/internal/data/api/AnalyticsMapperTest.kt +++ b/components-core/src/test/java/com/adyen/checkout/components/core/internal/data/api/AnalyticsMapperTest.kt @@ -110,7 +110,7 @@ internal class AnalyticsMapperTest { ) val expected = AnalyticsSetupRequest( - version = "5.0.0", + version = "5.0.1", channel = "android", platform = "android", locale = "en_US", diff --git a/dependencies.gradle b/dependencies.gradle index 14503f6cd9..a8ac4251d1 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -16,7 +16,7 @@ ext { // just for example app, don't need to increment version_code = 1 // The version_name format is "major.minor.patch(-(alpha|beta|rc)[0-9]{2}){0,1}" (e.g. 3.0.0, 3.1.1-alpha04 or 3.1.4-rc01 etc). - version_name = "5.0.0" + version_name = "5.0.1" // Build Script android_gradle_plugin_version = '8.1.1' diff --git a/example-app/build.gradle b/example-app/build.gradle index 8274070c5c..c5c8a69457 100644 --- a/example-app/build.gradle +++ b/example-app/build.gradle @@ -62,7 +62,7 @@ android { dependencies { // Checkout implementation project(':drop-in') -// implementation "com.adyen.checkout:drop-in:5.0.0" +// implementation "com.adyen.checkout:drop-in:5.0.1" // Dependencies implementation libraries.kotlinCoroutines From 1aa78c474d7a16e2bd7c5da4be383f9d82c30aee Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 8 Nov 2023 13:11:26 +0100 Subject: [PATCH 13/22] Update release notes --- RELEASE_NOTES.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 4ebae3c15c..5ba0f46de0 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -10,3 +10,12 @@ ## Fixed - `@RestrictTo` annotations no longer cause false errors with Android Studio and Lint. +- Using the layout inspector or having view attribute inspection enabled in the developer options no longer causes a crash when viewing a payment method. +- Implementing the `:action` module no longer gives a duplicate class error caused by a duplicate namespace. +- For Drop-in, dismissing the gift card payment method no longer prevents further interaction. + +## Changed +- Dependency versions: + | Name | Version | + |--------------------------------------------------------------------------------------------------------|-------------------------------| + | [AndroidX Compose BoM](https://developer.android.com/jetpack/compose/bom/bom-mapping) | **2023.09.01** | From bb651ed13f9dcb93aeebffe1a830ac91e5365e5f Mon Sep 17 00:00:00 2001 From: ozgur <6615094+ozgur00@users.noreply.github.com> Date: Tue, 5 Sep 2023 13:46:06 +0200 Subject: [PATCH 14/22] Refactor bcmc to inherit card component COAND-795 --- .../adyen/checkout/bcmc/BcmcComponentNew.kt | 33 +++ .../provider/BcmcComponentProviderNew.kt | 274 ++++++++++++++++++ .../bcmc/internal/ui/BcmcViewProvider.kt | 4 +- .../ui/model/BcmcComponentParamsMapperNew.kt | 86 ++++++ .../com/adyen/checkout/card/CardComponent.kt | 6 +- .../adyen/checkout/card/CardComponentState.kt | 8 +- .../internal/data/api/BinLookupService.kt | 2 +- .../api/DefaultDetectCardTypeRepository.kt | 16 +- .../data/api/DetectCardTypeRepository.kt | 3 +- .../internal/data/model/BinLookupRequest.kt | 10 +- .../internal/data/model/BinLookupResponse.kt | 2 +- .../internal/data/model/DetectedCardType.kt | 2 +- .../checkout/card/internal/ui/CardDelegate.kt | 2 +- .../card/internal/ui/DefaultCardDelegate.kt | 5 +- .../internal/ui/model/CardComponentParams.kt | 2 +- .../card/internal/ui/model/CardInputData.kt | 2 +- .../card/internal/ui/model/CardListItem.kt | 2 +- .../card/internal/ui/model/CardOutputData.kt | 2 +- .../internal/ui/model/InputFieldUIState.kt | 2 +- .../internal/ui/model/InstallmentOption.kt | 2 +- .../ui/model/InstallmentOptionParams.kt | 2 +- .../internal/ui/model/InstallmentParams.kt | 2 +- .../card/internal/ui/view/CardView.kt | 2 +- .../ui/view/InstallmentListAdapter.kt | 2 +- .../data/api/TestDetectCardTypeRepository.kt | 1 + .../provider/ComponentParsingProvider.kt | 13 +- 26 files changed, 446 insertions(+), 41 deletions(-) create mode 100644 bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcComponentNew.kt create mode 100644 bcmc/src/main/java/com/adyen/checkout/bcmc/internal/provider/BcmcComponentProviderNew.kt create mode 100644 bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperNew.kt diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcComponentNew.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcComponentNew.kt new file mode 100644 index 0000000000..752cfea507 --- /dev/null +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcComponentNew.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by ozgur on 22/8/2023. + */ + +package com.adyen.checkout.bcmc + +import com.adyen.checkout.action.core.internal.DefaultActionHandlingComponent +import com.adyen.checkout.action.core.internal.ui.GenericActionDelegate +import com.adyen.checkout.bcmc.internal.provider.BcmcComponentProviderNew +import com.adyen.checkout.card.CardComponent +import com.adyen.checkout.card.CardComponentState +import com.adyen.checkout.card.internal.ui.CardDelegate +import com.adyen.checkout.components.core.PaymentMethodTypes +import com.adyen.checkout.components.core.internal.ComponentEventHandler + +class BcmcComponentNew( + private val cardDelegate: CardDelegate, + private val genericActionDelegate: GenericActionDelegate, + private val actionHandlingComponent: DefaultActionHandlingComponent, + internal val componentEventHandler: ComponentEventHandler, +) : CardComponent(cardDelegate, genericActionDelegate, actionHandlingComponent, componentEventHandler) { + companion object { + @JvmField + val PROVIDER = BcmcComponentProviderNew() + + @JvmField + val PAYMENT_METHOD_TYPES = listOf(PaymentMethodTypes.BCMC) + } +} diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/provider/BcmcComponentProviderNew.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/provider/BcmcComponentProviderNew.kt new file mode 100644 index 0000000000..1d30c62837 --- /dev/null +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/provider/BcmcComponentProviderNew.kt @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2023 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by ozgur on 22/8/2023. + */ + +package com.adyen.checkout.bcmc.internal.provider + +import android.app.Application +import androidx.annotation.RestrictTo +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelStoreOwner +import androidx.savedstate.SavedStateRegistryOwner +import com.adyen.checkout.action.core.internal.DefaultActionHandlingComponent +import com.adyen.checkout.action.core.internal.provider.GenericActionComponentProvider +import com.adyen.checkout.bcmc.BcmcComponentNew +import com.adyen.checkout.bcmc.BcmcConfiguration +import com.adyen.checkout.bcmc.internal.ui.model.BcmcComponentParamsMapperNew +import com.adyen.checkout.card.CardComponentState +import com.adyen.checkout.card.internal.data.api.BinLookupService +import com.adyen.checkout.card.internal.data.api.DefaultDetectCardTypeRepository +import com.adyen.checkout.card.internal.ui.CardValidationMapper +import com.adyen.checkout.card.internal.ui.DefaultCardDelegate +import com.adyen.checkout.components.core.ComponentCallback +import com.adyen.checkout.components.core.Order +import com.adyen.checkout.components.core.PaymentMethod +import com.adyen.checkout.components.core.internal.DefaultComponentEventHandler +import com.adyen.checkout.components.core.internal.PaymentObserverRepository +import com.adyen.checkout.components.core.internal.data.api.AnalyticsMapper +import com.adyen.checkout.components.core.internal.data.api.AnalyticsRepository +import com.adyen.checkout.components.core.internal.data.api.AnalyticsRepositoryData +import com.adyen.checkout.components.core.internal.data.api.AnalyticsService +import com.adyen.checkout.components.core.internal.data.api.DefaultAnalyticsRepository +import com.adyen.checkout.components.core.internal.data.api.DefaultPublicKeyRepository +import com.adyen.checkout.components.core.internal.data.api.PublicKeyService +import com.adyen.checkout.components.core.internal.provider.PaymentComponentProvider +import com.adyen.checkout.components.core.internal.ui.model.ComponentParams +import com.adyen.checkout.components.core.internal.ui.model.SessionParams +import com.adyen.checkout.components.core.internal.util.get +import com.adyen.checkout.components.core.internal.util.viewModelFactory +import com.adyen.checkout.core.exception.ComponentException +import com.adyen.checkout.core.internal.data.api.HttpClientFactory +import com.adyen.checkout.cse.internal.ClientSideEncrypter +import com.adyen.checkout.cse.internal.DateGenerator +import com.adyen.checkout.cse.internal.DefaultCardEncrypter +import com.adyen.checkout.cse.internal.DefaultGenericEncrypter +import com.adyen.checkout.sessions.core.CheckoutSession +import com.adyen.checkout.sessions.core.SessionComponentCallback +import com.adyen.checkout.sessions.core.internal.SessionComponentEventHandler +import com.adyen.checkout.sessions.core.internal.SessionInteractor +import com.adyen.checkout.sessions.core.internal.SessionSavedStateHandleContainer +import com.adyen.checkout.sessions.core.internal.data.api.SessionRepository +import com.adyen.checkout.sessions.core.internal.data.api.SessionService +import com.adyen.checkout.sessions.core.internal.provider.SessionPaymentComponentProvider +import com.adyen.checkout.sessions.core.internal.ui.model.SessionParamsFactory +import com.adyen.checkout.ui.core.internal.data.api.AddressService +import com.adyen.checkout.ui.core.internal.data.api.DefaultAddressRepository +import com.adyen.checkout.ui.core.internal.ui.SubmitHandler + +class BcmcComponentProviderNew +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +constructor( + overrideComponentParams: ComponentParams? = null, + overrideSessionParams: SessionParams? = null, + private val analyticsRepository: AnalyticsRepository? = null, +) : + PaymentComponentProvider< + BcmcComponentNew, + BcmcConfiguration, + CardComponentState, + ComponentCallback + >, + SessionPaymentComponentProvider< + BcmcComponentNew, + BcmcConfiguration, + CardComponentState, + SessionComponentCallback + > { + + private val componentParamsMapper = BcmcComponentParamsMapperNew(overrideComponentParams, overrideSessionParams) + + @Suppress("LongMethod") + override fun get( + savedStateRegistryOwner: SavedStateRegistryOwner, + viewModelStoreOwner: ViewModelStoreOwner, + lifecycleOwner: LifecycleOwner, + paymentMethod: PaymentMethod, + configuration: BcmcConfiguration, + application: Application, + componentCallback: ComponentCallback, + order: Order?, + key: String?, + ): BcmcComponentNew { + assertSupported(paymentMethod) + + val componentParams = componentParamsMapper.mapToParams(configuration, null) + val httpClient = HttpClientFactory.getHttpClient(componentParams.environment) + val publicKeyService = PublicKeyService(httpClient) + val publicKeyRepository = DefaultPublicKeyRepository(publicKeyService) + val cardValidationMapper = CardValidationMapper() + val dateGenerator = DateGenerator() + val clientSideEncrypter = ClientSideEncrypter() + val genericEncrypter = DefaultGenericEncrypter(clientSideEncrypter, dateGenerator) + val cardEncrypter = DefaultCardEncrypter(genericEncrypter) + val addressService = AddressService(httpClient) + val addressRepository = DefaultAddressRepository(addressService) + val binLookupService = BinLookupService(httpClient) + val detectCardTypeRepository = DefaultDetectCardTypeRepository(cardEncrypter, binLookupService) + + val analyticsRepository = analyticsRepository ?: DefaultAnalyticsRepository( + analyticsRepositoryData = AnalyticsRepositoryData( + application = application, + componentParams = componentParams, + paymentMethod = paymentMethod, + ), + analyticsService = AnalyticsService( + HttpClientFactory.getAnalyticsHttpClient(componentParams.environment) + ), + analyticsMapper = AnalyticsMapper(), + ) + + val bcmcFactory = viewModelFactory(savedStateRegistryOwner, null) { savedStateHandle -> + val cardDelegate = DefaultCardDelegate( + observerRepository = PaymentObserverRepository(), + publicKeyRepository = publicKeyRepository, + componentParams = componentParams, + paymentMethod = paymentMethod, + order = order, + analyticsRepository = analyticsRepository, + addressRepository = addressRepository, + detectCardTypeRepository = detectCardTypeRepository, + cardValidationMapper = cardValidationMapper, + cardEncrypter = cardEncrypter, + genericEncrypter = genericEncrypter, + submitHandler = SubmitHandler(savedStateHandle) + ) + + val genericActionDelegate = GenericActionComponentProvider(componentParams).getDelegate( + configuration = configuration.genericActionConfiguration, + savedStateHandle = savedStateHandle, + application = application, + ) + + BcmcComponentNew( + cardDelegate = cardDelegate, + genericActionDelegate = genericActionDelegate, + actionHandlingComponent = DefaultActionHandlingComponent(genericActionDelegate, cardDelegate), + componentEventHandler = DefaultComponentEventHandler(), + ) + } + return ViewModelProvider( + viewModelStoreOwner, + bcmcFactory + )[key, BcmcComponentNew::class.java].also { component -> + component.observe(lifecycleOwner) { + component.componentEventHandler.onPaymentComponentEvent(it, componentCallback) + } + } + } + + @Suppress("LongMethod") + override fun get( + savedStateRegistryOwner: SavedStateRegistryOwner, + viewModelStoreOwner: ViewModelStoreOwner, + lifecycleOwner: LifecycleOwner, + checkoutSession: CheckoutSession, + paymentMethod: PaymentMethod, + configuration: BcmcConfiguration, + application: Application, + componentCallback: SessionComponentCallback, + key: String? + ): BcmcComponentNew { + assertSupported(paymentMethod) + + val componentParams = componentParamsMapper.mapToParams( + bcmcConfiguration = configuration, + sessionParams = SessionParamsFactory.create(checkoutSession) + ) + val httpClient = HttpClientFactory.getHttpClient(componentParams.environment) + val publicKeyService = PublicKeyService(httpClient) + val publicKeyRepository = DefaultPublicKeyRepository(publicKeyService) + val cardValidationMapper = CardValidationMapper() + val dateGenerator = DateGenerator() + val clientSideEncrypter = ClientSideEncrypter() + val genericEncrypter = DefaultGenericEncrypter(clientSideEncrypter, dateGenerator) + val cardEncrypter = DefaultCardEncrypter(genericEncrypter) + val addressService = AddressService(httpClient) + val addressRepository = DefaultAddressRepository(addressService) + val binLookupService = BinLookupService(httpClient) + val detectCardTypeRepository = DefaultDetectCardTypeRepository(cardEncrypter, binLookupService) + + val analyticsRepository = analyticsRepository ?: DefaultAnalyticsRepository( + analyticsRepositoryData = AnalyticsRepositoryData( + application = application, + componentParams = componentParams, + paymentMethod = paymentMethod, + ), + analyticsService = AnalyticsService( + HttpClientFactory.getAnalyticsHttpClient(componentParams.environment) + ), + analyticsMapper = AnalyticsMapper(), + ) + + val bcmcFactory = viewModelFactory(savedStateRegistryOwner, null) { savedStateHandle -> + val cardDelegate = DefaultCardDelegate( + observerRepository = PaymentObserverRepository(), + publicKeyRepository = publicKeyRepository, + componentParams = componentParams, + paymentMethod = paymentMethod, + order = checkoutSession.order, + analyticsRepository = analyticsRepository, + addressRepository = addressRepository, + detectCardTypeRepository = detectCardTypeRepository, + cardValidationMapper = cardValidationMapper, + cardEncrypter = cardEncrypter, + genericEncrypter = genericEncrypter, + submitHandler = SubmitHandler(savedStateHandle) + ) + + val genericActionDelegate = GenericActionComponentProvider(componentParams).getDelegate( + configuration = configuration.genericActionConfiguration, + savedStateHandle = savedStateHandle, + application = application, + ) + + val sessionSavedStateHandleContainer = SessionSavedStateHandleContainer( + savedStateHandle = savedStateHandle, + checkoutSession = checkoutSession, + ) + + val sessionInteractor = SessionInteractor( + sessionRepository = SessionRepository( + sessionService = SessionService(httpClient), + clientKey = componentParams.clientKey, + ), + sessionModel = sessionSavedStateHandleContainer.getSessionModel(), + isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false + ) + + val sessionComponentEventHandler = SessionComponentEventHandler( + sessionInteractor = sessionInteractor, + sessionSavedStateHandleContainer = sessionSavedStateHandleContainer, + ) + + BcmcComponentNew( + cardDelegate = cardDelegate, + genericActionDelegate = genericActionDelegate, + actionHandlingComponent = DefaultActionHandlingComponent(genericActionDelegate, cardDelegate), + componentEventHandler = sessionComponentEventHandler, + ) + } + return ViewModelProvider( + viewModelStoreOwner, + bcmcFactory + )[key, BcmcComponentNew::class.java].also { component -> + component.observe(lifecycleOwner) { + component.componentEventHandler.onPaymentComponentEvent(it, componentCallback) + } + } + } + + private fun assertSupported(paymentMethod: PaymentMethod) { + if (!isPaymentMethodSupported(paymentMethod)) { + throw ComponentException("Unsupported payment method ${paymentMethod.type}") + } + } + + override fun isPaymentMethodSupported(paymentMethod: PaymentMethod): Boolean { + return BcmcComponentNew.PAYMENT_METHOD_TYPES.contains(paymentMethod.type) + } +} diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/BcmcViewProvider.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/BcmcViewProvider.kt index ad42c0230f..ed7b663145 100644 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/BcmcViewProvider.kt +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/BcmcViewProvider.kt @@ -9,7 +9,7 @@ package com.adyen.checkout.bcmc.internal.ui import android.content.Context -import com.adyen.checkout.bcmc.internal.ui.view.BcmcView +import com.adyen.checkout.card.internal.ui.view.CardView import com.adyen.checkout.ui.core.internal.ui.AmountButtonComponentViewType import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType import com.adyen.checkout.ui.core.internal.ui.ComponentView @@ -22,7 +22,7 @@ internal object BcmcViewProvider : ViewProvider { viewType: ComponentViewType, context: Context, ): ComponentView = when (viewType) { - BcmcComponentViewType -> BcmcView(context) + BcmcComponentViewType -> CardView(context) else -> throw IllegalArgumentException("Unsupported view type") } } diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperNew.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperNew.kt new file mode 100644 index 0000000000..b8c60fd305 --- /dev/null +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperNew.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2023 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by ozgur on 22/8/2023. + */ + +package com.adyen.checkout.bcmc.internal.ui.model + +import com.adyen.checkout.bcmc.BcmcConfiguration +import com.adyen.checkout.card.CardBrand +import com.adyen.checkout.card.CardType +import com.adyen.checkout.card.KCPAuthVisibility +import com.adyen.checkout.card.SocialSecurityNumberVisibility +import com.adyen.checkout.card.internal.ui.model.CardComponentParams +import com.adyen.checkout.components.core.internal.ui.model.AnalyticsParams +import com.adyen.checkout.components.core.internal.ui.model.ComponentParams +import com.adyen.checkout.components.core.internal.ui.model.SessionParams +import com.adyen.checkout.ui.core.internal.ui.model.AddressParams + +internal class BcmcComponentParamsMapperNew( + private val overrideComponentParams: ComponentParams?, + private val overrideSessionParams: SessionParams?, +) { + + fun mapToParams( + bcmcConfiguration: BcmcConfiguration, + sessionParams: SessionParams?, + ): CardComponentParams { + return bcmcConfiguration + .mapToParamsInternal() + .override(overrideComponentParams) + .override(sessionParams ?: overrideSessionParams) + } + + private fun BcmcConfiguration.mapToParamsInternal(): CardComponentParams { + return CardComponentParams( + shopperLocale = shopperLocale, + environment = environment, + clientKey = clientKey, + analyticsParams = AnalyticsParams(analyticsConfiguration), + isCreatedByDropIn = false, + amount = amount, + isSubmitButtonVisible = isSubmitButtonVisible ?: true, + isHolderNameRequired = isHolderNameRequired ?: false, + shopperReference = shopperReference, + isStorePaymentFieldVisible = isStorePaymentFieldVisible ?: false, + addressParams = AddressParams.None, + installmentParams = null, + isHideCvc = true, + isHideCvcStoredCard = true, + kcpAuthVisibility = KCPAuthVisibility.HIDE, + socialSecurityNumberVisibility = SocialSecurityNumberVisibility.HIDE, + supportedCardBrands = listOf( + CardBrand(cardType = CardType.BCMC), + CardBrand(cardType = CardType.MAESTRO), + CardBrand(cardType = CardType.VISA) + ) + ) + } + + private fun CardComponentParams.override( + overrideComponentParams: ComponentParams? + ): CardComponentParams { + if (overrideComponentParams == null) return this + return copy( + shopperLocale = overrideComponentParams.shopperLocale, + environment = overrideComponentParams.environment, + clientKey = overrideComponentParams.clientKey, + analyticsParams = overrideComponentParams.analyticsParams, + isCreatedByDropIn = overrideComponentParams.isCreatedByDropIn, + amount = overrideComponentParams.amount, + ) + } + + private fun CardComponentParams.override( + sessionParams: SessionParams? = null + ): CardComponentParams { + if (sessionParams == null) return this + return copy( + isStorePaymentFieldVisible = sessionParams.enableStoreDetails ?: isStorePaymentFieldVisible, + amount = sessionParams.amount ?: amount, + ) + } +} diff --git a/card/src/main/java/com/adyen/checkout/card/CardComponent.kt b/card/src/main/java/com/adyen/checkout/card/CardComponent.kt index 699046e406..ac55afcee3 100644 --- a/card/src/main/java/com/adyen/checkout/card/CardComponent.kt +++ b/card/src/main/java/com/adyen/checkout/card/CardComponent.kt @@ -34,7 +34,7 @@ import kotlinx.coroutines.flow.Flow /** * A [PaymentComponent] that supports the [PaymentMethodTypes.SCHEME] payment method. */ -class CardComponent internal constructor( +open class CardComponent constructor( private val cardDelegate: CardDelegate, private val genericActionDelegate: GenericActionDelegate, private val actionHandlingComponent: DefaultActionHandlingComponent, @@ -60,7 +60,7 @@ class CardComponent internal constructor( componentEventHandler.initialize(viewModelScope) } - internal fun observe( + fun observe( lifecycleOwner: LifecycleOwner, callback: (PaymentComponentEvent) -> Unit ) { @@ -73,7 +73,7 @@ class CardComponent internal constructor( ) } - internal fun removeObserver() { + fun removeObserver() { cardDelegate.removeObserver() genericActionDelegate.removeObserver() } diff --git a/card/src/main/java/com/adyen/checkout/card/CardComponentState.kt b/card/src/main/java/com/adyen/checkout/card/CardComponentState.kt index 71b85bf368..568ad195c5 100644 --- a/card/src/main/java/com/adyen/checkout/card/CardComponentState.kt +++ b/card/src/main/java/com/adyen/checkout/card/CardComponentState.kt @@ -14,11 +14,11 @@ import com.adyen.checkout.components.core.paymentmethod.CardPaymentMethod /** * Represents the state of [CardComponent]. */ -data class CardComponentState( +open class CardComponentState( override val data: PaymentComponentData, override val isInputValid: Boolean, override val isReady: Boolean, - val cardBrand: CardBrand?, - val binValue: String, - val lastFourDigits: String?, + open val cardBrand: CardBrand?, + open val binValue: String, + open val lastFourDigits: String?, ) : PaymentComponentState diff --git a/card/src/main/java/com/adyen/checkout/card/internal/data/api/BinLookupService.kt b/card/src/main/java/com/adyen/checkout/card/internal/data/api/BinLookupService.kt index e479d517fa..2c5c2125ee 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/data/api/BinLookupService.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/data/api/BinLookupService.kt @@ -15,7 +15,7 @@ import com.adyen.checkout.core.internal.data.api.post import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -internal class BinLookupService( +class BinLookupService( private val httpClient: HttpClient, ) { diff --git a/card/src/main/java/com/adyen/checkout/card/internal/data/api/DefaultDetectCardTypeRepository.kt b/card/src/main/java/com/adyen/checkout/card/internal/data/api/DefaultDetectCardTypeRepository.kt index 25f7e55c92..eb0fed66a1 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/data/api/DefaultDetectCardTypeRepository.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/data/api/DefaultDetectCardTypeRepository.kt @@ -28,7 +28,7 @@ import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import java.util.UUID -internal class DefaultDetectCardTypeRepository( +class DefaultDetectCardTypeRepository( private val cardEncrypter: BaseCardEncrypter, private val binLookupService: BinLookupService, ) : DetectCardTypeRepository { @@ -45,6 +45,7 @@ internal class DefaultDetectCardTypeRepository( supportedCardBrands: List, clientKey: String, coroutineScope: CoroutineScope, + type: String? ) { Logger.d(TAG, "detectCardType") if (shouldFetchReliableTypes(cardNumber)) { @@ -64,7 +65,8 @@ internal class DefaultDetectCardTypeRepository( publicKey, supportedCardBrands, clientKey, - coroutineScope + coroutineScope, + type ) } } @@ -79,6 +81,7 @@ internal class DefaultDetectCardTypeRepository( supportedCardBrands: List, clientKey: String, coroutineScope: CoroutineScope, + type: String? ) { if (publicKey != null) { Logger.d(TAG, "Launching Bin Lookup") @@ -89,7 +92,8 @@ internal class DefaultDetectCardTypeRepository( cardNumber, publicKey, supportedCardBrands, - clientKey + clientKey, + type )?.let { _detectedCardTypesFlow.send(it) } @@ -139,10 +143,11 @@ internal class DefaultDetectCardTypeRepository( publicKey: String, supportedCardBrands: List, clientKey: String, + type: String? ): List? { val key = hashBin(cardNumber) cachedBinLookup[key] = BinLookupResult.Loading - val binLookupResponse = makeBinLookup(cardNumber, publicKey, supportedCardBrands, clientKey) + val binLookupResponse = makeBinLookup(cardNumber, publicKey, supportedCardBrands, clientKey, type) return if (binLookupResponse == null) { cachedBinLookup.remove(key) @@ -159,11 +164,12 @@ internal class DefaultDetectCardTypeRepository( publicKey: String, supportedCardBrands: List, clientKey: String, + type: String? ): BinLookupResponse? { return runSuspendCatching { val encryptedBin = cardEncrypter.encryptBin(cardNumber, publicKey) val cardBrands = supportedCardBrands.map { it.txVariant } - val request = BinLookupRequest(encryptedBin, UUID.randomUUID().toString(), cardBrands) + val request = BinLookupRequest(encryptedBin, UUID.randomUUID().toString(), cardBrands, type) binLookupService.makeBinLookup( request = request, diff --git a/card/src/main/java/com/adyen/checkout/card/internal/data/api/DetectCardTypeRepository.kt b/card/src/main/java/com/adyen/checkout/card/internal/data/api/DetectCardTypeRepository.kt index d86fec995e..41959d9a7b 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/data/api/DetectCardTypeRepository.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/data/api/DetectCardTypeRepository.kt @@ -13,7 +13,7 @@ import com.adyen.checkout.card.internal.data.model.DetectedCardType import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow -internal interface DetectCardTypeRepository { +interface DetectCardTypeRepository { val detectedCardTypesFlow: Flow> @@ -24,5 +24,6 @@ internal interface DetectCardTypeRepository { supportedCardBrands: List, clientKey: String, coroutineScope: CoroutineScope, + type: String? = null ) } diff --git a/card/src/main/java/com/adyen/checkout/card/internal/data/model/BinLookupRequest.kt b/card/src/main/java/com/adyen/checkout/card/internal/data/model/BinLookupRequest.kt index 5c297542b5..e0ed4f946c 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/data/model/BinLookupRequest.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/data/model/BinLookupRequest.kt @@ -18,16 +18,18 @@ import org.json.JSONException import org.json.JSONObject @Parcelize -internal data class BinLookupRequest( +data class BinLookupRequest( val encryptedBin: String? = null, val requestId: String? = null, - val supportedBrands: List? = null + val supportedBrands: List? = null, + val type: String? = null, ) : ModelObject() { companion object { private const val ENCRYPTED_BIN = "encryptedBin" private const val REQUEST_ID = "requestId" private const val SUPPORTED_BRANDS = "supportedBrands" + private const val TYPE = "type" @JvmField val SERIALIZER: Serializer = object : Serializer { @@ -37,6 +39,7 @@ internal data class BinLookupRequest( jsonObject.putOpt(ENCRYPTED_BIN, modelObject.encryptedBin) jsonObject.putOpt(REQUEST_ID, modelObject.requestId) jsonObject.putOpt(SUPPORTED_BRANDS, JsonUtils.serializeOptStringList(modelObject.supportedBrands)) + jsonObject.putOpt(TYPE, modelObject.type) } catch (e: JSONException) { throw ModelSerializationException(BinLookupRequest::class.java, e) } @@ -48,7 +51,8 @@ internal data class BinLookupRequest( BinLookupRequest( encryptedBin = jsonObject.getStringOrNull(ENCRYPTED_BIN), requestId = jsonObject.getStringOrNull(REQUEST_ID), - supportedBrands = jsonObject.optStringList(SUPPORTED_BRANDS) + supportedBrands = jsonObject.optStringList(SUPPORTED_BRANDS), + type = jsonObject.getStringOrNull(TYPE), ) } catch (e: JSONException) { throw ModelSerializationException(BinLookupRequest::class.java, e) diff --git a/card/src/main/java/com/adyen/checkout/card/internal/data/model/BinLookupResponse.kt b/card/src/main/java/com/adyen/checkout/card/internal/data/model/BinLookupResponse.kt index 0bb04e0218..905318648d 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/data/model/BinLookupResponse.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/data/model/BinLookupResponse.kt @@ -17,7 +17,7 @@ import org.json.JSONException import org.json.JSONObject @Parcelize -internal data class BinLookupResponse( +data class BinLookupResponse( val brands: List? = null, val issuingCountryCode: String? = null, val requestId: String? = null diff --git a/card/src/main/java/com/adyen/checkout/card/internal/data/model/DetectedCardType.kt b/card/src/main/java/com/adyen/checkout/card/internal/data/model/DetectedCardType.kt index faf99e221e..a2dc65450d 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/data/model/DetectedCardType.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/data/model/DetectedCardType.kt @@ -10,7 +10,7 @@ package com.adyen.checkout.card.internal.data.model import com.adyen.checkout.card.CardBrand -internal data class DetectedCardType( +data class DetectedCardType( val cardBrand: CardBrand, val isReliable: Boolean, val enableLuhnCheck: Boolean, diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/CardDelegate.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/CardDelegate.kt index db4d5669f1..eddf741c9d 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/CardDelegate.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/CardDelegate.kt @@ -20,7 +20,7 @@ import com.adyen.checkout.ui.core.internal.ui.UIStateDelegate import com.adyen.checkout.ui.core.internal.ui.ViewProvidingDelegate import kotlinx.coroutines.flow.Flow -internal interface CardDelegate : +interface CardDelegate : PaymentComponentDelegate, ViewProvidingDelegate, ButtonDelegate, diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt index 786f81cf77..485a7e2981 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt @@ -84,7 +84,7 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @Suppress("LongParameterList", "TooManyFunctions") -internal class DefaultCardDelegate( +class DefaultCardDelegate( private val observerRepository: PaymentObserverRepository, private val publicKeyRepository: PublicKeyRepository, override val componentParams: CardComponentParams, @@ -222,7 +222,8 @@ internal class DefaultCardDelegate( publicKey = publicKey, supportedCardBrands = componentParams.supportedCardBrands, clientKey = componentParams.clientKey, - coroutineScope = coroutineScope + coroutineScope = coroutineScope, + type = paymentMethod.type ) requestStateList(inputData.address.country) } diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParams.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParams.kt index 13c47c9467..56f27d9b6f 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParams.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParams.kt @@ -19,7 +19,7 @@ import com.adyen.checkout.core.Environment import com.adyen.checkout.ui.core.internal.ui.model.AddressParams import java.util.Locale -internal data class CardComponentParams( +data class CardComponentParams( override val shopperLocale: Locale, override val environment: Environment, override val clientKey: String, diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardInputData.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardInputData.kt index 0c57b4568c..ea11acc93a 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardInputData.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardInputData.kt @@ -11,7 +11,7 @@ import com.adyen.checkout.card.internal.ui.view.InstallmentModel import com.adyen.checkout.components.core.internal.ui.model.InputData import com.adyen.checkout.ui.core.internal.ui.model.AddressInputModel -internal data class CardInputData( +data class CardInputData( var cardNumber: String = "", var expiryDate: ExpiryDate = ExpiryDate.EMPTY_DATE, var securityCode: String = "", diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardListItem.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardListItem.kt index e8e50c3503..5eb07831a0 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardListItem.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardListItem.kt @@ -11,7 +11,7 @@ package com.adyen.checkout.card.internal.ui.model import com.adyen.checkout.card.CardBrand import com.adyen.checkout.core.Environment -internal data class CardListItem( +data class CardListItem( val cardBrand: CardBrand, val isDetected: Boolean, // We need the environment to load the logo diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardOutputData.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardOutputData.kt index 703b1659c6..1438a10e12 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardOutputData.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardOutputData.kt @@ -15,7 +15,7 @@ import com.adyen.checkout.components.core.internal.ui.model.OutputData import com.adyen.checkout.ui.core.internal.ui.AddressFormUIState import com.adyen.checkout.ui.core.internal.ui.model.AddressOutputData -internal data class CardOutputData( +data class CardOutputData( val cardNumberState: FieldState, val expiryDateState: FieldState, val securityCodeState: FieldState, diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InputFieldUIState.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InputFieldUIState.kt index fa9a69c40a..6cd95120f8 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InputFieldUIState.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InputFieldUIState.kt @@ -8,6 +8,6 @@ package com.adyen.checkout.card.internal.ui.model -internal enum class InputFieldUIState { +enum class InputFieldUIState { REQUIRED, OPTIONAL, HIDDEN } diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentOption.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentOption.kt index ac7c2899af..bf6fe99a1d 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentOption.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentOption.kt @@ -8,7 +8,7 @@ package com.adyen.checkout.card.internal.ui.model -internal enum class InstallmentOption(val type: String?) { +enum class InstallmentOption(val type: String?) { ONE_TIME(null), REGULAR("regular"), REVOLVING("revolving") diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentOptionParams.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentOptionParams.kt index b020847632..16d2f7cb1c 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentOptionParams.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentOptionParams.kt @@ -15,7 +15,7 @@ import com.adyen.checkout.card.CardBrand * * Note: All values specified in [values] must be greater than 1. */ -internal sealed class InstallmentOptionParams { +sealed class InstallmentOptionParams { abstract val values: List abstract val includeRevolving: Boolean diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentParams.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentParams.kt index b4a3878930..fe932a7cc0 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentParams.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentParams.kt @@ -22,7 +22,7 @@ import com.adyen.checkout.card.CardBrand * @param defaultOptions Installment Options to be used for all card types. * @param cardBasedOptions Installment Options to be used for specific card types. */ -internal data class InstallmentParams( +data class InstallmentParams( val defaultOptions: InstallmentOptionParams.DefaultInstallmentOptions? = null, val cardBasedOptions: List = emptyList() ) diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/view/CardView.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/view/CardView.kt index c9bd84b4e8..7695726b03 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/view/CardView.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/view/CardView.kt @@ -55,7 +55,7 @@ import kotlinx.coroutines.flow.onEach * CardView for [CardComponent]. */ @Suppress("TooManyFunctions", "LargeClass") -internal class CardView @JvmOverloads constructor( +class CardView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/view/InstallmentListAdapter.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/view/InstallmentListAdapter.kt index 68c45ec2a0..0f6e5938fc 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/view/InstallmentListAdapter.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/view/InstallmentListAdapter.kt @@ -64,7 +64,7 @@ internal class InstallmentListAdapter( } } -internal data class InstallmentModel( +data class InstallmentModel( @StringRes val textResId: Int, val value: Int?, val option: InstallmentOption diff --git a/card/src/test/java/com/adyen/checkout/card/internal/data/api/TestDetectCardTypeRepository.kt b/card/src/test/java/com/adyen/checkout/card/internal/data/api/TestDetectCardTypeRepository.kt index 832ad9677b..cc3d20ee22 100644 --- a/card/src/test/java/com/adyen/checkout/card/internal/data/api/TestDetectCardTypeRepository.kt +++ b/card/src/test/java/com/adyen/checkout/card/internal/data/api/TestDetectCardTypeRepository.kt @@ -34,6 +34,7 @@ internal class TestDetectCardTypeRepository : DetectCardTypeRepository { supportedCardBrands: List, clientKey: String, coroutineScope: CoroutineScope, + type: String? ) { val detectedCardTypes = when (detectionResult) { TestDetectedCardType.ERROR -> null diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/provider/ComponentParsingProvider.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/provider/ComponentParsingProvider.kt index 841f078ad4..daa1977934 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/provider/ComponentParsingProvider.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/provider/ComponentParsingProvider.kt @@ -21,10 +21,9 @@ import com.adyen.checkout.bacs.BacsDirectDebitComponent import com.adyen.checkout.bacs.BacsDirectDebitComponentState import com.adyen.checkout.bacs.BacsDirectDebitConfiguration import com.adyen.checkout.bacs.internal.provider.BacsDirectDebitComponentProvider -import com.adyen.checkout.bcmc.BcmcComponent -import com.adyen.checkout.bcmc.BcmcComponentState +import com.adyen.checkout.bcmc.BcmcComponentNew import com.adyen.checkout.bcmc.BcmcConfiguration -import com.adyen.checkout.bcmc.internal.provider.BcmcComponentProvider +import com.adyen.checkout.bcmc.internal.provider.BcmcComponentProviderNew import com.adyen.checkout.blik.BlikComponent import com.adyen.checkout.blik.BlikComponentState import com.adyen.checkout.blik.BlikConfiguration @@ -249,7 +248,7 @@ internal fun getDefaultConfigForPaymentMethod( clientKey = clientKey ) - checkCompileOnly { BcmcComponent.PROVIDER.isPaymentMethodSupported(paymentMethod) } -> + checkCompileOnly { BcmcComponentNew.PROVIDER.isPaymentMethodSupported(paymentMethod) } -> BcmcConfiguration.Builder( shopperLocale = shopperLocale, environment = environment, @@ -617,14 +616,14 @@ internal fun getComponentFor( ) } - checkCompileOnly { BcmcComponent.PROVIDER.isPaymentMethodSupported(paymentMethod) } -> { + checkCompileOnly { BcmcComponentNew.PROVIDER.isPaymentMethodSupported(paymentMethod) } -> { val bcmcConfiguration: BcmcConfiguration = getConfigurationForPaymentMethod(paymentMethod, dropInConfiguration, context) - BcmcComponentProvider(dropInParams, sessionParams, analyticsRepository).get( + BcmcComponentProviderNew(dropInParams, sessionParams, analyticsRepository).get( fragment = fragment, paymentMethod = paymentMethod, configuration = bcmcConfiguration, - callback = componentCallback as ComponentCallback, + callback = componentCallback as ComponentCallback, ) } From d328842133ee1be6b0533ef06953e4131f630a15 Mon Sep 17 00:00:00 2001 From: ozgur <6615094+ozgur00@users.noreply.github.com> Date: Fri, 15 Sep 2023 10:11:17 +0200 Subject: [PATCH 15/22] Make cvc visibility an enum instead of a boolean COAND-795 --- .../adyen/checkout/bcmc/BcmcConfiguration.kt | 22 ++++++ .../ui/model/BcmcComponentParamsMapperNew.kt | 6 +- .../com/adyen/checkout/card/CVCVisibility.kt | 17 +++++ .../adyen/checkout/card/CardConfiguration.kt | 76 ++++++++++--------- .../card/internal/ui/DefaultCardDelegate.kt | 28 +++++-- .../card/internal/ui/StoredCardDelegate.kt | 14 ++-- .../internal/ui/model/CardComponentParams.kt | 6 +- .../ui/model/CardComponentParamsMapper.kt | 14 +++- .../internal/ui/DefaultCardDelegateTest.kt | 8 +- .../internal/ui/StoredCardDelegateTest.kt | 10 ++- .../ui/model/CardComponentParamsMapperTest.kt | 20 ++--- .../CheckoutConfigurationProvider.kt | 2 + 12 files changed, 150 insertions(+), 73 deletions(-) create mode 100644 card/src/main/java/com/adyen/checkout/card/CVCVisibility.kt diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcConfiguration.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcConfiguration.kt index 6c0a5e1c9c..c7de43fa14 100644 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcConfiguration.kt +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcConfiguration.kt @@ -10,6 +10,8 @@ package com.adyen.checkout.bcmc import android.content.Context import com.adyen.checkout.action.core.GenericActionConfiguration import com.adyen.checkout.action.core.internal.ActionHandlingPaymentMethodConfigurationBuilder +import com.adyen.checkout.card.CVCVisibility +import com.adyen.checkout.card.CardConfiguration import com.adyen.checkout.components.core.Amount import com.adyen.checkout.components.core.AnalyticsConfiguration import com.adyen.checkout.components.core.PaymentComponentData @@ -35,6 +37,7 @@ class BcmcConfiguration private constructor( val isHolderNameRequired: Boolean?, val shopperReference: String?, val isStorePaymentFieldVisible: Boolean?, + val cvcVisibility: CVCVisibility?, internal val genericActionConfiguration: GenericActionConfiguration, ) : Configuration, ButtonConfiguration { @@ -49,6 +52,7 @@ class BcmcConfiguration private constructor( private var showStorePaymentField: Boolean? = null private var shopperReference: String? = null private var isSubmitButtonVisible: Boolean? = null + private var cvcVisibility: CVCVisibility? = null /** * Alternative constructor that uses the [context] to fetch the user locale and use it as a shopper locale. @@ -117,6 +121,23 @@ class BcmcConfiguration private constructor( return this } + // TODO docs + /** + * Set if the CVC field should be hidden from the Component and not requested to the shopper on a regular + * payment. + * Note that this might have implications for the risk of the transaction. Talk to Adyen Support before enabling + * this. + * + * Default is false. + * + * @param hideCvc If CVC should be hidden or not. + * @return [CardConfiguration.Builder] + */ + fun setCvcVisibility(cvcVisibility: CVCVisibility): Builder { + this.cvcVisibility = cvcVisibility + return this + } + /** * Sets if submit button will be visible or not. * @@ -145,6 +166,7 @@ class BcmcConfiguration private constructor( shopperReference = shopperReference, isStorePaymentFieldVisible = showStorePaymentField, isSubmitButtonVisible = isSubmitButtonVisible, + cvcVisibility = cvcVisibility, genericActionConfiguration = genericActionConfigurationBuilder.build(), ) } diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperNew.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperNew.kt index b8c60fd305..fbd851b6f2 100644 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperNew.kt +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperNew.kt @@ -9,10 +9,12 @@ package com.adyen.checkout.bcmc.internal.ui.model import com.adyen.checkout.bcmc.BcmcConfiguration +import com.adyen.checkout.card.CVCVisibility import com.adyen.checkout.card.CardBrand import com.adyen.checkout.card.CardType import com.adyen.checkout.card.KCPAuthVisibility import com.adyen.checkout.card.SocialSecurityNumberVisibility +import com.adyen.checkout.card.StoredCVCVisibility import com.adyen.checkout.card.internal.ui.model.CardComponentParams import com.adyen.checkout.components.core.internal.ui.model.AnalyticsParams import com.adyen.checkout.components.core.internal.ui.model.ComponentParams @@ -48,10 +50,10 @@ internal class BcmcComponentParamsMapperNew( isStorePaymentFieldVisible = isStorePaymentFieldVisible ?: false, addressParams = AddressParams.None, installmentParams = null, - isHideCvc = true, - isHideCvcStoredCard = true, kcpAuthVisibility = KCPAuthVisibility.HIDE, socialSecurityNumberVisibility = SocialSecurityNumberVisibility.HIDE, + cvcVisibility = cvcVisibility ?: CVCVisibility.ALWAYS_HIDE, + storedCVCVisibility = StoredCVCVisibility.HIDE, supportedCardBrands = listOf( CardBrand(cardType = CardType.BCMC), CardBrand(cardType = CardType.MAESTRO), diff --git a/card/src/main/java/com/adyen/checkout/card/CVCVisibility.kt b/card/src/main/java/com/adyen/checkout/card/CVCVisibility.kt new file mode 100644 index 0000000000..6f4a4d4387 --- /dev/null +++ b/card/src/main/java/com/adyen/checkout/card/CVCVisibility.kt @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2023 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by ozgur on 5/9/2023. + */ + +package com.adyen.checkout.card + +enum class CVCVisibility { + SHOW_FIRST, HIDE_FIRST, ALWAYS_HIDE +} + +enum class StoredCVCVisibility { + SHOW, HIDE +} diff --git a/card/src/main/java/com/adyen/checkout/card/CardConfiguration.kt b/card/src/main/java/com/adyen/checkout/card/CardConfiguration.kt index c715f79bac..15e87d879b 100644 --- a/card/src/main/java/com/adyen/checkout/card/CardConfiguration.kt +++ b/card/src/main/java/com/adyen/checkout/card/CardConfiguration.kt @@ -37,12 +37,12 @@ class CardConfiguration private constructor( val supportedCardBrands: List?, val shopperReference: String?, val isStorePaymentFieldVisible: Boolean?, - val isHideCvc: Boolean?, - val isHideCvcStoredCard: Boolean?, val socialSecurityNumberVisibility: SocialSecurityNumberVisibility?, val kcpAuthVisibility: KCPAuthVisibility?, val installmentConfiguration: InstallmentConfiguration?, val addressConfiguration: AddressConfiguration?, + val cvcVisibility: CVCVisibility?, + val storedCVCVisibility: StoredCVCVisibility?, internal val genericActionConfiguration: GenericActionConfiguration, ) : Configuration, ButtonConfiguration { @@ -57,12 +57,12 @@ class CardConfiguration private constructor( private var holderNameRequired: Boolean? = null private var isStorePaymentFieldVisible: Boolean? = null private var shopperReference: String? = null - private var isHideCvc: Boolean? = null - private var isHideCvcStoredCard: Boolean? = null private var isSubmitButtonVisible: Boolean? = null private var socialSecurityNumberVisibility: SocialSecurityNumberVisibility? = null private var kcpAuthVisibility: KCPAuthVisibility? = null private var installmentConfiguration: InstallmentConfiguration? = null + private var cvcVisibility: CVCVisibility? = null + private var storedCVCVisibility: StoredCVCVisibility? = null private var addressConfiguration: AddressConfiguration? = null /** @@ -160,37 +160,6 @@ class CardConfiguration private constructor( return this } - /** - * Set if the CVC field should be hidden from the Component and not requested to the shopper on a regular - * payment. - * Note that this might have implications for the risk of the transaction. Talk to Adyen Support before enabling - * this. - * - * Default is false. - * - * @param hideCvc If CVC should be hidden or not. - * @return [CardConfiguration.Builder] - */ - fun setHideCvc(hideCvc: Boolean): Builder { - this.isHideCvc = hideCvc - return this - } - - /** - * Set if the CVC field should be hidden from the Component and not requested to the shopper on a stored payment - * flow. - * Note that this has implications for the risk of the transaction. Talk to Adyen Support before enabling this. - * - * Default is false. - * - * @param hideCvcStoredCard If CVC should be hidden or not for stored payments. - * @return [CardConfiguration.Builder] - */ - fun setHideCvcStoredCard(hideCvcStoredCard: Boolean): Builder { - isHideCvcStoredCard = hideCvcStoredCard - return this - } - /** * Set if CPF/CNPJ field for Brazil merchants should be visible or not. * @@ -244,6 +213,39 @@ class CardConfiguration private constructor( return this } + // TODO docs + /** + * Set if the CVC field should be hidden from the Component and not requested to the shopper on a regular + * payment. + * Note that this might have implications for the risk of the transaction. Talk to Adyen Support before enabling + * this. + * + * Default is false. + * + * @param hideCvc If CVC should be hidden or not. + * @return [CardConfiguration.Builder] + */ + fun setCvcVisibility(cvcVisibility: CVCVisibility): Builder { + this.cvcVisibility = cvcVisibility + return this + } + + // TODO docs + /** + * Set if the CVC field should be hidden from the Component and not requested to the shopper on a stored payment + * flow. + * Note that this has implications for the risk of the transaction. Talk to Adyen Support before enabling this. + * + * Default is false. + * + * @param hideCvcStoredCard If CVC should be hidden or not for stored payments. + * @return [CardConfiguration.Builder] + */ + fun setStoredCvcVisibility(storedCVCVisibility: StoredCVCVisibility): Builder { + this.storedCVCVisibility = storedCVCVisibility + return this + } + /** * Sets if submit button will be visible or not. * @@ -273,12 +275,12 @@ class CardConfiguration private constructor( supportedCardBrands = supportedCardBrands, shopperReference = shopperReference, isStorePaymentFieldVisible = isStorePaymentFieldVisible, - isHideCvc = isHideCvc, - isHideCvcStoredCard = isHideCvcStoredCard, socialSecurityNumberVisibility = socialSecurityNumberVisibility, kcpAuthVisibility = kcpAuthVisibility, installmentConfiguration = installmentConfiguration, addressConfiguration = addressConfiguration, + cvcVisibility = cvcVisibility, + storedCVCVisibility = storedCVCVisibility, genericActionConfiguration = genericActionConfigurationBuilder.build() ) } diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt index 485a7e2981..f6c501cd78 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt @@ -11,6 +11,7 @@ package com.adyen.checkout.card.internal.ui import androidx.annotation.VisibleForTesting import androidx.lifecycle.LifecycleOwner import com.adyen.checkout.card.BinLookupData +import com.adyen.checkout.card.CVCVisibility import com.adyen.checkout.card.CardBrand import com.adyen.checkout.card.CardComponentState import com.adyen.checkout.card.KCPAuthVisibility @@ -478,7 +479,7 @@ class DefaultCardDelegate( securityCode: String, cardType: DetectedCardType? ): FieldState { - return if (componentParams.isHideCvc) { + return if (isCvcHidden()) { FieldState( securityCode, Validation.Valid @@ -549,7 +550,9 @@ class DefaultCardDelegate( } private fun isCvcHidden(): Boolean { - return componentParams.isHideCvc + val hiddenAfterBinLookup = componentParams.cvcVisibility == CVCVisibility.HIDE_FIRST && + outputData.cvcUIState == InputFieldUIState.HIDDEN + return componentParams.cvcVisibility == CVCVisibility.ALWAYS_HIDE || hiddenAfterBinLookup } private fun isSocialSecurityNumberRequired(): Boolean { @@ -606,10 +609,23 @@ class DefaultCardDelegate( private fun makeCvcUIState(cvcPolicy: Brand.FieldPolicy?): InputFieldUIState { Logger.d(TAG, "makeCvcUIState: $cvcPolicy") - return when { - isCvcHidden() -> InputFieldUIState.HIDDEN - cvcPolicy?.isRequired() == false -> InputFieldUIState.OPTIONAL - else -> InputFieldUIState.REQUIRED + + return when (componentParams.cvcVisibility) { + CVCVisibility.SHOW_FIRST -> { + when { + cvcPolicy?.isRequired() == false -> InputFieldUIState.OPTIONAL + else -> InputFieldUIState.REQUIRED + } + } + + CVCVisibility.HIDE_FIRST -> { + when { + cvcPolicy?.isRequired() == false -> InputFieldUIState.OPTIONAL + else -> InputFieldUIState.REQUIRED + } + } + + CVCVisibility.ALWAYS_HIDE -> InputFieldUIState.HIDDEN } } diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/StoredCardDelegate.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/StoredCardDelegate.kt index 238c6175c2..4e25b413c3 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/StoredCardDelegate.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/StoredCardDelegate.kt @@ -14,6 +14,7 @@ import com.adyen.checkout.card.BinLookupData import com.adyen.checkout.card.CardBrand import com.adyen.checkout.card.CardComponentState import com.adyen.checkout.card.CardType +import com.adyen.checkout.card.StoredCVCVisibility import com.adyen.checkout.card.internal.data.model.Brand import com.adyen.checkout.card.internal.data.model.DetectedCardType import com.adyen.checkout.card.internal.ui.model.CardComponentParams @@ -82,7 +83,7 @@ internal class StoredCardDelegate( isReliable = true, enableLuhnCheck = true, cvcPolicy = when { - componentParams.isHideCvcStoredCard || noCvcBrands.contains(cardType) -> Brand.FieldPolicy.HIDDEN + isCvcHidden() -> Brand.FieldPolicy.HIDDEN else -> Brand.FieldPolicy.REQUIRED }, expiryDatePolicy = Brand.FieldPolicy.REQUIRED, @@ -306,7 +307,7 @@ internal class StoredCardDelegate( } private fun validateSecurityCode(securityCode: String, detectedCardType: DetectedCardType): FieldState { - return if (componentParams.isHideCvcStoredCard || noCvcBrands.contains(detectedCardType.cardBrand)) { + return if (isCvcHidden()) { FieldState( securityCode, Validation.Valid @@ -317,7 +318,7 @@ internal class StoredCardDelegate( } private fun isCvcHidden(): Boolean { - return componentParams.isHideCvcStoredCard || noCvcBrands.contains(cardType) + return componentParams.storedCVCVisibility == StoredCVCVisibility.HIDE || noCvcBrands.contains(cardType) } private fun mapComponentState( @@ -382,10 +383,9 @@ internal class StoredCardDelegate( private fun makeCvcUIState(cvcPolicy: Brand.FieldPolicy): InputFieldUIState { Logger.d(TAG, "makeCvcUIState: $cvcPolicy") - return when { - isCvcHidden() -> InputFieldUIState.HIDDEN - !cvcPolicy.isRequired() -> InputFieldUIState.OPTIONAL - else -> InputFieldUIState.REQUIRED + return when (componentParams.storedCVCVisibility) { + StoredCVCVisibility.SHOW -> InputFieldUIState.REQUIRED + StoredCVCVisibility.HIDE -> InputFieldUIState.HIDDEN } } diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParams.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParams.kt index 56f27d9b6f..067cc13233 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParams.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParams.kt @@ -8,9 +8,11 @@ package com.adyen.checkout.card.internal.ui.model +import com.adyen.checkout.card.CVCVisibility import com.adyen.checkout.card.CardBrand import com.adyen.checkout.card.KCPAuthVisibility import com.adyen.checkout.card.SocialSecurityNumberVisibility +import com.adyen.checkout.card.StoredCVCVisibility import com.adyen.checkout.components.core.Amount import com.adyen.checkout.components.core.internal.ui.model.AnalyticsParams import com.adyen.checkout.components.core.internal.ui.model.ButtonParams @@ -31,10 +33,10 @@ data class CardComponentParams( val supportedCardBrands: List, val shopperReference: String?, val isStorePaymentFieldVisible: Boolean, - val isHideCvc: Boolean, - val isHideCvcStoredCard: Boolean, val socialSecurityNumberVisibility: SocialSecurityNumberVisibility, val kcpAuthVisibility: KCPAuthVisibility, val installmentParams: InstallmentParams?, val addressParams: AddressParams, + val cvcVisibility: CVCVisibility, + val storedCVCVisibility: StoredCVCVisibility ) : ComponentParams, ButtonParams diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParamsMapper.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParamsMapper.kt index 7a4ffbf88d..c2140eea98 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParamsMapper.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParamsMapper.kt @@ -9,10 +9,12 @@ package com.adyen.checkout.card.internal.ui.model import com.adyen.checkout.card.AddressConfiguration +import com.adyen.checkout.card.CVCVisibility import com.adyen.checkout.card.CardBrand import com.adyen.checkout.card.CardConfiguration import com.adyen.checkout.card.KCPAuthVisibility import com.adyen.checkout.card.SocialSecurityNumberVisibility +import com.adyen.checkout.card.StoredCVCVisibility import com.adyen.checkout.components.core.PaymentMethod import com.adyen.checkout.components.core.internal.ui.model.AnalyticsParams import com.adyen.checkout.components.core.internal.ui.model.ComponentParams @@ -80,12 +82,12 @@ internal class CardComponentParamsMapper( supportedCardBrands = supportedCardBrands, shopperReference = shopperReference, isStorePaymentFieldVisible = isStorePaymentFieldVisible ?: true, - isHideCvc = isHideCvc ?: false, - isHideCvcStoredCard = isHideCvcStoredCard ?: false, socialSecurityNumberVisibility = socialSecurityNumberVisibility ?: SocialSecurityNumberVisibility.HIDE, kcpAuthVisibility = kcpAuthVisibility ?: KCPAuthVisibility.HIDE, installmentParams = installmentsParamsMapper.mapToInstallmentParams(installmentConfiguration), - addressParams = addressConfiguration?.mapToAddressParam() ?: AddressParams.None + addressParams = addressConfiguration?.mapToAddressParam() ?: AddressParams.None, + cvcVisibility = cvcVisibility ?: CVCVisibility.SHOW_FIRST, + storedCVCVisibility = storedCVCVisibility ?: StoredCVCVisibility.SHOW ) } @@ -102,12 +104,14 @@ internal class CardComponentParamsMapper( Logger.v(TAG, "Reading supportedCardTypes from configuration") supportedCardBrands } + paymentMethod.brands.orEmpty().isNotEmpty() -> { Logger.v(TAG, "Reading supportedCardTypes from API brands") paymentMethod.brands.orEmpty().map { CardBrand(txVariant = it) } } + else -> { Logger.v(TAG, "Falling back to CardConfiguration.DEFAULT_SUPPORTED_CARDS_LIST") CardConfiguration.DEFAULT_SUPPORTED_CARDS_LIST @@ -146,9 +150,11 @@ internal class CardComponentParamsMapper( addressFieldPolicy.mapToAddressParamFieldPolicy() ) } + AddressConfiguration.None -> { AddressParams.None } + is AddressConfiguration.PostalCode -> { AddressParams.PostalCode(addressFieldPolicy.mapToAddressParamFieldPolicy()) } @@ -160,9 +166,11 @@ internal class CardComponentParamsMapper( is AddressConfiguration.CardAddressFieldPolicy.Optional -> { AddressFieldPolicyParams.Optional } + is AddressConfiguration.CardAddressFieldPolicy.OptionalForCardTypes -> { AddressFieldPolicyParams.OptionalForCardTypes(brands) } + is AddressConfiguration.CardAddressFieldPolicy.Required -> { AddressFieldPolicyParams.Required } diff --git a/card/src/test/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegateTest.kt b/card/src/test/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegateTest.kt index de44d012c0..baf6de1ad6 100644 --- a/card/src/test/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegateTest.kt +++ b/card/src/test/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegateTest.kt @@ -12,6 +12,7 @@ import androidx.annotation.StringRes import app.cash.turbine.test import app.cash.turbine.testIn import com.adyen.checkout.card.AddressConfiguration +import com.adyen.checkout.card.CVCVisibility import com.adyen.checkout.card.CardBrand import com.adyen.checkout.card.CardComponentState import com.adyen.checkout.card.CardConfiguration @@ -21,6 +22,7 @@ import com.adyen.checkout.card.InstallmentOptions import com.adyen.checkout.card.KCPAuthVisibility import com.adyen.checkout.card.R import com.adyen.checkout.card.SocialSecurityNumberVisibility +import com.adyen.checkout.card.StoredCVCVisibility import com.adyen.checkout.card.internal.data.api.DetectCardTypeRepository import com.adyen.checkout.card.internal.data.api.TestDetectCardTypeRepository import com.adyen.checkout.card.internal.data.api.TestDetectedCardType @@ -545,8 +547,8 @@ internal class DefaultCardDelegateTest( delegate = createCardDelegate( configuration = CardConfiguration.Builder(Locale.US, Environment.TEST, TEST_CLIENT_KEY) - .setHideCvc(true) - .setHideCvcStoredCard(true) + .setCvcVisibility(CVCVisibility.ALWAYS_HIDE) + .setStoredCvcVisibility(StoredCVCVisibility.HIDE) .setSocialSecurityNumberVisibility(SocialSecurityNumberVisibility.SHOW) .setInstallmentConfigurations(installmentConfiguration) .setHolderNameRequired(true) @@ -1189,7 +1191,7 @@ internal class DefaultCardDelegateTest( private fun getCustomCardConfigurationBuilder(): CardConfiguration.Builder { return CardConfiguration.Builder(Locale.US, Environment.TEST, TEST_CLIENT_KEY) - .setHideCvc(true) + .setCvcVisibility(CVCVisibility.ALWAYS_HIDE) .setShopperReference("shopper_android") .setSocialSecurityNumberVisibility(SocialSecurityNumberVisibility.SHOW) .setInstallmentConfigurations( diff --git a/card/src/test/java/com/adyen/checkout/card/internal/ui/StoredCardDelegateTest.kt b/card/src/test/java/com/adyen/checkout/card/internal/ui/StoredCardDelegateTest.kt index 219801313c..9b7e0de4c6 100644 --- a/card/src/test/java/com/adyen/checkout/card/internal/ui/StoredCardDelegateTest.kt +++ b/card/src/test/java/com/adyen/checkout/card/internal/ui/StoredCardDelegateTest.kt @@ -10,6 +10,7 @@ package com.adyen.checkout.card.internal.ui import app.cash.turbine.test import com.adyen.checkout.card.AddressConfiguration +import com.adyen.checkout.card.CVCVisibility import com.adyen.checkout.card.CardBrand import com.adyen.checkout.card.CardComponentState import com.adyen.checkout.card.CardConfiguration @@ -19,6 +20,7 @@ import com.adyen.checkout.card.InstallmentOptions import com.adyen.checkout.card.KCPAuthVisibility import com.adyen.checkout.card.R import com.adyen.checkout.card.SocialSecurityNumberVisibility +import com.adyen.checkout.card.StoredCVCVisibility import com.adyen.checkout.card.internal.data.model.Brand import com.adyen.checkout.card.internal.data.model.DetectedCardType import com.adyen.checkout.card.internal.ui.model.CardComponentParamsMapper @@ -108,7 +110,7 @@ internal class StoredCardDelegateTest( fun `when component is initialized with cvc shown, then view flow emits StoredCardView`() = runTest { delegate = createCardDelegate( configuration = getDefaultCardConfigurationBuilder() - .setHideCvcStoredCard(false) + .setStoredCvcVisibility(StoredCVCVisibility.SHOW) .build() ) delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) @@ -121,7 +123,7 @@ internal class StoredCardDelegateTest( fun `when component is initialized with cvc hidden, then view flow emits null`() = runTest { delegate = createCardDelegate( configuration = getDefaultCardConfigurationBuilder() - .setHideCvcStoredCard(true) + .setStoredCvcVisibility(StoredCVCVisibility.HIDE) .build() ) delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) @@ -202,7 +204,7 @@ internal class StoredCardDelegateTest( fun `security code is empty with hide cvc stored config, then output data should be valid`() = runTest { delegate = createCardDelegate( configuration = getDefaultCardConfigurationBuilder() - .setHideCvcStoredCard(true) + .setStoredCvcVisibility(StoredCVCVisibility.HIDE) .build() ) @@ -496,7 +498,7 @@ internal class StoredCardDelegateTest( private fun getCustomCardConfigurationBuilder(): CardConfiguration.Builder { return CardConfiguration.Builder(Locale.US, Environment.TEST, TEST_CLIENT_KEY) - .setHideCvc(true) + .setCvcVisibility(CVCVisibility.ALWAYS_HIDE) .setShopperReference("shopper_android") .setSocialSecurityNumberVisibility(SocialSecurityNumberVisibility.SHOW) .setInstallmentConfigurations( diff --git a/card/src/test/java/com/adyen/checkout/card/internal/ui/model/CardComponentParamsMapperTest.kt b/card/src/test/java/com/adyen/checkout/card/internal/ui/model/CardComponentParamsMapperTest.kt index 83e65786a0..79a3498408 100644 --- a/card/src/test/java/com/adyen/checkout/card/internal/ui/model/CardComponentParamsMapperTest.kt +++ b/card/src/test/java/com/adyen/checkout/card/internal/ui/model/CardComponentParamsMapperTest.kt @@ -9,6 +9,7 @@ package com.adyen.checkout.card.internal.ui.model import com.adyen.checkout.card.AddressConfiguration +import com.adyen.checkout.card.CVCVisibility import com.adyen.checkout.card.CardBrand import com.adyen.checkout.card.CardConfiguration import com.adyen.checkout.card.CardType @@ -16,6 +17,7 @@ import com.adyen.checkout.card.InstallmentConfiguration import com.adyen.checkout.card.InstallmentOptions import com.adyen.checkout.card.KCPAuthVisibility import com.adyen.checkout.card.SocialSecurityNumberVisibility +import com.adyen.checkout.card.StoredCVCVisibility import com.adyen.checkout.components.core.Amount import com.adyen.checkout.components.core.PaymentMethod import com.adyen.checkout.components.core.internal.ui.model.AnalyticsParams @@ -80,8 +82,8 @@ internal class CardComponentParamsMapperTest { .setSupportedCardTypes(CardType.DINERS, CardType.MAESTRO) .setShopperReference(shopperReference) .setShowStorePaymentField(false) - .setHideCvc(true) - .setHideCvcStoredCard(true) + .setCvcVisibility(CVCVisibility.ALWAYS_HIDE) + .setStoredCvcVisibility(StoredCVCVisibility.HIDE) .setSubmitButtonVisible(false) .setSocialSecurityNumberVisibility(SocialSecurityNumberVisibility.SHOW) .setKcpAuthVisibility(KCPAuthVisibility.SHOW) @@ -107,8 +109,8 @@ internal class CardComponentParamsMapperTest { shopperReference = shopperReference, isStorePaymentFieldVisible = false, isSubmitButtonVisible = false, - isHideCvc = true, - isHideCvcStoredCard = true, + cvcVisibility = CVCVisibility.ALWAYS_HIDE, + storedCVCVisibility = StoredCVCVisibility.HIDE, socialSecurityNumberVisibility = SocialSecurityNumberVisibility.SHOW, kcpAuthVisibility = KCPAuthVisibility.SHOW, installmentParams = expectedInstallmentParams, @@ -451,12 +453,12 @@ internal class CardComponentParamsMapperTest { supportedCardBrands: List = CardConfiguration.DEFAULT_SUPPORTED_CARDS_LIST, shopperReference: String? = null, isStorePaymentFieldVisible: Boolean = true, - isHideCvc: Boolean = false, - isHideCvcStoredCard: Boolean = false, socialSecurityNumberVisibility: SocialSecurityNumberVisibility = SocialSecurityNumberVisibility.HIDE, kcpAuthVisibility: KCPAuthVisibility = KCPAuthVisibility.HIDE, installmentParams: InstallmentParams? = null, addressParams: AddressParams = AddressParams.None, + cvcVisibility: CVCVisibility = CVCVisibility.SHOW_FIRST, + storedCVCVisibility: StoredCVCVisibility = StoredCVCVisibility.SHOW ) = CardComponentParams( shopperLocale = shopperLocale, environment = environment, @@ -468,13 +470,13 @@ internal class CardComponentParamsMapperTest { supportedCardBrands = supportedCardBrands, shopperReference = shopperReference, isStorePaymentFieldVisible = isStorePaymentFieldVisible, - isHideCvc = isHideCvc, - isHideCvcStoredCard = isHideCvcStoredCard, socialSecurityNumberVisibility = socialSecurityNumberVisibility, kcpAuthVisibility = kcpAuthVisibility, installmentParams = installmentParams, addressParams = addressParams, - amount = amount + amount = amount, + cvcVisibility = cvcVisibility, + storedCVCVisibility = storedCVCVisibility, ) companion object { diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/configuration/CheckoutConfigurationProvider.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/configuration/CheckoutConfigurationProvider.kt index 9fcaa629d7..c73d7b9039 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/configuration/CheckoutConfigurationProvider.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/configuration/CheckoutConfigurationProvider.kt @@ -6,6 +6,7 @@ import com.adyen.checkout.bacs.BacsDirectDebitConfiguration import com.adyen.checkout.bcmc.BcmcConfiguration import com.adyen.checkout.blik.BlikConfiguration import com.adyen.checkout.card.AddressConfiguration +import com.adyen.checkout.card.CVCVisibility import com.adyen.checkout.card.CardBrand import com.adyen.checkout.card.CardConfiguration import com.adyen.checkout.card.CardType @@ -74,6 +75,7 @@ internal class CheckoutConfigurationProvider @Inject constructor( .setInstallmentConfigurations(getInstallmentConfiguration()) .setAmount(amount) .setAnalyticsConfiguration(getAnalyticsConfiguration()) + .setCvcVisibility(CVCVisibility.SHOW_FIRST) .build() private fun getCashAppPayConfiguration(): CashAppPayConfiguration = From 1e588cd832b9f8561ce9d141a6a47f1fe6e335e2 Mon Sep 17 00:00:00 2001 From: ozgur <6615094+ozgur00@users.noreply.github.com> Date: Fri, 15 Sep 2023 11:50:44 +0200 Subject: [PATCH 16/22] Make invokeOnCleared look for the function in super classes as well COAND-795 --- .../adyen/checkout/test/extensions/ViewModelExtensions.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test-core/src/main/java/com/adyen/checkout/test/extensions/ViewModelExtensions.kt b/test-core/src/main/java/com/adyen/checkout/test/extensions/ViewModelExtensions.kt index 032905614a..6897897300 100644 --- a/test-core/src/main/java/com/adyen/checkout/test/extensions/ViewModelExtensions.kt +++ b/test-core/src/main/java/com/adyen/checkout/test/extensions/ViewModelExtensions.kt @@ -18,7 +18,11 @@ import androidx.lifecycle.ViewModel */ @RestrictTo(RestrictTo.Scope.TESTS) fun ViewModel.invokeOnCleared() { - with(javaClass.getDeclaredMethod("onCleared")) { + var clazz = javaClass as Class + while (clazz.declaredMethods.toList().none { it.name == "onCleared" }) { + clazz = clazz.superclass as Class + } + with(clazz.getDeclaredMethod("onCleared")) { isAccessible = true invoke(this@invokeOnCleared) } From 3f8cc9feaa3b799c13d0db11af166e123b0d173f Mon Sep 17 00:00:00 2001 From: ozgur <6615094+ozgur00@users.noreply.github.com> Date: Fri, 15 Sep 2023 11:51:34 +0200 Subject: [PATCH 17/22] Update tests for bcmc COAND-795 --- .../adyen/checkout/bcmc/BcmcComponentTest.kt | 49 ++++++++++++------- .../ui/model/BcmcComponentParamsMapperTest.kt | 42 ++++++++++++---- 2 files changed, 62 insertions(+), 29 deletions(-) diff --git a/bcmc/src/test/java/com/adyen/checkout/bcmc/BcmcComponentTest.kt b/bcmc/src/test/java/com/adyen/checkout/bcmc/BcmcComponentTest.kt index 92cacce9c3..ed35c56496 100644 --- a/bcmc/src/test/java/com/adyen/checkout/bcmc/BcmcComponentTest.kt +++ b/bcmc/src/test/java/com/adyen/checkout/bcmc/BcmcComponentTest.kt @@ -14,7 +14,8 @@ import app.cash.turbine.test import com.adyen.checkout.action.core.internal.DefaultActionHandlingComponent import com.adyen.checkout.action.core.internal.ui.GenericActionDelegate import com.adyen.checkout.bcmc.internal.ui.BcmcComponentViewType -import com.adyen.checkout.bcmc.internal.ui.BcmcDelegate +import com.adyen.checkout.card.CardComponentState +import com.adyen.checkout.card.internal.ui.CardDelegate import com.adyen.checkout.components.core.internal.ComponentEventHandler import com.adyen.checkout.components.core.internal.PaymentComponentEvent import com.adyen.checkout.core.AdyenLogger @@ -42,21 +43,21 @@ import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) @ExtendWith(MockitoExtension::class, TestDispatcherExtension::class) internal class BcmcComponentTest( - @Mock private val bcmcDelegate: BcmcDelegate, + @Mock private val cardDelegate: CardDelegate, @Mock private val genericActionDelegate: GenericActionDelegate, @Mock private val actionHandlingComponent: DefaultActionHandlingComponent, - @Mock private val componentEventHandler: ComponentEventHandler, + @Mock private val componentEventHandler: ComponentEventHandler, ) { - private lateinit var component: BcmcComponent + private lateinit var component: BcmcComponentNew @BeforeEach fun before() { - whenever(bcmcDelegate.viewFlow) doReturn MutableStateFlow(BcmcComponentViewType) + whenever(cardDelegate.viewFlow) doReturn MutableStateFlow(BcmcComponentViewType) whenever(genericActionDelegate.viewFlow) doReturn MutableStateFlow(null) - component = BcmcComponent( - bcmcDelegate, + component = BcmcComponentNew( + cardDelegate, genericActionDelegate, actionHandlingComponent, componentEventHandler, @@ -66,7 +67,7 @@ internal class BcmcComponentTest( @Test fun `when component is created then delegates are initialized`() { - verify(bcmcDelegate).initialize(component.viewModelScope) + verify(cardDelegate).initialize(component.viewModelScope) verify(genericActionDelegate).initialize(component.viewModelScope) verify(componentEventHandler).initialize(component.viewModelScope) } @@ -75,7 +76,7 @@ internal class BcmcComponentTest( fun `when component is cleared then delegates are cleared`() { component.invokeOnCleared() - verify(bcmcDelegate).onCleared() + verify(cardDelegate).onCleared() verify(genericActionDelegate).onCleared() verify(componentEventHandler).onCleared() } @@ -83,11 +84,11 @@ internal class BcmcComponentTest( @Test fun `when observe is called then observe in delegates is called`() { val lifecycleOwner = mock() - val callback: (PaymentComponentEvent) -> Unit = {} + val callback: (PaymentComponentEvent) -> Unit = {} component.observe(lifecycleOwner, callback) - verify(bcmcDelegate).observe(lifecycleOwner, component.viewModelScope, callback) + verify(cardDelegate).observe(lifecycleOwner, component.viewModelScope, callback) verify(genericActionDelegate).observe(eq(lifecycleOwner), eq(component.viewModelScope), any()) } @@ -95,7 +96,7 @@ internal class BcmcComponentTest( fun `when removeObserver is called then removeObserver in delegates is called`() { component.removeObserver() - verify(bcmcDelegate).removeObserver() + verify(cardDelegate).removeObserver() verify(genericActionDelegate).removeObserver() } @@ -110,8 +111,13 @@ internal class BcmcComponentTest( @Test fun `when bcmc delegate view flow emits a value then component view flow should match that value`() = runTest { val bcmcDelegateViewFlow = MutableStateFlow(TestComponentViewType.VIEW_TYPE_1) - whenever(bcmcDelegate.viewFlow) doReturn bcmcDelegateViewFlow - component = BcmcComponent(bcmcDelegate, genericActionDelegate, actionHandlingComponent, componentEventHandler) + whenever(cardDelegate.viewFlow) doReturn bcmcDelegateViewFlow + component = BcmcComponentNew( + cardDelegate = cardDelegate, + genericActionDelegate = genericActionDelegate, + actionHandlingComponent = actionHandlingComponent, + componentEventHandler = componentEventHandler + ) component.viewFlow.test { assertEquals(TestComponentViewType.VIEW_TYPE_1, awaitItem()) @@ -127,7 +133,12 @@ internal class BcmcComponentTest( fun `when action delegate view flow emits a value then component view flow should match that value`() = runTest { val actionDelegateViewFlow = MutableStateFlow(TestComponentViewType.VIEW_TYPE_1) whenever(genericActionDelegate.viewFlow) doReturn actionDelegateViewFlow - component = BcmcComponent(bcmcDelegate, genericActionDelegate, actionHandlingComponent, componentEventHandler) + component = BcmcComponentNew( + cardDelegate = cardDelegate, + genericActionDelegate = genericActionDelegate, + actionHandlingComponent = actionHandlingComponent, + componentEventHandler = componentEventHandler + ) component.viewFlow.test { // this value should match the value of the main delegate and not the action delegate @@ -144,20 +155,20 @@ internal class BcmcComponentTest( @Test fun `when isConfirmationRequired, then delegate is called`() { component.isConfirmationRequired() - verify(bcmcDelegate).isConfirmationRequired() + verify(cardDelegate).isConfirmationRequired() } @Test fun `when submit is called and active delegate is the payment delegate, then delegate onSubmit is called`() { - whenever(component.delegate).thenReturn(bcmcDelegate) + whenever(component.delegate).thenReturn(cardDelegate) component.submit() - verify(bcmcDelegate).onSubmit() + verify(cardDelegate).onSubmit() } @Test fun `when submit is called and active delegate is the action delegate, then delegate onSubmit is not called`() { whenever(component.delegate).thenReturn(genericActionDelegate) component.submit() - verify(bcmcDelegate, never()).onSubmit() + verify(cardDelegate, never()).onSubmit() } } diff --git a/bcmc/src/test/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperTest.kt b/bcmc/src/test/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperTest.kt index 88784bed80..64193a2af8 100644 --- a/bcmc/src/test/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperTest.kt +++ b/bcmc/src/test/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperTest.kt @@ -9,12 +9,20 @@ package com.adyen.checkout.bcmc.internal.ui.model import com.adyen.checkout.bcmc.BcmcConfiguration +import com.adyen.checkout.card.CVCVisibility +import com.adyen.checkout.card.CardBrand +import com.adyen.checkout.card.CardType +import com.adyen.checkout.card.KCPAuthVisibility +import com.adyen.checkout.card.SocialSecurityNumberVisibility +import com.adyen.checkout.card.StoredCVCVisibility +import com.adyen.checkout.card.internal.ui.model.CardComponentParams import com.adyen.checkout.components.core.Amount import com.adyen.checkout.components.core.internal.ui.model.AnalyticsParams import com.adyen.checkout.components.core.internal.ui.model.AnalyticsParamsLevel import com.adyen.checkout.components.core.internal.ui.model.GenericComponentParams import com.adyen.checkout.components.core.internal.ui.model.SessionParams import com.adyen.checkout.core.Environment +import com.adyen.checkout.ui.core.internal.ui.model.AddressParams import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest @@ -31,7 +39,7 @@ internal class BcmcComponentParamsMapperTest { val params = BcmcComponentParamsMapper(null, null).mapToParams(bcmcConfiguration, null) - val expected = getBcmcComponentParams() + val expected = getCardComponentParams() assertEquals(expected, params) } @@ -45,15 +53,17 @@ internal class BcmcComponentParamsMapperTest { .setHolderNameRequired(true) .setShowStorePaymentField(true) .setSubmitButtonVisible(false) + .setCvcVisibility(CVCVisibility.SHOW_FIRST) .build() val params = BcmcComponentParamsMapper(null, null).mapToParams(bcmcConfiguration, null) - val expected = getBcmcComponentParams( + val expected = getCardComponentParams( isHolderNameRequired = true, shopperReference = shopperReference, isStorePaymentFieldVisible = true, - isSubmitButtonVisible = false + isSubmitButtonVisible = false, + cvcVisibility = CVCVisibility.SHOW_FIRST ) assertEquals(expected, params) @@ -80,7 +90,7 @@ internal class BcmcComponentParamsMapperTest { val params = BcmcComponentParamsMapper(overrideParams, null).mapToParams(bcmcConfiguration, null) - val expected = getBcmcComponentParams( + val expected = getCardComponentParams( shopperLocale = Locale.GERMAN, environment = Environment.EUROPE, clientKey = TEST_CLIENT_KEY_2, @@ -117,7 +127,7 @@ internal class BcmcComponentParamsMapperTest { ) ) - val expected = getBcmcComponentParams(isStorePaymentFieldVisible = expectedValue) + val expected = getCardComponentParams(isStorePaymentFieldVisible = expectedValue) assertEquals(expected, params) } @@ -136,7 +146,7 @@ internal class BcmcComponentParamsMapperTest { // this is in practice DropInComponentParams, but we don't have access to it in this module and any // ComponentParams class can work - val overrideParams = dropInValue?.let { getBcmcComponentParams(amount = it) } + val overrideParams = dropInValue?.let { getCardComponentParams(amount = it) } val params = BcmcComponentParamsMapper(overrideParams, null).mapToParams( bcmcConfiguration, @@ -148,7 +158,7 @@ internal class BcmcComponentParamsMapperTest { ) ) - val expected = getBcmcComponentParams( + val expected = getCardComponentParams( amount = expectedValue ) @@ -162,7 +172,7 @@ internal class BcmcComponentParamsMapperTest { ) @Suppress("LongParameterList") - private fun getBcmcComponentParams( + private fun getCardComponentParams( shopperLocale: Locale = Locale.US, environment: Environment = Environment.TEST, clientKey: String = TEST_CLIENT_KEY_1, @@ -173,7 +183,8 @@ internal class BcmcComponentParamsMapperTest { isHolderNameRequired: Boolean = false, shopperReference: String? = null, isStorePaymentFieldVisible: Boolean = false, - ) = BcmcComponentParams( + cvcVisibility: CVCVisibility = CVCVisibility.ALWAYS_HIDE, + ) = CardComponentParams( shopperLocale = shopperLocale, environment = environment, clientKey = clientKey, @@ -183,7 +194,18 @@ internal class BcmcComponentParamsMapperTest { isSubmitButtonVisible = isSubmitButtonVisible, isHolderNameRequired = isHolderNameRequired, shopperReference = shopperReference, - isStorePaymentFieldVisible = isStorePaymentFieldVisible + isStorePaymentFieldVisible = isStorePaymentFieldVisible, + cvcVisibility = cvcVisibility, + addressParams = AddressParams.None, + installmentParams = null, + socialSecurityNumberVisibility = SocialSecurityNumberVisibility.HIDE, + kcpAuthVisibility = KCPAuthVisibility.HIDE, + storedCVCVisibility = StoredCVCVisibility.HIDE, + supportedCardBrands = listOf( + CardBrand(cardType = CardType.BCMC), + CardBrand(cardType = CardType.MAESTRO), + CardBrand(cardType = CardType.VISA) + ) ) companion object { From b7d21498c914d0276a253909512ae091a15d0038 Mon Sep 17 00:00:00 2001 From: ozgur <6615094+ozgur00@users.noreply.github.com> Date: Fri, 15 Sep 2023 11:56:19 +0200 Subject: [PATCH 18/22] Remove old bcmc files COAND-795 --- .../com/adyen/checkout/bcmc/BcmcComponent.kt | 90 +--- .../adyen/checkout/bcmc/BcmcComponentNew.kt | 33 -- .../adyen/checkout/bcmc/BcmcComponentState.kt | 15 +- .../adyen/checkout/bcmc/BcmcConfiguration.kt | 22 - .../provider/BcmcComponentProvider.kt | 154 +++--- .../provider/BcmcComponentProviderNew.kt | 274 ---------- .../checkout/bcmc/internal/ui/BcmcDelegate.kt | 43 -- .../bcmc/internal/ui/DefaultBcmcDelegate.kt | 296 ----------- .../internal/ui/model/BcmcComponentParams.kt | 29 -- .../ui/model/BcmcComponentParamsMapper.kt | 37 +- .../ui/model/BcmcComponentParamsMapperNew.kt | 88 ---- .../bcmc/internal/ui/model/BcmcInputData.kt | 18 - .../bcmc/internal/ui/model/BcmcOutputData.kt | 27 - .../bcmc/internal/ui/view/BcmcView.kt | 206 -------- .../adyen/checkout/bcmc/BcmcComponentTest.kt | 10 +- .../internal/ui/DefaultBcmcDelegateTest.kt | 487 ------------------ .../ui/model/BcmcComponentParamsMapperTest.kt | 9 +- .../com/adyen/checkout/card/CardComponent.kt | 3 + .../adyen/checkout/card/CardComponentState.kt | 8 +- .../adyen/checkout/card/CardConfiguration.kt | 76 ++- .../internal/data/api/BinLookupService.kt | 2 + .../api/DefaultDetectCardTypeRepository.kt | 2 + .../data/api/DetectCardTypeRepository.kt | 2 + .../internal/data/model/BinLookupRequest.kt | 2 + .../internal/data/model/BinLookupResponse.kt | 2 + .../internal/data/model/DetectedCardType.kt | 2 + .../checkout/card/internal/ui/CardDelegate.kt | 2 + .../card/internal/ui/DefaultCardDelegate.kt | 41 +- .../card/internal/ui/StoredCardDelegate.kt | 2 +- .../{ => internal/ui/model}/CVCVisibility.kt | 8 +- .../internal/ui/model/CardComponentParams.kt | 4 +- .../ui/model/CardComponentParamsMapper.kt | 14 +- .../card/internal/ui/model/CardInputData.kt | 2 + .../card/internal/ui/model/CardListItem.kt | 2 + .../card/internal/ui/model/CardOutputData.kt | 2 + .../internal/ui/model/InputFieldUIState.kt | 3 + .../internal/ui/model/InstallmentOption.kt | 3 + .../ui/model/InstallmentOptionParams.kt | 2 + .../internal/ui/model/InstallmentParams.kt | 2 + .../card/internal/ui/view/CardView.kt | 9 + .../ui/view/InstallmentListAdapter.kt | 3 + .../internal/ui/DefaultCardDelegateTest.kt | 35 +- .../internal/ui/StoredCardDelegateTest.kt | 10 +- .../ui/model/CardComponentParamsMapperTest.kt | 8 +- .../provider/ComponentParsingProvider.kt | 13 +- .../CheckoutConfigurationProvider.kt | 2 - 46 files changed, 298 insertions(+), 1806 deletions(-) delete mode 100644 bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcComponentNew.kt delete mode 100644 bcmc/src/main/java/com/adyen/checkout/bcmc/internal/provider/BcmcComponentProviderNew.kt delete mode 100644 bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/BcmcDelegate.kt delete mode 100644 bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/DefaultBcmcDelegate.kt delete mode 100644 bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParams.kt delete mode 100644 bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperNew.kt delete mode 100644 bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcInputData.kt delete mode 100644 bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcOutputData.kt delete mode 100644 bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/view/BcmcView.kt delete mode 100644 bcmc/src/test/java/com/adyen/checkout/bcmc/internal/ui/DefaultBcmcDelegateTest.kt rename card/src/main/java/com/adyen/checkout/card/{ => internal/ui/model}/CVCVisibility.kt (54%) diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcComponent.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcComponent.kt index e784956fdf..7f1a786332 100644 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcComponent.kt +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcComponent.kt @@ -1,106 +1,36 @@ /* - * Copyright (c) 2019 Adyen N.V. + * Copyright (c) 2023 Adyen N.V. * * This file is open source and available under the MIT license. See the LICENSE file for more info. * - * Created by arman on 18/9/2019. + * Created by ozgur on 22/8/2023. */ + package com.adyen.checkout.bcmc -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.adyen.checkout.action.core.internal.ActionHandlingComponent import com.adyen.checkout.action.core.internal.DefaultActionHandlingComponent import com.adyen.checkout.action.core.internal.ui.GenericActionDelegate import com.adyen.checkout.bcmc.internal.provider.BcmcComponentProvider -import com.adyen.checkout.bcmc.internal.ui.BcmcDelegate -import com.adyen.checkout.card.CardBrand -import com.adyen.checkout.card.CardType +import com.adyen.checkout.card.CardComponent +import com.adyen.checkout.card.internal.ui.CardDelegate import com.adyen.checkout.components.core.PaymentMethodTypes -import com.adyen.checkout.components.core.internal.ButtonComponent import com.adyen.checkout.components.core.internal.ComponentEventHandler import com.adyen.checkout.components.core.internal.PaymentComponent -import com.adyen.checkout.components.core.internal.PaymentComponentEvent -import com.adyen.checkout.components.core.internal.toActionCallback -import com.adyen.checkout.components.core.internal.ui.ComponentDelegate -import com.adyen.checkout.core.internal.util.LogUtil -import com.adyen.checkout.core.internal.util.Logger -import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate -import com.adyen.checkout.ui.core.internal.ui.ComponentViewType -import com.adyen.checkout.ui.core.internal.ui.ViewableComponent -import com.adyen.checkout.ui.core.internal.util.mergeViewFlows -import kotlinx.coroutines.flow.Flow /** * A [PaymentComponent] that supports the [PaymentMethodTypes.BCMC] payment method. */ -class BcmcComponent internal constructor( - private val bcmcDelegate: BcmcDelegate, - private val genericActionDelegate: GenericActionDelegate, - private val actionHandlingComponent: DefaultActionHandlingComponent, +class BcmcComponent( + cardDelegate: CardDelegate, + genericActionDelegate: GenericActionDelegate, + actionHandlingComponent: DefaultActionHandlingComponent, internal val componentEventHandler: ComponentEventHandler, -) : ViewModel(), - PaymentComponent, - ViewableComponent, - ButtonComponent, - ActionHandlingComponent by actionHandlingComponent { - - override val delegate: ComponentDelegate get() = actionHandlingComponent.activeDelegate - - override val viewFlow: Flow = mergeViewFlows( - viewModelScope, - bcmcDelegate.viewFlow, - genericActionDelegate.viewFlow, - ) - - init { - bcmcDelegate.initialize(viewModelScope) - genericActionDelegate.initialize(viewModelScope) - componentEventHandler.initialize(viewModelScope) - } - - internal fun observe( - lifecycleOwner: LifecycleOwner, - callback: (PaymentComponentEvent) -> Unit - ) { - bcmcDelegate.observe(lifecycleOwner, viewModelScope, callback) - genericActionDelegate.observe(lifecycleOwner, viewModelScope, callback.toActionCallback()) - } - - internal fun removeObserver() { - bcmcDelegate.removeObserver() - genericActionDelegate.removeObserver() - } - - override fun isConfirmationRequired(): Boolean = bcmcDelegate.isConfirmationRequired() - - override fun submit() { - (delegate as? ButtonDelegate)?.onSubmit() ?: Logger.e(TAG, "Component is currently not submittable, ignoring.") - } - - override fun setInteractionBlocked(isInteractionBlocked: Boolean) { - (delegate as? BcmcDelegate)?.setInteractionBlocked(isInteractionBlocked) - ?: Logger.e(TAG, "Payment component is not interactable, ignoring.") - } - - override fun onCleared() { - super.onCleared() - Logger.d(TAG, "onCleared") - bcmcDelegate.onCleared() - genericActionDelegate.onCleared() - componentEventHandler.onCleared() - } - +) : CardComponent(cardDelegate, genericActionDelegate, actionHandlingComponent, componentEventHandler) { companion object { - private val TAG = LogUtil.getTag() - @JvmField val PROVIDER = BcmcComponentProvider() @JvmField val PAYMENT_METHOD_TYPES = listOf(PaymentMethodTypes.BCMC) - - internal val SUPPORTED_CARD_TYPE = CardBrand(cardType = CardType.BCMC) } } diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcComponentNew.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcComponentNew.kt deleted file mode 100644 index 752cfea507..0000000000 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcComponentNew.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2023 Adyen N.V. - * - * This file is open source and available under the MIT license. See the LICENSE file for more info. - * - * Created by ozgur on 22/8/2023. - */ - -package com.adyen.checkout.bcmc - -import com.adyen.checkout.action.core.internal.DefaultActionHandlingComponent -import com.adyen.checkout.action.core.internal.ui.GenericActionDelegate -import com.adyen.checkout.bcmc.internal.provider.BcmcComponentProviderNew -import com.adyen.checkout.card.CardComponent -import com.adyen.checkout.card.CardComponentState -import com.adyen.checkout.card.internal.ui.CardDelegate -import com.adyen.checkout.components.core.PaymentMethodTypes -import com.adyen.checkout.components.core.internal.ComponentEventHandler - -class BcmcComponentNew( - private val cardDelegate: CardDelegate, - private val genericActionDelegate: GenericActionDelegate, - private val actionHandlingComponent: DefaultActionHandlingComponent, - internal val componentEventHandler: ComponentEventHandler, -) : CardComponent(cardDelegate, genericActionDelegate, actionHandlingComponent, componentEventHandler) { - companion object { - @JvmField - val PROVIDER = BcmcComponentProviderNew() - - @JvmField - val PAYMENT_METHOD_TYPES = listOf(PaymentMethodTypes.BCMC) - } -} diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcComponentState.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcComponentState.kt index 53e71d76ba..1cfdb7d0d6 100644 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcComponentState.kt +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcComponentState.kt @@ -3,20 +3,11 @@ * * This file is open source and available under the MIT license. See the LICENSE file for more info. * - * Created by ozgur on 20/2/2023. + * Created by ozgur on 27/9/2023. */ package com.adyen.checkout.bcmc -import com.adyen.checkout.components.core.PaymentComponentData -import com.adyen.checkout.components.core.PaymentComponentState -import com.adyen.checkout.components.core.paymentmethod.CardPaymentMethod +import com.adyen.checkout.card.CardComponentState -/** - * Represents the state of [BcmcComponent]. - */ -data class BcmcComponentState( - override val data: PaymentComponentData, - override val isInputValid: Boolean, - override val isReady: Boolean -) : PaymentComponentState +typealias BcmcComponentState = CardComponentState diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcConfiguration.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcConfiguration.kt index c7de43fa14..6c0a5e1c9c 100644 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcConfiguration.kt +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcConfiguration.kt @@ -10,8 +10,6 @@ package com.adyen.checkout.bcmc import android.content.Context import com.adyen.checkout.action.core.GenericActionConfiguration import com.adyen.checkout.action.core.internal.ActionHandlingPaymentMethodConfigurationBuilder -import com.adyen.checkout.card.CVCVisibility -import com.adyen.checkout.card.CardConfiguration import com.adyen.checkout.components.core.Amount import com.adyen.checkout.components.core.AnalyticsConfiguration import com.adyen.checkout.components.core.PaymentComponentData @@ -37,7 +35,6 @@ class BcmcConfiguration private constructor( val isHolderNameRequired: Boolean?, val shopperReference: String?, val isStorePaymentFieldVisible: Boolean?, - val cvcVisibility: CVCVisibility?, internal val genericActionConfiguration: GenericActionConfiguration, ) : Configuration, ButtonConfiguration { @@ -52,7 +49,6 @@ class BcmcConfiguration private constructor( private var showStorePaymentField: Boolean? = null private var shopperReference: String? = null private var isSubmitButtonVisible: Boolean? = null - private var cvcVisibility: CVCVisibility? = null /** * Alternative constructor that uses the [context] to fetch the user locale and use it as a shopper locale. @@ -121,23 +117,6 @@ class BcmcConfiguration private constructor( return this } - // TODO docs - /** - * Set if the CVC field should be hidden from the Component and not requested to the shopper on a regular - * payment. - * Note that this might have implications for the risk of the transaction. Talk to Adyen Support before enabling - * this. - * - * Default is false. - * - * @param hideCvc If CVC should be hidden or not. - * @return [CardConfiguration.Builder] - */ - fun setCvcVisibility(cvcVisibility: CVCVisibility): Builder { - this.cvcVisibility = cvcVisibility - return this - } - /** * Sets if submit button will be visible or not. * @@ -166,7 +145,6 @@ class BcmcConfiguration private constructor( shopperReference = shopperReference, isStorePaymentFieldVisible = showStorePaymentField, isSubmitButtonVisible = isSubmitButtonVisible, - cvcVisibility = cvcVisibility, genericActionConfiguration = genericActionConfigurationBuilder.build(), ) } diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/provider/BcmcComponentProvider.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/provider/BcmcComponentProvider.kt index 8badfe4aa2..377a2ad937 100644 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/provider/BcmcComponentProvider.kt +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/provider/BcmcComponentProvider.kt @@ -1,9 +1,9 @@ /* - * Copyright (c) 2019 Adyen N.V. + * Copyright (c) 2023 Adyen N.V. * * This file is open source and available under the MIT license. See the LICENSE file for more info. * - * Created by arman on 18/9/2019. + * Created by ozgur on 22/8/2023. */ package com.adyen.checkout.bcmc.internal.provider @@ -19,9 +19,11 @@ import com.adyen.checkout.action.core.internal.provider.GenericActionComponentPr import com.adyen.checkout.bcmc.BcmcComponent import com.adyen.checkout.bcmc.BcmcComponentState import com.adyen.checkout.bcmc.BcmcConfiguration -import com.adyen.checkout.bcmc.internal.ui.DefaultBcmcDelegate import com.adyen.checkout.bcmc.internal.ui.model.BcmcComponentParamsMapper +import com.adyen.checkout.card.internal.data.api.BinLookupService +import com.adyen.checkout.card.internal.data.api.DefaultDetectCardTypeRepository import com.adyen.checkout.card.internal.ui.CardValidationMapper +import com.adyen.checkout.card.internal.ui.DefaultCardDelegate import com.adyen.checkout.components.core.ComponentCallback import com.adyen.checkout.components.core.Order import com.adyen.checkout.components.core.PaymentMethod @@ -54,6 +56,8 @@ import com.adyen.checkout.sessions.core.internal.data.api.SessionRepository import com.adyen.checkout.sessions.core.internal.data.api.SessionService import com.adyen.checkout.sessions.core.internal.provider.SessionPaymentComponentProvider import com.adyen.checkout.sessions.core.internal.ui.model.SessionParamsFactory +import com.adyen.checkout.ui.core.internal.data.api.AddressService +import com.adyen.checkout.ui.core.internal.data.api.DefaultAddressRepository import com.adyen.checkout.ui.core.internal.ui.SubmitHandler class BcmcComponentProvider @@ -78,6 +82,7 @@ constructor( private val componentParamsMapper = BcmcComponentParamsMapper(overrideComponentParams, overrideSessionParams) + @Suppress("LongMethod") override fun get( savedStateRegistryOwner: SavedStateRegistryOwner, viewModelStoreOwner: ViewModelStoreOwner, @@ -90,39 +95,45 @@ constructor( key: String?, ): BcmcComponent { assertSupported(paymentMethod) + val bcmcFactory = viewModelFactory(savedStateRegistryOwner, null) { savedStateHandle -> + val componentParams = componentParamsMapper.mapToParams(configuration, null) + val httpClient = HttpClientFactory.getHttpClient(componentParams.environment) + val publicKeyService = PublicKeyService(httpClient) + val publicKeyRepository = DefaultPublicKeyRepository(publicKeyService) + val cardValidationMapper = CardValidationMapper() + val dateGenerator = DateGenerator() + val clientSideEncrypter = ClientSideEncrypter() + val genericEncrypter = DefaultGenericEncrypter(clientSideEncrypter, dateGenerator) + val cardEncrypter = DefaultCardEncrypter(genericEncrypter) + val addressService = AddressService(httpClient) + val addressRepository = DefaultAddressRepository(addressService) + val binLookupService = BinLookupService(httpClient) + val detectCardTypeRepository = DefaultDetectCardTypeRepository(cardEncrypter, binLookupService) - val componentParams = componentParamsMapper.mapToParams(configuration, null) - val httpClient = HttpClientFactory.getHttpClient(componentParams.environment) - val publicKeyService = PublicKeyService(httpClient) - val publicKeyRepository = DefaultPublicKeyRepository(publicKeyService) - val cardValidationMapper = CardValidationMapper() - val dateGenerator = DateGenerator() - val clientSideEncrypter = ClientSideEncrypter() - val genericEncrypter = DefaultGenericEncrypter(clientSideEncrypter, dateGenerator) - val cardEncrypter = DefaultCardEncrypter(genericEncrypter) - - val analyticsRepository = analyticsRepository ?: DefaultAnalyticsRepository( - analyticsRepositoryData = AnalyticsRepositoryData( - application = application, - componentParams = componentParams, - paymentMethod = paymentMethod, - ), - analyticsService = AnalyticsService( - HttpClientFactory.getAnalyticsHttpClient(componentParams.environment) - ), - analyticsMapper = AnalyticsMapper(), - ) + val analyticsRepository = analyticsRepository ?: DefaultAnalyticsRepository( + analyticsRepositoryData = AnalyticsRepositoryData( + application = application, + componentParams = componentParams, + paymentMethod = paymentMethod, + ), + analyticsService = AnalyticsService( + HttpClientFactory.getAnalyticsHttpClient(componentParams.environment) + ), + analyticsMapper = AnalyticsMapper(), + ) - val bcmcFactory = viewModelFactory(savedStateRegistryOwner, null) { savedStateHandle -> - val bcmcDelegate = DefaultBcmcDelegate( + val cardDelegate = DefaultCardDelegate( observerRepository = PaymentObserverRepository(), - paymentMethod = paymentMethod, - order = order, publicKeyRepository = publicKeyRepository, componentParams = componentParams, + paymentMethod = paymentMethod, + order = order, + analyticsRepository = analyticsRepository, + addressRepository = addressRepository, + detectCardTypeRepository = detectCardTypeRepository, cardValidationMapper = cardValidationMapper, cardEncrypter = cardEncrypter, - analyticsRepository = analyticsRepository, + genericEncrypter = genericEncrypter, submitHandler = SubmitHandler(savedStateHandle) ) @@ -133,13 +144,16 @@ constructor( ) BcmcComponent( - bcmcDelegate = bcmcDelegate, + cardDelegate = cardDelegate, genericActionDelegate = genericActionDelegate, - actionHandlingComponent = DefaultActionHandlingComponent(genericActionDelegate, bcmcDelegate), + actionHandlingComponent = DefaultActionHandlingComponent(genericActionDelegate, cardDelegate), componentEventHandler = DefaultComponentEventHandler(), ) } - return ViewModelProvider(viewModelStoreOwner, bcmcFactory)[key, BcmcComponent::class.java].also { component -> + return ViewModelProvider( + viewModelStoreOwner, + bcmcFactory + )[key, BcmcComponent::class.java].also { component -> component.observe(lifecycleOwner) { component.componentEventHandler.onPaymentComponentEvent(it, componentCallback) } @@ -159,43 +173,49 @@ constructor( key: String? ): BcmcComponent { assertSupported(paymentMethod) + val bcmcFactory = viewModelFactory(savedStateRegistryOwner, null) { savedStateHandle -> + val componentParams = componentParamsMapper.mapToParams( + bcmcConfiguration = configuration, + sessionParams = SessionParamsFactory.create(checkoutSession) + ) + val httpClient = HttpClientFactory.getHttpClient(componentParams.environment) + val publicKeyService = PublicKeyService(httpClient) + val publicKeyRepository = DefaultPublicKeyRepository(publicKeyService) + val cardValidationMapper = CardValidationMapper() + val dateGenerator = DateGenerator() + val clientSideEncrypter = ClientSideEncrypter() + val genericEncrypter = DefaultGenericEncrypter(clientSideEncrypter, dateGenerator) + val cardEncrypter = DefaultCardEncrypter(genericEncrypter) + val addressService = AddressService(httpClient) + val addressRepository = DefaultAddressRepository(addressService) + val binLookupService = BinLookupService(httpClient) + val detectCardTypeRepository = DefaultDetectCardTypeRepository(cardEncrypter, binLookupService) - val componentParams = componentParamsMapper.mapToParams( - bcmcConfiguration = configuration, - sessionParams = SessionParamsFactory.create(checkoutSession) - ) - val httpClient = HttpClientFactory.getHttpClient(componentParams.environment) - val publicKeyService = PublicKeyService(httpClient) - val publicKeyRepository = DefaultPublicKeyRepository(publicKeyService) - val cardValidationMapper = CardValidationMapper() - val dateGenerator = DateGenerator() - val clientSideEncrypter = ClientSideEncrypter() - val genericEncrypter = DefaultGenericEncrypter(clientSideEncrypter, dateGenerator) - val cardEncrypter = DefaultCardEncrypter(genericEncrypter) - - val analyticsRepository = analyticsRepository ?: DefaultAnalyticsRepository( - analyticsRepositoryData = AnalyticsRepositoryData( - application = application, - componentParams = componentParams, - paymentMethod = paymentMethod, - sessionId = checkoutSession.sessionSetupResponse.id, - ), - analyticsService = AnalyticsService( - HttpClientFactory.getAnalyticsHttpClient(componentParams.environment) - ), - analyticsMapper = AnalyticsMapper(), - ) + val analyticsRepository = analyticsRepository ?: DefaultAnalyticsRepository( + analyticsRepositoryData = AnalyticsRepositoryData( + application = application, + componentParams = componentParams, + paymentMethod = paymentMethod, + sessionId = checkoutSession.sessionSetupResponse.id, + ), + analyticsService = AnalyticsService( + HttpClientFactory.getAnalyticsHttpClient(componentParams.environment) + ), + analyticsMapper = AnalyticsMapper(), + ) - val bcmcFactory = viewModelFactory(savedStateRegistryOwner, null) { savedStateHandle -> - val bcmcDelegate = DefaultBcmcDelegate( + val cardDelegate = DefaultCardDelegate( observerRepository = PaymentObserverRepository(), - paymentMethod = paymentMethod, - order = checkoutSession.order, publicKeyRepository = publicKeyRepository, componentParams = componentParams, + paymentMethod = paymentMethod, + order = checkoutSession.order, + analyticsRepository = analyticsRepository, + addressRepository = addressRepository, + detectCardTypeRepository = detectCardTypeRepository, cardValidationMapper = cardValidationMapper, cardEncrypter = cardEncrypter, - analyticsRepository = analyticsRepository, + genericEncrypter = genericEncrypter, submitHandler = SubmitHandler(savedStateHandle) ) @@ -225,13 +245,17 @@ constructor( ) BcmcComponent( - bcmcDelegate = bcmcDelegate, + cardDelegate = cardDelegate, genericActionDelegate = genericActionDelegate, - actionHandlingComponent = DefaultActionHandlingComponent(genericActionDelegate, bcmcDelegate), + actionHandlingComponent = DefaultActionHandlingComponent(genericActionDelegate, cardDelegate), componentEventHandler = sessionComponentEventHandler, ) } - return ViewModelProvider(viewModelStoreOwner, bcmcFactory)[key, BcmcComponent::class.java].also { component -> + + return ViewModelProvider( + viewModelStoreOwner, + bcmcFactory + )[key, BcmcComponent::class.java].also { component -> component.observe(lifecycleOwner) { component.componentEventHandler.onPaymentComponentEvent(it, componentCallback) } diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/provider/BcmcComponentProviderNew.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/provider/BcmcComponentProviderNew.kt deleted file mode 100644 index 1d30c62837..0000000000 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/provider/BcmcComponentProviderNew.kt +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Copyright (c) 2023 Adyen N.V. - * - * This file is open source and available under the MIT license. See the LICENSE file for more info. - * - * Created by ozgur on 22/8/2023. - */ - -package com.adyen.checkout.bcmc.internal.provider - -import android.app.Application -import androidx.annotation.RestrictTo -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.ViewModelStoreOwner -import androidx.savedstate.SavedStateRegistryOwner -import com.adyen.checkout.action.core.internal.DefaultActionHandlingComponent -import com.adyen.checkout.action.core.internal.provider.GenericActionComponentProvider -import com.adyen.checkout.bcmc.BcmcComponentNew -import com.adyen.checkout.bcmc.BcmcConfiguration -import com.adyen.checkout.bcmc.internal.ui.model.BcmcComponentParamsMapperNew -import com.adyen.checkout.card.CardComponentState -import com.adyen.checkout.card.internal.data.api.BinLookupService -import com.adyen.checkout.card.internal.data.api.DefaultDetectCardTypeRepository -import com.adyen.checkout.card.internal.ui.CardValidationMapper -import com.adyen.checkout.card.internal.ui.DefaultCardDelegate -import com.adyen.checkout.components.core.ComponentCallback -import com.adyen.checkout.components.core.Order -import com.adyen.checkout.components.core.PaymentMethod -import com.adyen.checkout.components.core.internal.DefaultComponentEventHandler -import com.adyen.checkout.components.core.internal.PaymentObserverRepository -import com.adyen.checkout.components.core.internal.data.api.AnalyticsMapper -import com.adyen.checkout.components.core.internal.data.api.AnalyticsRepository -import com.adyen.checkout.components.core.internal.data.api.AnalyticsRepositoryData -import com.adyen.checkout.components.core.internal.data.api.AnalyticsService -import com.adyen.checkout.components.core.internal.data.api.DefaultAnalyticsRepository -import com.adyen.checkout.components.core.internal.data.api.DefaultPublicKeyRepository -import com.adyen.checkout.components.core.internal.data.api.PublicKeyService -import com.adyen.checkout.components.core.internal.provider.PaymentComponentProvider -import com.adyen.checkout.components.core.internal.ui.model.ComponentParams -import com.adyen.checkout.components.core.internal.ui.model.SessionParams -import com.adyen.checkout.components.core.internal.util.get -import com.adyen.checkout.components.core.internal.util.viewModelFactory -import com.adyen.checkout.core.exception.ComponentException -import com.adyen.checkout.core.internal.data.api.HttpClientFactory -import com.adyen.checkout.cse.internal.ClientSideEncrypter -import com.adyen.checkout.cse.internal.DateGenerator -import com.adyen.checkout.cse.internal.DefaultCardEncrypter -import com.adyen.checkout.cse.internal.DefaultGenericEncrypter -import com.adyen.checkout.sessions.core.CheckoutSession -import com.adyen.checkout.sessions.core.SessionComponentCallback -import com.adyen.checkout.sessions.core.internal.SessionComponentEventHandler -import com.adyen.checkout.sessions.core.internal.SessionInteractor -import com.adyen.checkout.sessions.core.internal.SessionSavedStateHandleContainer -import com.adyen.checkout.sessions.core.internal.data.api.SessionRepository -import com.adyen.checkout.sessions.core.internal.data.api.SessionService -import com.adyen.checkout.sessions.core.internal.provider.SessionPaymentComponentProvider -import com.adyen.checkout.sessions.core.internal.ui.model.SessionParamsFactory -import com.adyen.checkout.ui.core.internal.data.api.AddressService -import com.adyen.checkout.ui.core.internal.data.api.DefaultAddressRepository -import com.adyen.checkout.ui.core.internal.ui.SubmitHandler - -class BcmcComponentProviderNew -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -constructor( - overrideComponentParams: ComponentParams? = null, - overrideSessionParams: SessionParams? = null, - private val analyticsRepository: AnalyticsRepository? = null, -) : - PaymentComponentProvider< - BcmcComponentNew, - BcmcConfiguration, - CardComponentState, - ComponentCallback - >, - SessionPaymentComponentProvider< - BcmcComponentNew, - BcmcConfiguration, - CardComponentState, - SessionComponentCallback - > { - - private val componentParamsMapper = BcmcComponentParamsMapperNew(overrideComponentParams, overrideSessionParams) - - @Suppress("LongMethod") - override fun get( - savedStateRegistryOwner: SavedStateRegistryOwner, - viewModelStoreOwner: ViewModelStoreOwner, - lifecycleOwner: LifecycleOwner, - paymentMethod: PaymentMethod, - configuration: BcmcConfiguration, - application: Application, - componentCallback: ComponentCallback, - order: Order?, - key: String?, - ): BcmcComponentNew { - assertSupported(paymentMethod) - - val componentParams = componentParamsMapper.mapToParams(configuration, null) - val httpClient = HttpClientFactory.getHttpClient(componentParams.environment) - val publicKeyService = PublicKeyService(httpClient) - val publicKeyRepository = DefaultPublicKeyRepository(publicKeyService) - val cardValidationMapper = CardValidationMapper() - val dateGenerator = DateGenerator() - val clientSideEncrypter = ClientSideEncrypter() - val genericEncrypter = DefaultGenericEncrypter(clientSideEncrypter, dateGenerator) - val cardEncrypter = DefaultCardEncrypter(genericEncrypter) - val addressService = AddressService(httpClient) - val addressRepository = DefaultAddressRepository(addressService) - val binLookupService = BinLookupService(httpClient) - val detectCardTypeRepository = DefaultDetectCardTypeRepository(cardEncrypter, binLookupService) - - val analyticsRepository = analyticsRepository ?: DefaultAnalyticsRepository( - analyticsRepositoryData = AnalyticsRepositoryData( - application = application, - componentParams = componentParams, - paymentMethod = paymentMethod, - ), - analyticsService = AnalyticsService( - HttpClientFactory.getAnalyticsHttpClient(componentParams.environment) - ), - analyticsMapper = AnalyticsMapper(), - ) - - val bcmcFactory = viewModelFactory(savedStateRegistryOwner, null) { savedStateHandle -> - val cardDelegate = DefaultCardDelegate( - observerRepository = PaymentObserverRepository(), - publicKeyRepository = publicKeyRepository, - componentParams = componentParams, - paymentMethod = paymentMethod, - order = order, - analyticsRepository = analyticsRepository, - addressRepository = addressRepository, - detectCardTypeRepository = detectCardTypeRepository, - cardValidationMapper = cardValidationMapper, - cardEncrypter = cardEncrypter, - genericEncrypter = genericEncrypter, - submitHandler = SubmitHandler(savedStateHandle) - ) - - val genericActionDelegate = GenericActionComponentProvider(componentParams).getDelegate( - configuration = configuration.genericActionConfiguration, - savedStateHandle = savedStateHandle, - application = application, - ) - - BcmcComponentNew( - cardDelegate = cardDelegate, - genericActionDelegate = genericActionDelegate, - actionHandlingComponent = DefaultActionHandlingComponent(genericActionDelegate, cardDelegate), - componentEventHandler = DefaultComponentEventHandler(), - ) - } - return ViewModelProvider( - viewModelStoreOwner, - bcmcFactory - )[key, BcmcComponentNew::class.java].also { component -> - component.observe(lifecycleOwner) { - component.componentEventHandler.onPaymentComponentEvent(it, componentCallback) - } - } - } - - @Suppress("LongMethod") - override fun get( - savedStateRegistryOwner: SavedStateRegistryOwner, - viewModelStoreOwner: ViewModelStoreOwner, - lifecycleOwner: LifecycleOwner, - checkoutSession: CheckoutSession, - paymentMethod: PaymentMethod, - configuration: BcmcConfiguration, - application: Application, - componentCallback: SessionComponentCallback, - key: String? - ): BcmcComponentNew { - assertSupported(paymentMethod) - - val componentParams = componentParamsMapper.mapToParams( - bcmcConfiguration = configuration, - sessionParams = SessionParamsFactory.create(checkoutSession) - ) - val httpClient = HttpClientFactory.getHttpClient(componentParams.environment) - val publicKeyService = PublicKeyService(httpClient) - val publicKeyRepository = DefaultPublicKeyRepository(publicKeyService) - val cardValidationMapper = CardValidationMapper() - val dateGenerator = DateGenerator() - val clientSideEncrypter = ClientSideEncrypter() - val genericEncrypter = DefaultGenericEncrypter(clientSideEncrypter, dateGenerator) - val cardEncrypter = DefaultCardEncrypter(genericEncrypter) - val addressService = AddressService(httpClient) - val addressRepository = DefaultAddressRepository(addressService) - val binLookupService = BinLookupService(httpClient) - val detectCardTypeRepository = DefaultDetectCardTypeRepository(cardEncrypter, binLookupService) - - val analyticsRepository = analyticsRepository ?: DefaultAnalyticsRepository( - analyticsRepositoryData = AnalyticsRepositoryData( - application = application, - componentParams = componentParams, - paymentMethod = paymentMethod, - ), - analyticsService = AnalyticsService( - HttpClientFactory.getAnalyticsHttpClient(componentParams.environment) - ), - analyticsMapper = AnalyticsMapper(), - ) - - val bcmcFactory = viewModelFactory(savedStateRegistryOwner, null) { savedStateHandle -> - val cardDelegate = DefaultCardDelegate( - observerRepository = PaymentObserverRepository(), - publicKeyRepository = publicKeyRepository, - componentParams = componentParams, - paymentMethod = paymentMethod, - order = checkoutSession.order, - analyticsRepository = analyticsRepository, - addressRepository = addressRepository, - detectCardTypeRepository = detectCardTypeRepository, - cardValidationMapper = cardValidationMapper, - cardEncrypter = cardEncrypter, - genericEncrypter = genericEncrypter, - submitHandler = SubmitHandler(savedStateHandle) - ) - - val genericActionDelegate = GenericActionComponentProvider(componentParams).getDelegate( - configuration = configuration.genericActionConfiguration, - savedStateHandle = savedStateHandle, - application = application, - ) - - val sessionSavedStateHandleContainer = SessionSavedStateHandleContainer( - savedStateHandle = savedStateHandle, - checkoutSession = checkoutSession, - ) - - val sessionInteractor = SessionInteractor( - sessionRepository = SessionRepository( - sessionService = SessionService(httpClient), - clientKey = componentParams.clientKey, - ), - sessionModel = sessionSavedStateHandleContainer.getSessionModel(), - isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false - ) - - val sessionComponentEventHandler = SessionComponentEventHandler( - sessionInteractor = sessionInteractor, - sessionSavedStateHandleContainer = sessionSavedStateHandleContainer, - ) - - BcmcComponentNew( - cardDelegate = cardDelegate, - genericActionDelegate = genericActionDelegate, - actionHandlingComponent = DefaultActionHandlingComponent(genericActionDelegate, cardDelegate), - componentEventHandler = sessionComponentEventHandler, - ) - } - return ViewModelProvider( - viewModelStoreOwner, - bcmcFactory - )[key, BcmcComponentNew::class.java].also { component -> - component.observe(lifecycleOwner) { - component.componentEventHandler.onPaymentComponentEvent(it, componentCallback) - } - } - } - - private fun assertSupported(paymentMethod: PaymentMethod) { - if (!isPaymentMethodSupported(paymentMethod)) { - throw ComponentException("Unsupported payment method ${paymentMethod.type}") - } - } - - override fun isPaymentMethodSupported(paymentMethod: PaymentMethod): Boolean { - return BcmcComponentNew.PAYMENT_METHOD_TYPES.contains(paymentMethod.type) - } -} diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/BcmcDelegate.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/BcmcDelegate.kt deleted file mode 100644 index 00cb78d269..0000000000 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/BcmcDelegate.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2022 Adyen N.V. - * - * This file is open source and available under the MIT license. See the LICENSE file for more info. - * - * Created by oscars on 8/7/2022. - */ - -package com.adyen.checkout.bcmc.internal.ui - -import com.adyen.checkout.bcmc.BcmcComponentState -import com.adyen.checkout.bcmc.internal.ui.model.BcmcComponentParams -import com.adyen.checkout.bcmc.internal.ui.model.BcmcInputData -import com.adyen.checkout.bcmc.internal.ui.model.BcmcOutputData -import com.adyen.checkout.components.core.internal.ui.PaymentComponentDelegate -import com.adyen.checkout.core.exception.CheckoutException -import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate -import com.adyen.checkout.ui.core.internal.ui.UIStateDelegate -import com.adyen.checkout.ui.core.internal.ui.ViewProvidingDelegate -import kotlinx.coroutines.flow.Flow - -internal interface BcmcDelegate : - PaymentComponentDelegate, - ViewProvidingDelegate, - ButtonDelegate, - UIStateDelegate { - - override val componentParams: BcmcComponentParams - - val outputData: BcmcOutputData - - val outputDataFlow: Flow - - val componentStateFlow: Flow - - val exceptionFlow: Flow - - fun isCardNumberSupported(cardNumber: String?): Boolean - - fun updateInputData(update: BcmcInputData.() -> Unit) - - fun setInteractionBlocked(isInteractionBlocked: Boolean) -} diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/DefaultBcmcDelegate.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/DefaultBcmcDelegate.kt deleted file mode 100644 index 36d3877425..0000000000 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/DefaultBcmcDelegate.kt +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright (c) 2022 Adyen N.V. - * - * This file is open source and available under the MIT license. See the LICENSE file for more info. - * - * Created by oscars on 8/7/2022. - */ - -package com.adyen.checkout.bcmc.internal.ui - -import androidx.annotation.VisibleForTesting -import androidx.lifecycle.LifecycleOwner -import com.adyen.checkout.bcmc.BcmcComponent -import com.adyen.checkout.bcmc.BcmcComponentState -import com.adyen.checkout.bcmc.internal.ui.model.BcmcComponentParams -import com.adyen.checkout.bcmc.internal.ui.model.BcmcInputData -import com.adyen.checkout.bcmc.internal.ui.model.BcmcOutputData -import com.adyen.checkout.card.CardBrand -import com.adyen.checkout.card.R -import com.adyen.checkout.card.internal.data.model.Brand -import com.adyen.checkout.card.internal.ui.CardValidationMapper -import com.adyen.checkout.card.internal.ui.model.ExpiryDate -import com.adyen.checkout.card.internal.util.CardValidationUtils -import com.adyen.checkout.components.core.Order -import com.adyen.checkout.components.core.PaymentComponentData -import com.adyen.checkout.components.core.PaymentMethod -import com.adyen.checkout.components.core.PaymentMethodTypes -import com.adyen.checkout.components.core.internal.PaymentComponentEvent -import com.adyen.checkout.components.core.internal.PaymentObserverRepository -import com.adyen.checkout.components.core.internal.data.api.AnalyticsRepository -import com.adyen.checkout.components.core.internal.data.api.PublicKeyRepository -import com.adyen.checkout.components.core.internal.ui.model.FieldState -import com.adyen.checkout.components.core.internal.ui.model.Validation -import com.adyen.checkout.components.core.internal.util.bufferedChannel -import com.adyen.checkout.components.core.paymentmethod.CardPaymentMethod -import com.adyen.checkout.core.exception.CheckoutException -import com.adyen.checkout.core.exception.ComponentException -import com.adyen.checkout.core.internal.util.LogUtil -import com.adyen.checkout.core.internal.util.Logger -import com.adyen.checkout.core.internal.util.runCompileOnly -import com.adyen.checkout.cse.EncryptedCard -import com.adyen.checkout.cse.EncryptionException -import com.adyen.checkout.cse.UnencryptedCard -import com.adyen.checkout.cse.internal.BaseCardEncrypter -import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType -import com.adyen.checkout.ui.core.internal.ui.ComponentViewType -import com.adyen.checkout.ui.core.internal.ui.PaymentComponentUIEvent -import com.adyen.checkout.ui.core.internal.ui.PaymentComponentUIState -import com.adyen.checkout.ui.core.internal.ui.SubmitHandler -import com.adyen.threeds2.ThreeDS2Service -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.receiveAsFlow -import kotlinx.coroutines.launch - -@Suppress("TooManyFunctions", "LongParameterList") -internal class DefaultBcmcDelegate( - private val observerRepository: PaymentObserverRepository, - private val paymentMethod: PaymentMethod, - private val order: Order?, - private val analyticsRepository: AnalyticsRepository, - private val publicKeyRepository: PublicKeyRepository, - override val componentParams: BcmcComponentParams, - private val cardValidationMapper: CardValidationMapper, - private val cardEncrypter: BaseCardEncrypter, - private val submitHandler: SubmitHandler -) : BcmcDelegate { - - private val inputData = BcmcInputData() - - private val _outputDataFlow = MutableStateFlow(createOutputData()) - override val outputDataFlow: Flow = _outputDataFlow - - private val _componentStateFlow = MutableStateFlow(createComponentState()) - override val componentStateFlow: Flow = _componentStateFlow - - private val exceptionChannel: Channel = bufferedChannel() - override val exceptionFlow: Flow = exceptionChannel.receiveAsFlow() - - override val outputData get() = _outputDataFlow.value - - private val _viewFlow: MutableStateFlow = MutableStateFlow(BcmcComponentViewType) - override val viewFlow: Flow = _viewFlow - - override val submitFlow: Flow = submitHandler.submitFlow - - override val uiStateFlow: Flow = submitHandler.uiStateFlow - - override val uiEventFlow: Flow = submitHandler.uiEventFlow - - private var publicKey: String? = null - - override fun initialize(coroutineScope: CoroutineScope) { - submitHandler.initialize(coroutineScope, componentStateFlow) - setupAnalytics(coroutineScope) - fetchPublicKey(coroutineScope) - } - - private fun setupAnalytics(coroutineScope: CoroutineScope) { - Logger.v(TAG, "setupAnalytics") - coroutineScope.launch { - analyticsRepository.setupAnalytics() - } - } - - private fun fetchPublicKey(coroutineScope: CoroutineScope) { - Logger.d(TAG, "fetchPublicKey") - coroutineScope.launch { - publicKeyRepository.fetchPublicKey( - environment = componentParams.environment, - clientKey = componentParams.clientKey - ).fold( - onSuccess = { key -> - Logger.d(TAG, "Public key fetched") - publicKey = key - updateComponentState(outputData) - }, - onFailure = { e -> - Logger.e(TAG, "Unable to fetch public key") - exceptionChannel.trySend(ComponentException("Unable to fetch publicKey.", e)) - } - ) - } - } - - override fun observe( - lifecycleOwner: LifecycleOwner, - coroutineScope: CoroutineScope, - callback: (PaymentComponentEvent) -> Unit - ) { - observerRepository.addObservers( - stateFlow = componentStateFlow, - exceptionFlow = exceptionFlow, - submitFlow = submitFlow, - lifecycleOwner = lifecycleOwner, - coroutineScope = coroutineScope, - callback = callback - ) - } - - override fun removeObserver() { - observerRepository.removeObservers() - } - - override fun updateInputData(update: BcmcInputData.() -> Unit) { - inputData.update() - onInputDataChanged() - } - - private fun onInputDataChanged() { - val outputData = createOutputData() - - _outputDataFlow.tryEmit(outputData) - - updateComponentState(outputData) - } - - private fun createOutputData() = BcmcOutputData( - cardNumberField = validateCardNumber(inputData.cardNumber), - expiryDateField = CardValidationUtils.validateExpiryDate(inputData.expiryDate, Brand.FieldPolicy.REQUIRED), - cardHolderNameField = validateHolderName(inputData.cardHolderName), - shouldStorePaymentMethod = inputData.isStorePaymentMethodSwitchChecked, - showStorePaymentField = showStorePaymentField(), - ) - - private fun validateCardNumber(cardNumber: String): FieldState { - val validation = - CardValidationUtils.validateCardNumber(cardNumber, enableLuhnCheck = true, isBrandSupported = true) - return cardValidationMapper.mapCardNumberValidation(cardNumber, validation) - } - - private fun validateHolderName(holderName: String): FieldState { - return if (componentParams.isHolderNameRequired && holderName.isBlank()) { - FieldState( - holderName, - Validation.Invalid(R.string.checkout_holder_name_not_valid) - ) - } else { - FieldState( - holderName, - Validation.Valid - ) - } - } - - private fun showStorePaymentField(): Boolean { - return componentParams.isStorePaymentFieldVisible - } - - @VisibleForTesting - internal fun updateComponentState(outputData: BcmcOutputData) { - Logger.v(TAG, "updateComponentState") - val componentState = createComponentState(outputData) - _componentStateFlow.tryEmit(componentState) - } - - @Suppress("ReturnCount") - private fun createComponentState( - outputData: BcmcOutputData = this.outputData - ): BcmcComponentState { - val publicKey = publicKey - - // If data is not valid we just return empty object, encryption would fail and we don't pass unencrypted data. - if (!outputData.isValid || publicKey == null) { - return BcmcComponentState( - data = PaymentComponentData(null, null, null), - isInputValid = outputData.isValid, - isReady = publicKey != null, - ) - } - - val encryptedCard = encryptCardData(outputData, publicKey) ?: return BcmcComponentState( - data = PaymentComponentData(null, null, null), - isInputValid = false, - isReady = true, - ) - - // BCMC payment method is scheme type. - val cardPaymentMethod = CardPaymentMethod( - type = CardPaymentMethod.PAYMENT_METHOD_TYPE, - checkoutAttemptId = analyticsRepository.getCheckoutAttemptId(), - encryptedCardNumber = encryptedCard.encryptedCardNumber, - encryptedExpiryMonth = encryptedCard.encryptedExpiryMonth, - encryptedExpiryYear = encryptedCard.encryptedExpiryYear, - threeDS2SdkVersion = runCompileOnly { ThreeDS2Service.INSTANCE.sdkVersion }, - brand = PaymentMethodTypes.BCMC - ).apply { - if (componentParams.isHolderNameRequired) { - holderName = outputData.cardHolderNameField.value - } - } - - val paymentComponentData = PaymentComponentData( - order = order, - paymentMethod = cardPaymentMethod, - storePaymentMethod = if (showStorePaymentField()) outputData.shouldStorePaymentMethod else null, - shopperReference = componentParams.shopperReference, - amount = componentParams.amount, - ) - - return BcmcComponentState(paymentComponentData, isInputValid = true, isReady = true) - } - - override fun onSubmit() { - val state = _componentStateFlow.value - submitHandler.onSubmit(state) - } - - private fun encryptCardData( - outputData: BcmcOutputData, - publicKey: String, - ): EncryptedCard? = try { - val unencryptedCardBuilder = UnencryptedCard.Builder() - .setNumber(outputData.cardNumberField.value) - - val expiryDateResult = outputData.expiryDateField.value - if (expiryDateResult != ExpiryDate.EMPTY_DATE) { - unencryptedCardBuilder.setExpiryDate( - expiryMonth = expiryDateResult.expiryMonth.toString(), - expiryYear = expiryDateResult.expiryYear.toString() - ) - } - - cardEncrypter.encryptFields(unencryptedCardBuilder.build(), publicKey) - } catch (e: EncryptionException) { - exceptionChannel.trySend(e) - null - } - - override fun getPaymentMethodType(): String { - return paymentMethod.type ?: PaymentMethodTypes.UNKNOWN - } - - override fun isCardNumberSupported(cardNumber: String?): Boolean { - if (cardNumber.isNullOrEmpty()) return false - return CardBrand.estimate(cardNumber).contains(BcmcComponent.SUPPORTED_CARD_TYPE) - } - - override fun isConfirmationRequired(): Boolean = _viewFlow.value is ButtonComponentViewType - - override fun shouldShowSubmitButton(): Boolean = isConfirmationRequired() && componentParams.isSubmitButtonVisible - - override fun setInteractionBlocked(isInteractionBlocked: Boolean) { - submitHandler.setInteractionBlocked(isInteractionBlocked) - } - - override fun onCleared() { - removeObserver() - } - - companion object { - private val TAG = LogUtil.getTag() - } -} diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParams.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParams.kt deleted file mode 100644 index a888d5a2bb..0000000000 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParams.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2022 Adyen N.V. - * - * This file is open source and available under the MIT license. See the LICENSE file for more info. - * - * Created by josephj on 15/11/2022. - */ - -package com.adyen.checkout.bcmc.internal.ui.model - -import com.adyen.checkout.components.core.Amount -import com.adyen.checkout.components.core.internal.ui.model.AnalyticsParams -import com.adyen.checkout.components.core.internal.ui.model.ButtonParams -import com.adyen.checkout.components.core.internal.ui.model.ComponentParams -import com.adyen.checkout.core.Environment -import java.util.Locale - -internal data class BcmcComponentParams( - override val shopperLocale: Locale, - override val environment: Environment, - override val clientKey: String, - override val analyticsParams: AnalyticsParams, - override val isCreatedByDropIn: Boolean, - override val amount: Amount?, - override val isSubmitButtonVisible: Boolean, - val isHolderNameRequired: Boolean, - val shopperReference: String?, - val isStorePaymentFieldVisible: Boolean, -) : ComponentParams, ButtonParams diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapper.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapper.kt index 440c775bb6..235810cd13 100644 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapper.kt +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapper.kt @@ -1,17 +1,25 @@ /* - * Copyright (c) 2022 Adyen N.V. + * Copyright (c) 2023 Adyen N.V. * * This file is open source and available under the MIT license. See the LICENSE file for more info. * - * Created by josephj on 17/11/2022. + * Created by ozgur on 22/8/2023. */ package com.adyen.checkout.bcmc.internal.ui.model import com.adyen.checkout.bcmc.BcmcConfiguration +import com.adyen.checkout.card.CardBrand +import com.adyen.checkout.card.CardType +import com.adyen.checkout.card.KCPAuthVisibility +import com.adyen.checkout.card.SocialSecurityNumberVisibility +import com.adyen.checkout.card.internal.ui.model.CVCVisibility +import com.adyen.checkout.card.internal.ui.model.CardComponentParams +import com.adyen.checkout.card.internal.ui.model.StoredCVCVisibility import com.adyen.checkout.components.core.internal.ui.model.AnalyticsParams import com.adyen.checkout.components.core.internal.ui.model.ComponentParams import com.adyen.checkout.components.core.internal.ui.model.SessionParams +import com.adyen.checkout.ui.core.internal.ui.model.AddressParams internal class BcmcComponentParamsMapper( private val overrideComponentParams: ComponentParams?, @@ -21,15 +29,15 @@ internal class BcmcComponentParamsMapper( fun mapToParams( bcmcConfiguration: BcmcConfiguration, sessionParams: SessionParams?, - ): BcmcComponentParams { + ): CardComponentParams { return bcmcConfiguration .mapToParamsInternal() .override(overrideComponentParams) .override(sessionParams ?: overrideSessionParams) } - private fun BcmcConfiguration.mapToParamsInternal(): BcmcComponentParams { - return BcmcComponentParams( + private fun BcmcConfiguration.mapToParamsInternal(): CardComponentParams { + return CardComponentParams( shopperLocale = shopperLocale, environment = environment, clientKey = clientKey, @@ -40,12 +48,23 @@ internal class BcmcComponentParamsMapper( isHolderNameRequired = isHolderNameRequired ?: false, shopperReference = shopperReference, isStorePaymentFieldVisible = isStorePaymentFieldVisible ?: false, + addressParams = AddressParams.None, + installmentParams = null, + kcpAuthVisibility = KCPAuthVisibility.HIDE, + socialSecurityNumberVisibility = SocialSecurityNumberVisibility.HIDE, + cvcVisibility = CVCVisibility.HIDE_FIRST, + storedCVCVisibility = StoredCVCVisibility.HIDE, + supportedCardBrands = listOf( + CardBrand(cardType = CardType.BCMC), + CardBrand(cardType = CardType.MAESTRO), + CardBrand(cardType = CardType.VISA) + ) ) } - private fun BcmcComponentParams.override( + private fun CardComponentParams.override( overrideComponentParams: ComponentParams? - ): BcmcComponentParams { + ): CardComponentParams { if (overrideComponentParams == null) return this return copy( shopperLocale = overrideComponentParams.shopperLocale, @@ -57,9 +76,9 @@ internal class BcmcComponentParamsMapper( ) } - private fun BcmcComponentParams.override( + private fun CardComponentParams.override( sessionParams: SessionParams? = null - ): BcmcComponentParams { + ): CardComponentParams { if (sessionParams == null) return this return copy( isStorePaymentFieldVisible = sessionParams.enableStoreDetails ?: isStorePaymentFieldVisible, diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperNew.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperNew.kt deleted file mode 100644 index fbd851b6f2..0000000000 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperNew.kt +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2023 Adyen N.V. - * - * This file is open source and available under the MIT license. See the LICENSE file for more info. - * - * Created by ozgur on 22/8/2023. - */ - -package com.adyen.checkout.bcmc.internal.ui.model - -import com.adyen.checkout.bcmc.BcmcConfiguration -import com.adyen.checkout.card.CVCVisibility -import com.adyen.checkout.card.CardBrand -import com.adyen.checkout.card.CardType -import com.adyen.checkout.card.KCPAuthVisibility -import com.adyen.checkout.card.SocialSecurityNumberVisibility -import com.adyen.checkout.card.StoredCVCVisibility -import com.adyen.checkout.card.internal.ui.model.CardComponentParams -import com.adyen.checkout.components.core.internal.ui.model.AnalyticsParams -import com.adyen.checkout.components.core.internal.ui.model.ComponentParams -import com.adyen.checkout.components.core.internal.ui.model.SessionParams -import com.adyen.checkout.ui.core.internal.ui.model.AddressParams - -internal class BcmcComponentParamsMapperNew( - private val overrideComponentParams: ComponentParams?, - private val overrideSessionParams: SessionParams?, -) { - - fun mapToParams( - bcmcConfiguration: BcmcConfiguration, - sessionParams: SessionParams?, - ): CardComponentParams { - return bcmcConfiguration - .mapToParamsInternal() - .override(overrideComponentParams) - .override(sessionParams ?: overrideSessionParams) - } - - private fun BcmcConfiguration.mapToParamsInternal(): CardComponentParams { - return CardComponentParams( - shopperLocale = shopperLocale, - environment = environment, - clientKey = clientKey, - analyticsParams = AnalyticsParams(analyticsConfiguration), - isCreatedByDropIn = false, - amount = amount, - isSubmitButtonVisible = isSubmitButtonVisible ?: true, - isHolderNameRequired = isHolderNameRequired ?: false, - shopperReference = shopperReference, - isStorePaymentFieldVisible = isStorePaymentFieldVisible ?: false, - addressParams = AddressParams.None, - installmentParams = null, - kcpAuthVisibility = KCPAuthVisibility.HIDE, - socialSecurityNumberVisibility = SocialSecurityNumberVisibility.HIDE, - cvcVisibility = cvcVisibility ?: CVCVisibility.ALWAYS_HIDE, - storedCVCVisibility = StoredCVCVisibility.HIDE, - supportedCardBrands = listOf( - CardBrand(cardType = CardType.BCMC), - CardBrand(cardType = CardType.MAESTRO), - CardBrand(cardType = CardType.VISA) - ) - ) - } - - private fun CardComponentParams.override( - overrideComponentParams: ComponentParams? - ): CardComponentParams { - if (overrideComponentParams == null) return this - return copy( - shopperLocale = overrideComponentParams.shopperLocale, - environment = overrideComponentParams.environment, - clientKey = overrideComponentParams.clientKey, - analyticsParams = overrideComponentParams.analyticsParams, - isCreatedByDropIn = overrideComponentParams.isCreatedByDropIn, - amount = overrideComponentParams.amount, - ) - } - - private fun CardComponentParams.override( - sessionParams: SessionParams? = null - ): CardComponentParams { - if (sessionParams == null) return this - return copy( - isStorePaymentFieldVisible = sessionParams.enableStoreDetails ?: isStorePaymentFieldVisible, - amount = sessionParams.amount ?: amount, - ) - } -} diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcInputData.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcInputData.kt deleted file mode 100644 index d8768ef55c..0000000000 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcInputData.kt +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2019 Adyen N.V. - * - * This file is open source and available under the MIT license. See the LICENSE file for more info. - * - * Created by caiof on 27/8/2020. - */ -package com.adyen.checkout.bcmc.internal.ui.model - -import com.adyen.checkout.card.internal.ui.model.ExpiryDate -import com.adyen.checkout.components.core.internal.ui.model.InputData - -internal data class BcmcInputData( - var cardNumber: String = "", - var expiryDate: ExpiryDate = ExpiryDate.EMPTY_DATE, - var cardHolderName: String = "", - var isStorePaymentMethodSwitchChecked: Boolean = false, -) : InputData diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcOutputData.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcOutputData.kt deleted file mode 100644 index a8dcc44ba5..0000000000 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcOutputData.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2023 Adyen N.V. - * - * This file is open source and available under the MIT license. See the LICENSE file for more info. - * - * Created by oscars on 15/2/2023. - */ -package com.adyen.checkout.bcmc.internal.ui.model - -import com.adyen.checkout.card.internal.ui.model.ExpiryDate -import com.adyen.checkout.components.core.internal.ui.model.FieldState -import com.adyen.checkout.components.core.internal.ui.model.OutputData - -internal data class BcmcOutputData internal constructor( - val cardNumberField: FieldState, - val expiryDateField: FieldState, - val cardHolderNameField: FieldState, - val showStorePaymentField: Boolean, - val shouldStorePaymentMethod: Boolean, -) : OutputData { - override val isValid: Boolean - get() = ( - cardNumberField.validation.isValid() && - expiryDateField.validation.isValid() && - cardHolderNameField.validation.isValid() - ) -} diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/view/BcmcView.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/view/BcmcView.kt deleted file mode 100644 index 160659964c..0000000000 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/view/BcmcView.kt +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (c) 2022 Adyen N.V. - * - * This file is open source and available under the MIT license. See the LICENSE file for more info. - * - * Created by oscars on 29/9/2022. - */ - -package com.adyen.checkout.bcmc.internal.ui.view - -import android.content.Context -import android.util.AttributeSet -import android.view.LayoutInflater -import android.view.View -import android.view.View.OnFocusChangeListener -import android.widget.CompoundButton -import android.widget.LinearLayout -import androidx.annotation.StringRes -import androidx.core.view.isVisible -import com.adyen.checkout.bcmc.R -import com.adyen.checkout.bcmc.databinding.BcmcViewBinding -import com.adyen.checkout.bcmc.internal.ui.BcmcDelegate -import com.adyen.checkout.bcmc.internal.ui.model.BcmcOutputData -import com.adyen.checkout.components.core.internal.ui.ComponentDelegate -import com.adyen.checkout.components.core.internal.ui.model.Validation -import com.adyen.checkout.ui.core.internal.ui.ComponentView -import com.adyen.checkout.ui.core.internal.util.hideError -import com.adyen.checkout.ui.core.internal.util.isVisible -import com.adyen.checkout.ui.core.internal.util.setLocalizedHintFromStyle -import com.adyen.checkout.ui.core.internal.util.setLocalizedTextFromStyle -import com.adyen.checkout.ui.core.internal.util.showError -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach - -@Suppress("TooManyFunctions") -internal class BcmcView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : LinearLayout(context, attrs, defStyleAttr), - ComponentView { - - private val binding = BcmcViewBinding.inflate(LayoutInflater.from(context), this) - - private lateinit var localizedContext: Context - - private lateinit var delegate: BcmcDelegate - - init { - orientation = VERTICAL - val padding = resources.getDimension(R.dimen.standard_margin).toInt() - setPadding(padding, padding, padding, 0) - } - - override fun initView(delegate: ComponentDelegate, coroutineScope: CoroutineScope, localizedContext: Context) { - require(delegate is BcmcDelegate) { "Unsupported delegate type" } - this.delegate = delegate - - this.localizedContext = localizedContext - initLocalizedStrings(localizedContext) - - observeDelegate(delegate, coroutineScope) - - initCardNumberInput() - initExpiryDateInput() - initCardHolderInput() - initStorePaymentMethodSwitch() - } - - private fun initLocalizedStrings(localizedContext: Context) { - with(binding) { - textInputLayoutCardNumber.setLocalizedHintFromStyle( - R.style.AdyenCheckout_Card_CardNumberInput, - localizedContext - ) - textInputLayoutExpiryDate.setLocalizedHintFromStyle( - R.style.AdyenCheckout_Card_ExpiryDateInput, - localizedContext - ) - binding.textInputLayoutCardHolder.setLocalizedHintFromStyle( - R.style.AdyenCheckout_Card_HolderNameInput, - localizedContext - ) - switchStorePaymentMethod.setLocalizedTextFromStyle( - R.style.AdyenCheckout_Card_StorePaymentSwitch, - localizedContext - ) - } - } - - private fun observeDelegate(delegate: BcmcDelegate, coroutineScope: CoroutineScope) { - delegate.outputDataFlow - .onEach { outputDataChanged(it) } - .launchIn(coroutineScope) - } - - private fun outputDataChanged(bcmcOutputData: BcmcOutputData) { - setStorePaymentSwitchVisibility(bcmcOutputData.showStorePaymentField) - } - - private fun setStorePaymentSwitchVisibility(showStorePaymentField: Boolean) { - binding.switchStorePaymentMethod.isVisible = showStorePaymentField - } - - private fun initExpiryDateInput() { - binding.editTextExpiryDate.setOnChangeListener { - delegate.updateInputData { expiryDate = binding.editTextExpiryDate.date } - binding.textInputLayoutExpiryDate.hideError() - } - - binding.editTextExpiryDate.onFocusChangeListener = OnFocusChangeListener { _: View?, hasFocus: Boolean -> - val expiryDateValidation = delegate.outputData.expiryDateField.validation - if (hasFocus) { - binding.textInputLayoutExpiryDate.hideError() - } else if (expiryDateValidation is Validation.Invalid) { - val errorReasonResId = expiryDateValidation.reason - binding.textInputLayoutExpiryDate.showError(localizedContext.getString(errorReasonResId)) - } - } - } - - private fun initCardNumberInput() { - binding.editTextCardNumber.setOnChangeListener { - delegate.updateInputData { cardNumber = binding.editTextCardNumber.rawValue } - setCardNumberError(null) - } - - binding.editTextCardNumber.onFocusChangeListener = OnFocusChangeListener { _: View?, hasFocus: Boolean -> - val cardNumberValidation = delegate.outputData.cardNumberField.validation - if (hasFocus) { - setCardNumberError(null) - } else if (cardNumberValidation is Validation.Invalid) { - val errorReasonResId = cardNumberValidation.reason - setCardNumberError(errorReasonResId) - } - } - } - - private fun initCardHolderInput() { - binding.textInputLayoutCardHolder.isVisible = delegate.componentParams.isHolderNameRequired - binding.editTextCardHolder.setOnChangeListener { - delegate.updateInputData { cardHolderName = binding.editTextCardHolder.rawValue } - binding.textInputLayoutCardHolder.hideError() - } - - binding.editTextCardHolder.onFocusChangeListener = OnFocusChangeListener { _, hasFocus -> - val cardHolderValidation = delegate.outputData.cardHolderNameField.validation - if (hasFocus) { - binding.textInputLayoutCardHolder.hideError() - } else if (cardHolderValidation is Validation.Invalid) { - val errorReasonResId = cardHolderValidation.reason - binding.textInputLayoutCardHolder.showError(localizedContext.getString(errorReasonResId)) - } - } - } - - private fun initStorePaymentMethodSwitch() { - binding.switchStorePaymentMethod.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> - delegate.updateInputData { isStorePaymentMethodSwitchChecked = isChecked } - } - } - - override fun highlightValidationErrors() { - val outputData = delegate.outputData - - var isErrorFocused = false - val cardNumberValidation = outputData.cardNumberField.validation - if (cardNumberValidation is Validation.Invalid) { - isErrorFocused = true - binding.editTextCardNumber.requestFocus() - val errorReasonResId = cardNumberValidation.reason - setCardNumberError(errorReasonResId) - } - - val expiryFieldValidation = outputData.expiryDateField.validation - if (expiryFieldValidation is Validation.Invalid) { - if (!isErrorFocused) { - binding.textInputLayoutExpiryDate.requestFocus() - } - val errorReasonResId = expiryFieldValidation.reason - binding.textInputLayoutExpiryDate.showError(localizedContext.getString(errorReasonResId)) - } - - val cardHolderNameValidation = outputData.cardHolderNameField.validation - if (cardHolderNameValidation is Validation.Invalid) { - if (!isErrorFocused) { - binding.textInputLayoutCardHolder.requestFocus() - } - val errorReasonResId = cardHolderNameValidation.reason - binding.textInputLayoutCardHolder.showError(localizedContext.getString(errorReasonResId)) - } - } - - private fun setCardNumberError(@StringRes stringResId: Int?) { - if (stringResId == null) { - binding.textInputLayoutCardNumber.hideError() - binding.cardBrandLogoImageView.isVisible = true - } else { - binding.textInputLayoutCardNumber.showError(localizedContext.getString(stringResId)) - binding.cardBrandLogoImageView.isVisible = false - } - } - - override fun getView(): View = this -} diff --git a/bcmc/src/test/java/com/adyen/checkout/bcmc/BcmcComponentTest.kt b/bcmc/src/test/java/com/adyen/checkout/bcmc/BcmcComponentTest.kt index ed35c56496..e28059bd49 100644 --- a/bcmc/src/test/java/com/adyen/checkout/bcmc/BcmcComponentTest.kt +++ b/bcmc/src/test/java/com/adyen/checkout/bcmc/BcmcComponentTest.kt @@ -46,17 +46,17 @@ internal class BcmcComponentTest( @Mock private val cardDelegate: CardDelegate, @Mock private val genericActionDelegate: GenericActionDelegate, @Mock private val actionHandlingComponent: DefaultActionHandlingComponent, - @Mock private val componentEventHandler: ComponentEventHandler, + @Mock private val componentEventHandler: ComponentEventHandler, ) { - private lateinit var component: BcmcComponentNew + private lateinit var component: BcmcComponent @BeforeEach fun before() { whenever(cardDelegate.viewFlow) doReturn MutableStateFlow(BcmcComponentViewType) whenever(genericActionDelegate.viewFlow) doReturn MutableStateFlow(null) - component = BcmcComponentNew( + component = BcmcComponent( cardDelegate, genericActionDelegate, actionHandlingComponent, @@ -112,7 +112,7 @@ internal class BcmcComponentTest( fun `when bcmc delegate view flow emits a value then component view flow should match that value`() = runTest { val bcmcDelegateViewFlow = MutableStateFlow(TestComponentViewType.VIEW_TYPE_1) whenever(cardDelegate.viewFlow) doReturn bcmcDelegateViewFlow - component = BcmcComponentNew( + component = BcmcComponent( cardDelegate = cardDelegate, genericActionDelegate = genericActionDelegate, actionHandlingComponent = actionHandlingComponent, @@ -133,7 +133,7 @@ internal class BcmcComponentTest( fun `when action delegate view flow emits a value then component view flow should match that value`() = runTest { val actionDelegateViewFlow = MutableStateFlow(TestComponentViewType.VIEW_TYPE_1) whenever(genericActionDelegate.viewFlow) doReturn actionDelegateViewFlow - component = BcmcComponentNew( + component = BcmcComponent( cardDelegate = cardDelegate, genericActionDelegate = genericActionDelegate, actionHandlingComponent = actionHandlingComponent, diff --git a/bcmc/src/test/java/com/adyen/checkout/bcmc/internal/ui/DefaultBcmcDelegateTest.kt b/bcmc/src/test/java/com/adyen/checkout/bcmc/internal/ui/DefaultBcmcDelegateTest.kt deleted file mode 100644 index 5c40543c2b..0000000000 --- a/bcmc/src/test/java/com/adyen/checkout/bcmc/internal/ui/DefaultBcmcDelegateTest.kt +++ /dev/null @@ -1,487 +0,0 @@ -/* - * Copyright (c) 2022 Adyen N.V. - * - * This file is open source and available under the MIT license. See the LICENSE file for more info. - * - * Created by atef on 22/8/2022. - */ - -package com.adyen.checkout.bcmc.internal.ui - -import app.cash.turbine.test -import com.adyen.checkout.bcmc.BcmcComponentState -import com.adyen.checkout.bcmc.BcmcConfiguration -import com.adyen.checkout.bcmc.internal.ui.model.BcmcComponentParamsMapper -import com.adyen.checkout.bcmc.internal.ui.model.BcmcOutputData -import com.adyen.checkout.card.R -import com.adyen.checkout.card.internal.ui.CardValidationMapper -import com.adyen.checkout.card.internal.ui.model.ExpiryDate -import com.adyen.checkout.components.core.Amount -import com.adyen.checkout.components.core.OrderRequest -import com.adyen.checkout.components.core.PaymentMethod -import com.adyen.checkout.components.core.PaymentMethodTypes -import com.adyen.checkout.components.core.internal.PaymentObserverRepository -import com.adyen.checkout.components.core.internal.data.api.AnalyticsRepository -import com.adyen.checkout.components.core.internal.test.TestPublicKeyRepository -import com.adyen.checkout.components.core.internal.ui.model.FieldState -import com.adyen.checkout.components.core.internal.ui.model.Validation -import com.adyen.checkout.core.Environment -import com.adyen.checkout.cse.internal.test.TestCardEncrypter -import com.adyen.checkout.test.TestDispatcherExtension -import com.adyen.checkout.ui.core.internal.ui.SubmitHandler -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertFalse -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.Arguments.arguments -import org.junit.jupiter.params.provider.MethodSource -import org.mockito.Mock -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.verify -import org.mockito.kotlin.whenever -import java.util.Locale - -@OptIn(ExperimentalCoroutinesApi::class) -@ExtendWith(MockitoExtension::class, TestDispatcherExtension::class) -internal class DefaultBcmcDelegateTest( - @Mock private val analyticsRepository: AnalyticsRepository, - @Mock private val submitHandler: SubmitHandler, -) { - - private lateinit var testPublicKeyRepository: TestPublicKeyRepository - private lateinit var cardEncrypter: TestCardEncrypter - private lateinit var cardValidationMapper: CardValidationMapper - private lateinit var delegate: DefaultBcmcDelegate - - @BeforeEach - fun setup() { - testPublicKeyRepository = TestPublicKeyRepository() - cardEncrypter = TestCardEncrypter() - cardValidationMapper = CardValidationMapper() - delegate = createBcmcDelegate() - } - - @Test - fun `when fetching the public key fails, then an error is propagated`() = runTest { - testPublicKeyRepository.shouldReturnError = true - - delegate.exceptionFlow.test { - delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - - val exception = expectMostRecentItem() - assertEquals(testPublicKeyRepository.errorResult.exceptionOrNull(), exception.cause) - - cancelAndIgnoreRemainingEvents() - } - } - - @Nested - @DisplayName("when input data changes and") - inner class InputDataChangedTest { - @Test - fun `card number is empty, then output should be invalid`() = runTest { - delegate.outputDataFlow.test { - delegate.updateInputData { - cardNumber = "" - expiryDate = TEST_EXPIRY_DATE - } - - with(expectMostRecentItem()) { - assertTrue(cardNumberField.validation is Validation.Invalid) - assertTrue(expiryDateField.validation is Validation.Valid) - assertFalse(isValid) - } - } - } - - @Test - fun `card number is invalid, then output should be invalid`() = runTest { - delegate.outputDataFlow.test { - delegate.updateInputData { - cardNumber = "12345678" - expiryDate = TEST_EXPIRY_DATE - } - - with(expectMostRecentItem()) { - assertTrue(cardNumberField.validation is Validation.Invalid) - assertTrue(expiryDateField.validation is Validation.Valid) - assertFalse(isValid) - } - } - } - - @Test - fun `expiry date is invalid, then output should be invalid`() = - runTest { - delegate.outputDataFlow.test { - delegate.updateInputData { - cardNumber = TEST_CARD_NUMBER - expiryDate = ExpiryDate.INVALID_DATE - } - - with(expectMostRecentItem()) { - assertTrue(cardNumberField.validation is Validation.Valid) - assertTrue(expiryDateField.validation is Validation.Invalid) - assertFalse(isValid) - } - } - } - - @Test - fun `expiry date is empty, then output should be invalid`() = runTest { - delegate.outputDataFlow.test { - delegate.updateInputData { - cardNumber = TEST_CARD_NUMBER - expiryDate = ExpiryDate.EMPTY_DATE - } - - with(expectMostRecentItem()) { - assertTrue(cardNumberField.validation is Validation.Valid) - assertTrue(expiryDateField.validation is Validation.Invalid) - assertFalse(isValid) - } - } - } - - @Test - fun `input is valid, then output should be valid`() = runTest { - delegate.outputDataFlow.test { - delegate.updateInputData { - cardNumber = TEST_CARD_NUMBER - expiryDate = TEST_EXPIRY_DATE - } - - with(expectMostRecentItem()) { - assertTrue(cardNumberField.validation is Validation.Valid) - assertTrue(expiryDateField.validation is Validation.Valid) - assertTrue(isValid) - } - } - } - } - - @Nested - @DisplayName("when creating component state and") - inner class CreateComponentStateTest { - - @Test - fun `component is not initialized, then component state should not be ready`() = runTest { - delegate.componentStateFlow.test { - delegate.updateComponentState( - createOutputData( - cardNumber = FieldState(TEST_CARD_NUMBER, Validation.Valid), - expiryDate = FieldState(TEST_EXPIRY_DATE, Validation.Valid), - cardHolder = FieldState("Name", Validation.Valid) - ) - ) - - with(expectMostRecentItem()) { - assertFalse(isReady) - } - } - } - - @Test - fun `encryption fails, then component state should be invalid`() = runTest { - cardEncrypter.shouldThrowException = true - - delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - - delegate.componentStateFlow.test { - delegate.updateComponentState( - createOutputData( - cardNumber = FieldState(TEST_CARD_NUMBER, Validation.Valid), - expiryDate = FieldState(TEST_EXPIRY_DATE, Validation.Valid), - cardHolder = FieldState("Name", Validation.Valid) - ) - ) - - with(expectMostRecentItem()) { - assertTrue(isReady) - assertFalse(isInputValid) - } - } - } - - @Test - fun `card number in output data is invalid, then component state should be invalid`() = runTest { - delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - delegate.componentStateFlow.test { - delegate.updateComponentState( - createOutputData( - cardNumber = FieldState( - "12345678", - Validation.Invalid(R.string.checkout_card_number_not_valid) - ), - expiryDate = FieldState(TEST_EXPIRY_DATE, Validation.Valid), - cardHolder = FieldState("Name", Validation.Valid) - ) - ) - - with(expectMostRecentItem()) { - assertFalse(isValid) - assertFalse(isInputValid) - } - } - } - - @Test - fun `expiry date in output is invalid, then component state should be invalid`() = runTest { - delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - delegate.componentStateFlow.test { - delegate.updateComponentState( - createOutputData( - cardNumber = FieldState(TEST_CARD_NUMBER, Validation.Valid), - expiryDate = FieldState( - ExpiryDate.INVALID_DATE, - Validation.Invalid(R.string.checkout_expiry_date_not_valid) - ), - cardHolder = FieldState("Name", Validation.Valid), - ) - ) - - with(expectMostRecentItem()) { - assertFalse(isValid) - assertFalse(isInputValid) - } - } - } - - @Test - fun `holder name in output is invalid, then component state should be invalid`() = runTest { - delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - delegate.componentStateFlow.test { - delegate.updateComponentState( - createOutputData( - cardNumber = FieldState(TEST_CARD_NUMBER, Validation.Valid), - expiryDate = FieldState(TEST_EXPIRY_DATE, Validation.Valid), - cardHolder = FieldState("", Validation.Invalid(R.string.checkout_holder_name_not_valid)), - ) - ) - - with(expectMostRecentItem()) { - assertFalse(isValid) - assertFalse(isInputValid) - } - } - } - - @Test - fun `output data is valid, then component state should be valid`() = runTest { - delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - delegate.componentStateFlow.test { - delegate.updateComponentState( - createOutputData( - cardNumber = FieldState(TEST_CARD_NUMBER, Validation.Valid), - expiryDate = FieldState(TEST_EXPIRY_DATE, Validation.Valid), - cardHolder = FieldState("Name", Validation.Valid) - ) - ) - with(expectMostRecentItem()) { - assertTrue(isValid) - assertTrue(isInputValid) - assertEquals(TEST_ORDER, data.order) - assertEquals(PaymentMethodTypes.BCMC, data.paymentMethod?.brand) - } - } - } - - @ParameterizedTest - @MethodSource("com.adyen.checkout.bcmc.internal.ui.DefaultBcmcDelegateTest#shouldStorePaymentMethodSource") - fun `storePaymentMethod in component state should match store switch visibility and state`( - isStorePaymentMethodSwitchVisible: Boolean, - isStorePaymentMethodSwitchChecked: Boolean, - expectedStorePaymentMethod: Boolean?, - ) = runTest { - val configuration = getDefaultBcmcConfigurationBuilder() - .setShowStorePaymentField(isStorePaymentMethodSwitchVisible) - .build() - delegate = createBcmcDelegate(configuration = configuration) - delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - - delegate.componentStateFlow.test { - delegate.updateInputData { - cardNumber = TEST_CARD_NUMBER - expiryDate = TEST_EXPIRY_DATE - this.isStorePaymentMethodSwitchChecked = isStorePaymentMethodSwitchChecked - } - - val componentState = expectMostRecentItem() - assertEquals(expectedStorePaymentMethod, componentState.data.storePaymentMethod) - } - } - - @ParameterizedTest - @MethodSource("com.adyen.checkout.bcmc.internal.ui.DefaultBcmcDelegateTest#amountSource") - fun `when input data is valid then amount is propagated in component state if set`( - configurationValue: Amount?, - expectedComponentStateValue: Amount?, - ) = runTest { - if (configurationValue != null) { - val configuration = getDefaultBcmcConfigurationBuilder() - .setAmount(configurationValue) - .build() - delegate = createBcmcDelegate(configuration = configuration) - } - delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - delegate.componentStateFlow.test { - delegate.updateInputData { - cardNumber = TEST_CARD_NUMBER - expiryDate = TEST_EXPIRY_DATE - } - assertEquals(expectedComponentStateValue, expectMostRecentItem().data.amount) - } - } - } - - @Test - fun `when delegate is initialized then analytics event is sent`() = runTest { - delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - verify(analyticsRepository).setupAnalytics() - } - - @Nested - inner class SubmitButtonVisibilityTest { - - @Test - fun `when submit button is configured to be hidden, then it should not show`() { - delegate = createBcmcDelegate( - configuration = getDefaultBcmcConfigurationBuilder() - .setSubmitButtonVisible(false) - .build() - ) - - assertFalse(delegate.shouldShowSubmitButton()) - } - - @Test - fun `when submit button is configured to be visible, then it should show`() { - delegate = createBcmcDelegate( - configuration = getDefaultBcmcConfigurationBuilder() - .setSubmitButtonVisible(true) - .build() - ) - - assertTrue(delegate.shouldShowSubmitButton()) - } - } - - @Nested - inner class SubmitHandlerTest { - - @Test - fun `when delegate is initialized then submit handler event is initialized`() = runTest { - val coroutineScope = CoroutineScope(UnconfinedTestDispatcher()) - delegate.initialize(coroutineScope) - verify(submitHandler).initialize(coroutineScope, delegate.componentStateFlow) - } - - @Test - fun `when delegate setInteractionBlocked is called then submit handler setInteractionBlocked is called`() = - runTest { - delegate.setInteractionBlocked(true) - verify(submitHandler).setInteractionBlocked(true) - } - - @Test - fun `when delegate onSubmit is called then submit handler onSubmit is called`() = runTest { - delegate.componentStateFlow.test { - delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - delegate.onSubmit() - verify(submitHandler).onSubmit(expectMostRecentItem()) - } - } - } - - @Nested - inner class AnalyticsTest { - - @Test - fun `when component state is valid then PaymentMethodDetails should contain checkoutAttemptId`() = runTest { - whenever(analyticsRepository.getCheckoutAttemptId()) doReturn TEST_CHECKOUT_ATTEMPT_ID - - delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - - delegate.componentStateFlow.test { - delegate.updateInputData { - cardNumber = TEST_CARD_NUMBER - expiryDate = TEST_EXPIRY_DATE - } - - assertEquals(TEST_CHECKOUT_ATTEMPT_ID, expectMostRecentItem().data.paymentMethod?.checkoutAttemptId) - } - } - } - - private fun createOutputData( - cardNumber: FieldState, - expiryDate: FieldState, - cardHolder: FieldState, - showStorePaymentField: Boolean = false, - shouldStorePaymentMethod: Boolean = false - ): BcmcOutputData { - return BcmcOutputData( - cardNumberField = cardNumber, - expiryDateField = expiryDate, - cardHolderNameField = cardHolder, - showStorePaymentField = showStorePaymentField, - shouldStorePaymentMethod = shouldStorePaymentMethod, - ) - } - - private fun createBcmcDelegate( - configuration: BcmcConfiguration = getDefaultBcmcConfigurationBuilder().build() - ) = DefaultBcmcDelegate( - observerRepository = PaymentObserverRepository(), - paymentMethod = PaymentMethod(), - order = TEST_ORDER, - publicKeyRepository = testPublicKeyRepository, - componentParams = BcmcComponentParamsMapper(null, null).mapToParams(configuration, null), - cardValidationMapper = cardValidationMapper, - cardEncrypter = cardEncrypter, - analyticsRepository = analyticsRepository, - submitHandler = submitHandler, - ) - - private fun getDefaultBcmcConfigurationBuilder() = BcmcConfiguration.Builder( - Locale.US, - Environment.TEST, - TEST_CLIENT_KEY - ) - - companion object { - private const val TEST_CLIENT_KEY = "test_qwertyuiopasdfghjklzxcvbnmqwerty" - private const val TEST_CARD_NUMBER = "5555444433331111" - private val TEST_EXPIRY_DATE = ExpiryDate(3, 2030) - private val TEST_ORDER = OrderRequest("PSP", "ORDER_DATA") - private const val TEST_CHECKOUT_ATTEMPT_ID = "TEST_CHECKOUT_ATTEMPT_ID" - - @JvmStatic - fun shouldStorePaymentMethodSource() = listOf( - // isStorePaymentMethodSwitchVisible, isStorePaymentMethodSwitchChecked, expectedStorePaymentMethod - arguments(false, false, null), - arguments(false, true, null), - arguments(true, false, false), - arguments(true, true, true), - ) - - @JvmStatic - fun amountSource() = listOf( - // configurationValue, expectedComponentStateValue - arguments(Amount("EUR", 100), Amount("EUR", 100)), - arguments(Amount("USD", 0), Amount("USD", 0)), - arguments(null, null), - arguments(null, null), - ) - } -} diff --git a/bcmc/src/test/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperTest.kt b/bcmc/src/test/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperTest.kt index 64193a2af8..95989dd39b 100644 --- a/bcmc/src/test/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperTest.kt +++ b/bcmc/src/test/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperTest.kt @@ -9,13 +9,13 @@ package com.adyen.checkout.bcmc.internal.ui.model import com.adyen.checkout.bcmc.BcmcConfiguration -import com.adyen.checkout.card.CVCVisibility import com.adyen.checkout.card.CardBrand import com.adyen.checkout.card.CardType import com.adyen.checkout.card.KCPAuthVisibility import com.adyen.checkout.card.SocialSecurityNumberVisibility -import com.adyen.checkout.card.StoredCVCVisibility +import com.adyen.checkout.card.internal.ui.model.CVCVisibility import com.adyen.checkout.card.internal.ui.model.CardComponentParams +import com.adyen.checkout.card.internal.ui.model.StoredCVCVisibility import com.adyen.checkout.components.core.Amount import com.adyen.checkout.components.core.internal.ui.model.AnalyticsParams import com.adyen.checkout.components.core.internal.ui.model.AnalyticsParamsLevel @@ -53,7 +53,6 @@ internal class BcmcComponentParamsMapperTest { .setHolderNameRequired(true) .setShowStorePaymentField(true) .setSubmitButtonVisible(false) - .setCvcVisibility(CVCVisibility.SHOW_FIRST) .build() val params = BcmcComponentParamsMapper(null, null).mapToParams(bcmcConfiguration, null) @@ -63,7 +62,7 @@ internal class BcmcComponentParamsMapperTest { shopperReference = shopperReference, isStorePaymentFieldVisible = true, isSubmitButtonVisible = false, - cvcVisibility = CVCVisibility.SHOW_FIRST + cvcVisibility = CVCVisibility.HIDE_FIRST ) assertEquals(expected, params) @@ -183,7 +182,7 @@ internal class BcmcComponentParamsMapperTest { isHolderNameRequired: Boolean = false, shopperReference: String? = null, isStorePaymentFieldVisible: Boolean = false, - cvcVisibility: CVCVisibility = CVCVisibility.ALWAYS_HIDE, + cvcVisibility: CVCVisibility = CVCVisibility.HIDE_FIRST, ) = CardComponentParams( shopperLocale = shopperLocale, environment = environment, diff --git a/card/src/main/java/com/adyen/checkout/card/CardComponent.kt b/card/src/main/java/com/adyen/checkout/card/CardComponent.kt index ac55afcee3..108bc5fa14 100644 --- a/card/src/main/java/com/adyen/checkout/card/CardComponent.kt +++ b/card/src/main/java/com/adyen/checkout/card/CardComponent.kt @@ -8,6 +8,7 @@ package com.adyen.checkout.card +import androidx.annotation.RestrictTo import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -60,6 +61,7 @@ open class CardComponent constructor( componentEventHandler.initialize(viewModelScope) } + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) fun observe( lifecycleOwner: LifecycleOwner, callback: (PaymentComponentEvent) -> Unit @@ -73,6 +75,7 @@ open class CardComponent constructor( ) } + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) fun removeObserver() { cardDelegate.removeObserver() genericActionDelegate.removeObserver() diff --git a/card/src/main/java/com/adyen/checkout/card/CardComponentState.kt b/card/src/main/java/com/adyen/checkout/card/CardComponentState.kt index 568ad195c5..71b85bf368 100644 --- a/card/src/main/java/com/adyen/checkout/card/CardComponentState.kt +++ b/card/src/main/java/com/adyen/checkout/card/CardComponentState.kt @@ -14,11 +14,11 @@ import com.adyen.checkout.components.core.paymentmethod.CardPaymentMethod /** * Represents the state of [CardComponent]. */ -open class CardComponentState( +data class CardComponentState( override val data: PaymentComponentData, override val isInputValid: Boolean, override val isReady: Boolean, - open val cardBrand: CardBrand?, - open val binValue: String, - open val lastFourDigits: String?, + val cardBrand: CardBrand?, + val binValue: String, + val lastFourDigits: String?, ) : PaymentComponentState diff --git a/card/src/main/java/com/adyen/checkout/card/CardConfiguration.kt b/card/src/main/java/com/adyen/checkout/card/CardConfiguration.kt index 15e87d879b..c715f79bac 100644 --- a/card/src/main/java/com/adyen/checkout/card/CardConfiguration.kt +++ b/card/src/main/java/com/adyen/checkout/card/CardConfiguration.kt @@ -37,12 +37,12 @@ class CardConfiguration private constructor( val supportedCardBrands: List?, val shopperReference: String?, val isStorePaymentFieldVisible: Boolean?, + val isHideCvc: Boolean?, + val isHideCvcStoredCard: Boolean?, val socialSecurityNumberVisibility: SocialSecurityNumberVisibility?, val kcpAuthVisibility: KCPAuthVisibility?, val installmentConfiguration: InstallmentConfiguration?, val addressConfiguration: AddressConfiguration?, - val cvcVisibility: CVCVisibility?, - val storedCVCVisibility: StoredCVCVisibility?, internal val genericActionConfiguration: GenericActionConfiguration, ) : Configuration, ButtonConfiguration { @@ -57,12 +57,12 @@ class CardConfiguration private constructor( private var holderNameRequired: Boolean? = null private var isStorePaymentFieldVisible: Boolean? = null private var shopperReference: String? = null + private var isHideCvc: Boolean? = null + private var isHideCvcStoredCard: Boolean? = null private var isSubmitButtonVisible: Boolean? = null private var socialSecurityNumberVisibility: SocialSecurityNumberVisibility? = null private var kcpAuthVisibility: KCPAuthVisibility? = null private var installmentConfiguration: InstallmentConfiguration? = null - private var cvcVisibility: CVCVisibility? = null - private var storedCVCVisibility: StoredCVCVisibility? = null private var addressConfiguration: AddressConfiguration? = null /** @@ -160,6 +160,37 @@ class CardConfiguration private constructor( return this } + /** + * Set if the CVC field should be hidden from the Component and not requested to the shopper on a regular + * payment. + * Note that this might have implications for the risk of the transaction. Talk to Adyen Support before enabling + * this. + * + * Default is false. + * + * @param hideCvc If CVC should be hidden or not. + * @return [CardConfiguration.Builder] + */ + fun setHideCvc(hideCvc: Boolean): Builder { + this.isHideCvc = hideCvc + return this + } + + /** + * Set if the CVC field should be hidden from the Component and not requested to the shopper on a stored payment + * flow. + * Note that this has implications for the risk of the transaction. Talk to Adyen Support before enabling this. + * + * Default is false. + * + * @param hideCvcStoredCard If CVC should be hidden or not for stored payments. + * @return [CardConfiguration.Builder] + */ + fun setHideCvcStoredCard(hideCvcStoredCard: Boolean): Builder { + isHideCvcStoredCard = hideCvcStoredCard + return this + } + /** * Set if CPF/CNPJ field for Brazil merchants should be visible or not. * @@ -213,39 +244,6 @@ class CardConfiguration private constructor( return this } - // TODO docs - /** - * Set if the CVC field should be hidden from the Component and not requested to the shopper on a regular - * payment. - * Note that this might have implications for the risk of the transaction. Talk to Adyen Support before enabling - * this. - * - * Default is false. - * - * @param hideCvc If CVC should be hidden or not. - * @return [CardConfiguration.Builder] - */ - fun setCvcVisibility(cvcVisibility: CVCVisibility): Builder { - this.cvcVisibility = cvcVisibility - return this - } - - // TODO docs - /** - * Set if the CVC field should be hidden from the Component and not requested to the shopper on a stored payment - * flow. - * Note that this has implications for the risk of the transaction. Talk to Adyen Support before enabling this. - * - * Default is false. - * - * @param hideCvcStoredCard If CVC should be hidden or not for stored payments. - * @return [CardConfiguration.Builder] - */ - fun setStoredCvcVisibility(storedCVCVisibility: StoredCVCVisibility): Builder { - this.storedCVCVisibility = storedCVCVisibility - return this - } - /** * Sets if submit button will be visible or not. * @@ -275,12 +273,12 @@ class CardConfiguration private constructor( supportedCardBrands = supportedCardBrands, shopperReference = shopperReference, isStorePaymentFieldVisible = isStorePaymentFieldVisible, + isHideCvc = isHideCvc, + isHideCvcStoredCard = isHideCvcStoredCard, socialSecurityNumberVisibility = socialSecurityNumberVisibility, kcpAuthVisibility = kcpAuthVisibility, installmentConfiguration = installmentConfiguration, addressConfiguration = addressConfiguration, - cvcVisibility = cvcVisibility, - storedCVCVisibility = storedCVCVisibility, genericActionConfiguration = genericActionConfigurationBuilder.build() ) } diff --git a/card/src/main/java/com/adyen/checkout/card/internal/data/api/BinLookupService.kt b/card/src/main/java/com/adyen/checkout/card/internal/data/api/BinLookupService.kt index 2c5c2125ee..47affe25db 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/data/api/BinLookupService.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/data/api/BinLookupService.kt @@ -8,6 +8,7 @@ package com.adyen.checkout.card.internal.data.api +import androidx.annotation.RestrictTo import com.adyen.checkout.card.internal.data.model.BinLookupRequest import com.adyen.checkout.card.internal.data.model.BinLookupResponse import com.adyen.checkout.core.internal.data.api.HttpClient @@ -15,6 +16,7 @@ import com.adyen.checkout.core.internal.data.api.post import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) class BinLookupService( private val httpClient: HttpClient, ) { diff --git a/card/src/main/java/com/adyen/checkout/card/internal/data/api/DefaultDetectCardTypeRepository.kt b/card/src/main/java/com/adyen/checkout/card/internal/data/api/DefaultDetectCardTypeRepository.kt index eb0fed66a1..a0ba09c501 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/data/api/DefaultDetectCardTypeRepository.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/data/api/DefaultDetectCardTypeRepository.kt @@ -8,6 +8,7 @@ package com.adyen.checkout.card.internal.data.api +import androidx.annotation.RestrictTo import com.adyen.checkout.card.CardBrand import com.adyen.checkout.card.CardType import com.adyen.checkout.card.internal.data.model.BinLookupRequest @@ -28,6 +29,7 @@ import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import java.util.UUID +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) class DefaultDetectCardTypeRepository( private val cardEncrypter: BaseCardEncrypter, private val binLookupService: BinLookupService, diff --git a/card/src/main/java/com/adyen/checkout/card/internal/data/api/DetectCardTypeRepository.kt b/card/src/main/java/com/adyen/checkout/card/internal/data/api/DetectCardTypeRepository.kt index 41959d9a7b..ea81cf6544 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/data/api/DetectCardTypeRepository.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/data/api/DetectCardTypeRepository.kt @@ -8,11 +8,13 @@ package com.adyen.checkout.card.internal.data.api +import androidx.annotation.RestrictTo import com.adyen.checkout.card.CardBrand import com.adyen.checkout.card.internal.data.model.DetectedCardType import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) interface DetectCardTypeRepository { val detectedCardTypesFlow: Flow> diff --git a/card/src/main/java/com/adyen/checkout/card/internal/data/model/BinLookupRequest.kt b/card/src/main/java/com/adyen/checkout/card/internal/data/model/BinLookupRequest.kt index e0ed4f946c..e36fbc5694 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/data/model/BinLookupRequest.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/data/model/BinLookupRequest.kt @@ -8,6 +8,7 @@ package com.adyen.checkout.card.internal.data.model +import androidx.annotation.RestrictTo import com.adyen.checkout.core.exception.ModelSerializationException import com.adyen.checkout.core.internal.data.model.JsonUtils import com.adyen.checkout.core.internal.data.model.ModelObject @@ -18,6 +19,7 @@ import org.json.JSONException import org.json.JSONObject @Parcelize +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) data class BinLookupRequest( val encryptedBin: String? = null, val requestId: String? = null, diff --git a/card/src/main/java/com/adyen/checkout/card/internal/data/model/BinLookupResponse.kt b/card/src/main/java/com/adyen/checkout/card/internal/data/model/BinLookupResponse.kt index 905318648d..5908a98da4 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/data/model/BinLookupResponse.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/data/model/BinLookupResponse.kt @@ -8,6 +8,7 @@ package com.adyen.checkout.card.internal.data.model +import androidx.annotation.RestrictTo import com.adyen.checkout.core.exception.ModelSerializationException import com.adyen.checkout.core.internal.data.model.ModelObject import com.adyen.checkout.core.internal.data.model.ModelUtils @@ -17,6 +18,7 @@ import org.json.JSONException import org.json.JSONObject @Parcelize +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) data class BinLookupResponse( val brands: List? = null, val issuingCountryCode: String? = null, diff --git a/card/src/main/java/com/adyen/checkout/card/internal/data/model/DetectedCardType.kt b/card/src/main/java/com/adyen/checkout/card/internal/data/model/DetectedCardType.kt index a2dc65450d..f2fdfe5c1c 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/data/model/DetectedCardType.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/data/model/DetectedCardType.kt @@ -8,8 +8,10 @@ package com.adyen.checkout.card.internal.data.model +import androidx.annotation.RestrictTo import com.adyen.checkout.card.CardBrand +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) data class DetectedCardType( val cardBrand: CardBrand, val isReliable: Boolean, diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/CardDelegate.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/CardDelegate.kt index eddf741c9d..5d3a8b0c8d 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/CardDelegate.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/CardDelegate.kt @@ -8,6 +8,7 @@ package com.adyen.checkout.card.internal.ui +import androidx.annotation.RestrictTo import com.adyen.checkout.card.BinLookupData import com.adyen.checkout.card.CardComponentState import com.adyen.checkout.card.internal.ui.model.CardInputData @@ -20,6 +21,7 @@ import com.adyen.checkout.ui.core.internal.ui.UIStateDelegate import com.adyen.checkout.ui.core.internal.ui.ViewProvidingDelegate import kotlinx.coroutines.flow.Flow +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) interface CardDelegate : PaymentComponentDelegate, ViewProvidingDelegate, diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt index f6c501cd78..53f4d62b1b 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt @@ -8,10 +8,10 @@ package com.adyen.checkout.card.internal.ui +import androidx.annotation.RestrictTo import androidx.annotation.VisibleForTesting import androidx.lifecycle.LifecycleOwner import com.adyen.checkout.card.BinLookupData -import com.adyen.checkout.card.CVCVisibility import com.adyen.checkout.card.CardBrand import com.adyen.checkout.card.CardComponentState import com.adyen.checkout.card.KCPAuthVisibility @@ -20,6 +20,7 @@ import com.adyen.checkout.card.SocialSecurityNumberVisibility import com.adyen.checkout.card.internal.data.api.DetectCardTypeRepository import com.adyen.checkout.card.internal.data.model.Brand import com.adyen.checkout.card.internal.data.model.DetectedCardType +import com.adyen.checkout.card.internal.ui.model.CVCVisibility import com.adyen.checkout.card.internal.ui.model.CardComponentParams import com.adyen.checkout.card.internal.ui.model.CardInputData import com.adyen.checkout.card.internal.ui.model.CardListItem @@ -84,7 +85,8 @@ import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -@Suppress("LongParameterList", "TooManyFunctions") +@Suppress("LongParameterList", "TooManyFunctions", "LargeClass") +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) class DefaultCardDelegate( private val observerRepository: PaymentObserverRepository, private val publicKeyRepository: PublicKeyRepository, @@ -326,8 +328,8 @@ class DefaultCardDelegate( enableLuhnCheck = enableLuhnCheck, isBrandSupported = !shouldFailWithUnsupportedBrand ), - expiryDateState = validateExpiryDate(inputData.expiryDate, selectedOrFirstCardType?.expiryDatePolicy), - securityCodeState = validateSecurityCode(inputData.securityCode, selectedOrFirstCardType), + expiryDateState = validateExpiryDate(inputData.expiryDate, reliableSelectedCard?.expiryDatePolicy), + securityCodeState = validateSecurityCode(inputData.securityCode, reliableSelectedCard), holderNameState = validateHolderName(inputData.holderName), socialSecurityNumberState = validateSocialSecurityNumber(inputData.socialSecurityNumber), kcpBirthDateOrTaxNumberState = validateKcpBirthDateOrTaxNumber(inputData.kcpBirthDateOrTaxNumber), @@ -341,8 +343,8 @@ class DefaultCardDelegate( ), installmentState = makeInstallmentFieldState(inputData.installmentOption), shouldStorePaymentMethod = inputData.isStorePaymentMethodSwitchChecked, - cvcUIState = makeCvcUIState(selectedOrFirstCardType?.cvcPolicy), - expiryDateUIState = makeExpiryDateUIState(selectedOrFirstCardType?.expiryDatePolicy), + cvcUIState = makeCvcUIState(reliableSelectedCard?.cvcPolicy), + expiryDateUIState = makeExpiryDateUIState(reliableSelectedCard?.expiryDatePolicy), holderNameUIState = getHolderNameUIState(), showStorePaymentField = showStorePaymentField(), detectedCardTypes = filteredDetectedCardTypes, @@ -479,7 +481,7 @@ class DefaultCardDelegate( securityCode: String, cardType: DetectedCardType? ): FieldState { - return if (isCvcHidden()) { + return if (isCvcHidden(makeCvcUIState(cardType?.cvcPolicy))) { FieldState( securityCode, Validation.Valid @@ -549,9 +551,9 @@ class DefaultCardDelegate( ) } - private fun isCvcHidden(): Boolean { - val hiddenAfterBinLookup = componentParams.cvcVisibility == CVCVisibility.HIDE_FIRST && - outputData.cvcUIState == InputFieldUIState.HIDDEN + private fun isCvcHidden(cvcUIState: InputFieldUIState = outputData.cvcUIState): Boolean { + val hiddenAfterBinLookup = + componentParams.cvcVisibility == CVCVisibility.HIDE_FIRST && cvcUIState == InputFieldUIState.HIDDEN return componentParams.cvcVisibility == CVCVisibility.ALWAYS_HIDE || hiddenAfterBinLookup } @@ -611,17 +613,19 @@ class DefaultCardDelegate( Logger.d(TAG, "makeCvcUIState: $cvcPolicy") return when (componentParams.cvcVisibility) { - CVCVisibility.SHOW_FIRST -> { - when { - cvcPolicy?.isRequired() == false -> InputFieldUIState.OPTIONAL + CVCVisibility.ALWAYS_SHOW -> { + when (cvcPolicy) { + Brand.FieldPolicy.OPTIONAL -> InputFieldUIState.OPTIONAL + Brand.FieldPolicy.HIDDEN -> InputFieldUIState.HIDDEN else -> InputFieldUIState.REQUIRED } } CVCVisibility.HIDE_FIRST -> { - when { - cvcPolicy?.isRequired() == false -> InputFieldUIState.OPTIONAL - else -> InputFieldUIState.REQUIRED + when (cvcPolicy) { + Brand.FieldPolicy.REQUIRED -> InputFieldUIState.REQUIRED + Brand.FieldPolicy.OPTIONAL -> InputFieldUIState.OPTIONAL + else -> InputFieldUIState.HIDDEN } } @@ -630,8 +634,9 @@ class DefaultCardDelegate( } private fun makeExpiryDateUIState(expiryDatePolicy: Brand.FieldPolicy?): InputFieldUIState { - return when { - expiryDatePolicy?.isRequired() == false -> InputFieldUIState.OPTIONAL + return when (expiryDatePolicy) { + Brand.FieldPolicy.OPTIONAL -> InputFieldUIState.OPTIONAL + Brand.FieldPolicy.HIDDEN -> InputFieldUIState.HIDDEN else -> InputFieldUIState.REQUIRED } } diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/StoredCardDelegate.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/StoredCardDelegate.kt index 4e25b413c3..98cbbb50fd 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/StoredCardDelegate.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/StoredCardDelegate.kt @@ -14,7 +14,6 @@ import com.adyen.checkout.card.BinLookupData import com.adyen.checkout.card.CardBrand import com.adyen.checkout.card.CardComponentState import com.adyen.checkout.card.CardType -import com.adyen.checkout.card.StoredCVCVisibility import com.adyen.checkout.card.internal.data.model.Brand import com.adyen.checkout.card.internal.data.model.DetectedCardType import com.adyen.checkout.card.internal.ui.model.CardComponentParams @@ -22,6 +21,7 @@ import com.adyen.checkout.card.internal.ui.model.CardInputData import com.adyen.checkout.card.internal.ui.model.CardOutputData import com.adyen.checkout.card.internal.ui.model.ExpiryDate import com.adyen.checkout.card.internal.ui.model.InputFieldUIState +import com.adyen.checkout.card.internal.ui.model.StoredCVCVisibility import com.adyen.checkout.card.internal.util.CardValidationUtils import com.adyen.checkout.components.core.OrderRequest import com.adyen.checkout.components.core.PaymentComponentData diff --git a/card/src/main/java/com/adyen/checkout/card/CVCVisibility.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CVCVisibility.kt similarity index 54% rename from card/src/main/java/com/adyen/checkout/card/CVCVisibility.kt rename to card/src/main/java/com/adyen/checkout/card/internal/ui/model/CVCVisibility.kt index 6f4a4d4387..1a975b9fed 100644 --- a/card/src/main/java/com/adyen/checkout/card/CVCVisibility.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CVCVisibility.kt @@ -6,12 +6,16 @@ * Created by ozgur on 5/9/2023. */ -package com.adyen.checkout.card +package com.adyen.checkout.card.internal.ui.model +import androidx.annotation.RestrictTo + +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) enum class CVCVisibility { - SHOW_FIRST, HIDE_FIRST, ALWAYS_HIDE + ALWAYS_SHOW, HIDE_FIRST, ALWAYS_HIDE } +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) enum class StoredCVCVisibility { SHOW, HIDE } diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParams.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParams.kt index 067cc13233..36890caba5 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParams.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParams.kt @@ -8,11 +8,10 @@ package com.adyen.checkout.card.internal.ui.model -import com.adyen.checkout.card.CVCVisibility +import androidx.annotation.RestrictTo import com.adyen.checkout.card.CardBrand import com.adyen.checkout.card.KCPAuthVisibility import com.adyen.checkout.card.SocialSecurityNumberVisibility -import com.adyen.checkout.card.StoredCVCVisibility import com.adyen.checkout.components.core.Amount import com.adyen.checkout.components.core.internal.ui.model.AnalyticsParams import com.adyen.checkout.components.core.internal.ui.model.ButtonParams @@ -21,6 +20,7 @@ import com.adyen.checkout.core.Environment import com.adyen.checkout.ui.core.internal.ui.model.AddressParams import java.util.Locale +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) data class CardComponentParams( override val shopperLocale: Locale, override val environment: Environment, diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParamsMapper.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParamsMapper.kt index c2140eea98..161279da93 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParamsMapper.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParamsMapper.kt @@ -9,12 +9,10 @@ package com.adyen.checkout.card.internal.ui.model import com.adyen.checkout.card.AddressConfiguration -import com.adyen.checkout.card.CVCVisibility import com.adyen.checkout.card.CardBrand import com.adyen.checkout.card.CardConfiguration import com.adyen.checkout.card.KCPAuthVisibility import com.adyen.checkout.card.SocialSecurityNumberVisibility -import com.adyen.checkout.card.StoredCVCVisibility import com.adyen.checkout.components.core.PaymentMethod import com.adyen.checkout.components.core.internal.ui.model.AnalyticsParams import com.adyen.checkout.components.core.internal.ui.model.ComponentParams @@ -86,8 +84,16 @@ internal class CardComponentParamsMapper( kcpAuthVisibility = kcpAuthVisibility ?: KCPAuthVisibility.HIDE, installmentParams = installmentsParamsMapper.mapToInstallmentParams(installmentConfiguration), addressParams = addressConfiguration?.mapToAddressParam() ?: AddressParams.None, - cvcVisibility = cvcVisibility ?: CVCVisibility.SHOW_FIRST, - storedCVCVisibility = storedCVCVisibility ?: StoredCVCVisibility.SHOW + cvcVisibility = if (isHideCvc == true) { + CVCVisibility.ALWAYS_HIDE + } else { + CVCVisibility.ALWAYS_SHOW + }, + storedCVCVisibility = if (isHideCvcStoredCard == true) { + StoredCVCVisibility.HIDE + } else { + StoredCVCVisibility.SHOW + } ) } diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardInputData.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardInputData.kt index ea11acc93a..52f0d917d0 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardInputData.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardInputData.kt @@ -7,10 +7,12 @@ */ package com.adyen.checkout.card.internal.ui.model +import androidx.annotation.RestrictTo import com.adyen.checkout.card.internal.ui.view.InstallmentModel import com.adyen.checkout.components.core.internal.ui.model.InputData import com.adyen.checkout.ui.core.internal.ui.model.AddressInputModel +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) data class CardInputData( var cardNumber: String = "", var expiryDate: ExpiryDate = ExpiryDate.EMPTY_DATE, diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardListItem.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardListItem.kt index 5eb07831a0..f0497afe5b 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardListItem.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardListItem.kt @@ -8,9 +8,11 @@ package com.adyen.checkout.card.internal.ui.model +import androidx.annotation.RestrictTo import com.adyen.checkout.card.CardBrand import com.adyen.checkout.core.Environment +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) data class CardListItem( val cardBrand: CardBrand, val isDetected: Boolean, diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardOutputData.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardOutputData.kt index 1438a10e12..4a5858f99c 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardOutputData.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardOutputData.kt @@ -7,6 +7,7 @@ */ package com.adyen.checkout.card.internal.ui.model +import androidx.annotation.RestrictTo import androidx.annotation.StringRes import com.adyen.checkout.card.internal.data.model.DetectedCardType import com.adyen.checkout.card.internal.ui.view.InstallmentModel @@ -15,6 +16,7 @@ import com.adyen.checkout.components.core.internal.ui.model.OutputData import com.adyen.checkout.ui.core.internal.ui.AddressFormUIState import com.adyen.checkout.ui.core.internal.ui.model.AddressOutputData +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) data class CardOutputData( val cardNumberState: FieldState, val expiryDateState: FieldState, diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InputFieldUIState.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InputFieldUIState.kt index 6cd95120f8..852a0bf8c6 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InputFieldUIState.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InputFieldUIState.kt @@ -8,6 +8,9 @@ package com.adyen.checkout.card.internal.ui.model +import androidx.annotation.RestrictTo + +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) enum class InputFieldUIState { REQUIRED, OPTIONAL, HIDDEN } diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentOption.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentOption.kt index bf6fe99a1d..c42e6a0e89 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentOption.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentOption.kt @@ -8,6 +8,9 @@ package com.adyen.checkout.card.internal.ui.model +import androidx.annotation.RestrictTo + +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) enum class InstallmentOption(val type: String?) { ONE_TIME(null), REGULAR("regular"), diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentOptionParams.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentOptionParams.kt index 16d2f7cb1c..663a7aa753 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentOptionParams.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentOptionParams.kt @@ -8,6 +8,7 @@ package com.adyen.checkout.card.internal.ui.model +import androidx.annotation.RestrictTo import com.adyen.checkout.card.CardBrand /** @@ -15,6 +16,7 @@ import com.adyen.checkout.card.CardBrand * * Note: All values specified in [values] must be greater than 1. */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) sealed class InstallmentOptionParams { abstract val values: List abstract val includeRevolving: Boolean diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentParams.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentParams.kt index fe932a7cc0..972ad84762 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentParams.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentParams.kt @@ -8,6 +8,7 @@ package com.adyen.checkout.card.internal.ui.model +import androidx.annotation.RestrictTo import com.adyen.checkout.card.CardBrand /** @@ -22,6 +23,7 @@ import com.adyen.checkout.card.CardBrand * @param defaultOptions Installment Options to be used for all card types. * @param cardBasedOptions Installment Options to be used for specific card types. */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) data class InstallmentParams( val defaultOptions: InstallmentOptionParams.DefaultInstallmentOptions? = null, val cardBasedOptions: List = emptyList() diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/view/CardView.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/view/CardView.kt index 7695726b03..ba178b1cff 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/view/CardView.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/view/CardView.kt @@ -18,6 +18,7 @@ import android.view.View.OnFocusChangeListener import android.view.WindowManager import android.widget.AdapterView import android.widget.LinearLayout +import androidx.annotation.RestrictTo import androidx.annotation.StringRes import androidx.core.view.isVisible import com.adyen.checkout.card.CardBrand @@ -55,6 +56,7 @@ import kotlinx.coroutines.flow.onEach * CardView for [CardComponent]. */ @Suppress("TooManyFunctions", "LargeClass") +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) class CardView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, @@ -596,12 +598,14 @@ class CardView @JvmOverloads constructor( localizedContext ) } + InputFieldUIState.OPTIONAL -> { binding.textInputLayoutSecurityCode.isVisible = true binding.textInputLayoutSecurityCode.hint = localizedContext.getString( R.string.checkout_card_security_code_optional_hint ) } + InputFieldUIState.HIDDEN -> { binding.textInputLayoutSecurityCode.isVisible = false // We don't expect the hidden status to change back to isVisible, so we don't worry about putting the @@ -622,12 +626,14 @@ class CardView @JvmOverloads constructor( localizedContext ) } + InputFieldUIState.OPTIONAL -> { binding.textInputLayoutExpiryDate.isVisible = true binding.textInputLayoutExpiryDate.hint = localizedContext.getString( R.string.checkout_card_expiry_date_optional_hint ) } + InputFieldUIState.HIDDEN -> { binding.textInputLayoutExpiryDate.isVisible = false val params = binding.textInputLayoutSecurityCode.layoutParams as LayoutParams @@ -665,10 +671,12 @@ class CardView @JvmOverloads constructor( binding.addressFormInput.isVisible = true binding.textInputLayoutPostalCode.isVisible = false } + AddressFormUIState.POSTAL_CODE -> { binding.addressFormInput.isVisible = false binding.textInputLayoutPostalCode.isVisible = true } + AddressFormUIState.NONE -> { binding.addressFormInput.isVisible = false binding.textInputLayoutPostalCode.isVisible = false @@ -687,6 +695,7 @@ class CardView @JvmOverloads constructor( } binding.textInputLayoutPostalCode.setLocalizedHintFromStyle(postalCodeStyleResId, localizedContext) } + else -> { // no ops } diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/view/InstallmentListAdapter.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/view/InstallmentListAdapter.kt index 0f6e5938fc..74415926a3 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/view/InstallmentListAdapter.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/view/InstallmentListAdapter.kt @@ -15,6 +15,7 @@ import android.view.ViewGroup import android.widget.BaseAdapter import android.widget.Filter import android.widget.Filterable +import androidx.annotation.RestrictTo import androidx.annotation.StringRes import androidx.recyclerview.widget.RecyclerView import com.adyen.checkout.card.databinding.InstallmentViewBinding @@ -22,6 +23,7 @@ import com.adyen.checkout.card.internal.ui.model.InstallmentOption import com.adyen.checkout.card.internal.util.InstallmentUtils // We need context to inflate the views and localizedContext to fetch the strings +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) internal class InstallmentListAdapter( private val context: Context, private val localizedContext: Context @@ -64,6 +66,7 @@ internal class InstallmentListAdapter( } } +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) data class InstallmentModel( @StringRes val textResId: Int, val value: Int?, diff --git a/card/src/test/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegateTest.kt b/card/src/test/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegateTest.kt index baf6de1ad6..fcd71591f9 100644 --- a/card/src/test/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegateTest.kt +++ b/card/src/test/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegateTest.kt @@ -12,7 +12,6 @@ import androidx.annotation.StringRes import app.cash.turbine.test import app.cash.turbine.testIn import com.adyen.checkout.card.AddressConfiguration -import com.adyen.checkout.card.CVCVisibility import com.adyen.checkout.card.CardBrand import com.adyen.checkout.card.CardComponentState import com.adyen.checkout.card.CardConfiguration @@ -22,7 +21,6 @@ import com.adyen.checkout.card.InstallmentOptions import com.adyen.checkout.card.KCPAuthVisibility import com.adyen.checkout.card.R import com.adyen.checkout.card.SocialSecurityNumberVisibility -import com.adyen.checkout.card.StoredCVCVisibility import com.adyen.checkout.card.internal.data.api.DetectCardTypeRepository import com.adyen.checkout.card.internal.data.api.TestDetectCardTypeRepository import com.adyen.checkout.card.internal.data.api.TestDetectedCardType @@ -449,7 +447,7 @@ internal class DefaultCardDelegateTest( assertTrue(expiryDateState.validation is Validation.Valid) assertTrue(securityCodeState.validation is Validation.Valid) assertEquals(InputFieldUIState.OPTIONAL, cvcUIState) - assertEquals(InputFieldUIState.OPTIONAL, expiryDateUIState) + assertEquals(InputFieldUIState.HIDDEN, expiryDateUIState) assertTrue(isDualBranded) } } @@ -547,8 +545,8 @@ internal class DefaultCardDelegateTest( delegate = createCardDelegate( configuration = CardConfiguration.Builder(Locale.US, Environment.TEST, TEST_CLIENT_KEY) - .setCvcVisibility(CVCVisibility.ALWAYS_HIDE) - .setStoredCvcVisibility(StoredCVCVisibility.HIDE) + .setHideCvc(true) + .setHideCvcStoredCard(true) .setSocialSecurityNumberVisibility(SocialSecurityNumberVisibility.SHOW) .setInstallmentConfigurations(installmentConfiguration) .setHolderNameRequired(true) @@ -1107,23 +1105,24 @@ internal class DefaultCardDelegateTest( } @Test - fun `when card number is detected over network, then callback should be called with reliable result`() = runTest { - detectCardTypeRepository.detectionResult = TestDetectedCardType.FETCHED_FROM_NETWORK + fun `when card number is detected over network, then callback should be called with reliable result`() = + runTest { + detectCardTypeRepository.detectionResult = TestDetectedCardType.FETCHED_FROM_NETWORK - delegate.setOnBinLookupListener { data -> - launch(this.coroutineContext) { - with(data.first()) { - assertEquals("mc", brand) - assertEquals("mccredit", paymentMethodVariant) - assertTrue(isReliable) + delegate.setOnBinLookupListener { data -> + launch(this.coroutineContext) { + with(data.first()) { + assertEquals("mc", brand) + assertEquals("mccredit", paymentMethodVariant) + assertTrue(isReliable) + } } } - } - delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - delegate.updateInputData { cardNumber = "5555444" } - } + delegate.updateInputData { cardNumber = "5555444" } + } @Test fun `when callback is called multiple times, then it should only trigger if the data changed`() = runTest { @@ -1191,7 +1190,7 @@ internal class DefaultCardDelegateTest( private fun getCustomCardConfigurationBuilder(): CardConfiguration.Builder { return CardConfiguration.Builder(Locale.US, Environment.TEST, TEST_CLIENT_KEY) - .setCvcVisibility(CVCVisibility.ALWAYS_HIDE) + .setHideCvc(true) .setShopperReference("shopper_android") .setSocialSecurityNumberVisibility(SocialSecurityNumberVisibility.SHOW) .setInstallmentConfigurations( diff --git a/card/src/test/java/com/adyen/checkout/card/internal/ui/StoredCardDelegateTest.kt b/card/src/test/java/com/adyen/checkout/card/internal/ui/StoredCardDelegateTest.kt index 9b7e0de4c6..219801313c 100644 --- a/card/src/test/java/com/adyen/checkout/card/internal/ui/StoredCardDelegateTest.kt +++ b/card/src/test/java/com/adyen/checkout/card/internal/ui/StoredCardDelegateTest.kt @@ -10,7 +10,6 @@ package com.adyen.checkout.card.internal.ui import app.cash.turbine.test import com.adyen.checkout.card.AddressConfiguration -import com.adyen.checkout.card.CVCVisibility import com.adyen.checkout.card.CardBrand import com.adyen.checkout.card.CardComponentState import com.adyen.checkout.card.CardConfiguration @@ -20,7 +19,6 @@ import com.adyen.checkout.card.InstallmentOptions import com.adyen.checkout.card.KCPAuthVisibility import com.adyen.checkout.card.R import com.adyen.checkout.card.SocialSecurityNumberVisibility -import com.adyen.checkout.card.StoredCVCVisibility import com.adyen.checkout.card.internal.data.model.Brand import com.adyen.checkout.card.internal.data.model.DetectedCardType import com.adyen.checkout.card.internal.ui.model.CardComponentParamsMapper @@ -110,7 +108,7 @@ internal class StoredCardDelegateTest( fun `when component is initialized with cvc shown, then view flow emits StoredCardView`() = runTest { delegate = createCardDelegate( configuration = getDefaultCardConfigurationBuilder() - .setStoredCvcVisibility(StoredCVCVisibility.SHOW) + .setHideCvcStoredCard(false) .build() ) delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) @@ -123,7 +121,7 @@ internal class StoredCardDelegateTest( fun `when component is initialized with cvc hidden, then view flow emits null`() = runTest { delegate = createCardDelegate( configuration = getDefaultCardConfigurationBuilder() - .setStoredCvcVisibility(StoredCVCVisibility.HIDE) + .setHideCvcStoredCard(true) .build() ) delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) @@ -204,7 +202,7 @@ internal class StoredCardDelegateTest( fun `security code is empty with hide cvc stored config, then output data should be valid`() = runTest { delegate = createCardDelegate( configuration = getDefaultCardConfigurationBuilder() - .setStoredCvcVisibility(StoredCVCVisibility.HIDE) + .setHideCvcStoredCard(true) .build() ) @@ -498,7 +496,7 @@ internal class StoredCardDelegateTest( private fun getCustomCardConfigurationBuilder(): CardConfiguration.Builder { return CardConfiguration.Builder(Locale.US, Environment.TEST, TEST_CLIENT_KEY) - .setCvcVisibility(CVCVisibility.ALWAYS_HIDE) + .setHideCvc(true) .setShopperReference("shopper_android") .setSocialSecurityNumberVisibility(SocialSecurityNumberVisibility.SHOW) .setInstallmentConfigurations( diff --git a/card/src/test/java/com/adyen/checkout/card/internal/ui/model/CardComponentParamsMapperTest.kt b/card/src/test/java/com/adyen/checkout/card/internal/ui/model/CardComponentParamsMapperTest.kt index 79a3498408..55415eaf44 100644 --- a/card/src/test/java/com/adyen/checkout/card/internal/ui/model/CardComponentParamsMapperTest.kt +++ b/card/src/test/java/com/adyen/checkout/card/internal/ui/model/CardComponentParamsMapperTest.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.card.internal.ui.model import com.adyen.checkout.card.AddressConfiguration -import com.adyen.checkout.card.CVCVisibility import com.adyen.checkout.card.CardBrand import com.adyen.checkout.card.CardConfiguration import com.adyen.checkout.card.CardType @@ -17,7 +16,6 @@ import com.adyen.checkout.card.InstallmentConfiguration import com.adyen.checkout.card.InstallmentOptions import com.adyen.checkout.card.KCPAuthVisibility import com.adyen.checkout.card.SocialSecurityNumberVisibility -import com.adyen.checkout.card.StoredCVCVisibility import com.adyen.checkout.components.core.Amount import com.adyen.checkout.components.core.PaymentMethod import com.adyen.checkout.components.core.internal.ui.model.AnalyticsParams @@ -82,8 +80,8 @@ internal class CardComponentParamsMapperTest { .setSupportedCardTypes(CardType.DINERS, CardType.MAESTRO) .setShopperReference(shopperReference) .setShowStorePaymentField(false) - .setCvcVisibility(CVCVisibility.ALWAYS_HIDE) - .setStoredCvcVisibility(StoredCVCVisibility.HIDE) + .setHideCvc(true) + .setHideCvcStoredCard(true) .setSubmitButtonVisible(false) .setSocialSecurityNumberVisibility(SocialSecurityNumberVisibility.SHOW) .setKcpAuthVisibility(KCPAuthVisibility.SHOW) @@ -457,7 +455,7 @@ internal class CardComponentParamsMapperTest { kcpAuthVisibility: KCPAuthVisibility = KCPAuthVisibility.HIDE, installmentParams: InstallmentParams? = null, addressParams: AddressParams = AddressParams.None, - cvcVisibility: CVCVisibility = CVCVisibility.SHOW_FIRST, + cvcVisibility: CVCVisibility = CVCVisibility.ALWAYS_SHOW, storedCVCVisibility: StoredCVCVisibility = StoredCVCVisibility.SHOW ) = CardComponentParams( shopperLocale = shopperLocale, diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/provider/ComponentParsingProvider.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/provider/ComponentParsingProvider.kt index daa1977934..841f078ad4 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/provider/ComponentParsingProvider.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/provider/ComponentParsingProvider.kt @@ -21,9 +21,10 @@ import com.adyen.checkout.bacs.BacsDirectDebitComponent import com.adyen.checkout.bacs.BacsDirectDebitComponentState import com.adyen.checkout.bacs.BacsDirectDebitConfiguration import com.adyen.checkout.bacs.internal.provider.BacsDirectDebitComponentProvider -import com.adyen.checkout.bcmc.BcmcComponentNew +import com.adyen.checkout.bcmc.BcmcComponent +import com.adyen.checkout.bcmc.BcmcComponentState import com.adyen.checkout.bcmc.BcmcConfiguration -import com.adyen.checkout.bcmc.internal.provider.BcmcComponentProviderNew +import com.adyen.checkout.bcmc.internal.provider.BcmcComponentProvider import com.adyen.checkout.blik.BlikComponent import com.adyen.checkout.blik.BlikComponentState import com.adyen.checkout.blik.BlikConfiguration @@ -248,7 +249,7 @@ internal fun getDefaultConfigForPaymentMethod( clientKey = clientKey ) - checkCompileOnly { BcmcComponentNew.PROVIDER.isPaymentMethodSupported(paymentMethod) } -> + checkCompileOnly { BcmcComponent.PROVIDER.isPaymentMethodSupported(paymentMethod) } -> BcmcConfiguration.Builder( shopperLocale = shopperLocale, environment = environment, @@ -616,14 +617,14 @@ internal fun getComponentFor( ) } - checkCompileOnly { BcmcComponentNew.PROVIDER.isPaymentMethodSupported(paymentMethod) } -> { + checkCompileOnly { BcmcComponent.PROVIDER.isPaymentMethodSupported(paymentMethod) } -> { val bcmcConfiguration: BcmcConfiguration = getConfigurationForPaymentMethod(paymentMethod, dropInConfiguration, context) - BcmcComponentProviderNew(dropInParams, sessionParams, analyticsRepository).get( + BcmcComponentProvider(dropInParams, sessionParams, analyticsRepository).get( fragment = fragment, paymentMethod = paymentMethod, configuration = bcmcConfiguration, - callback = componentCallback as ComponentCallback, + callback = componentCallback as ComponentCallback, ) } diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/configuration/CheckoutConfigurationProvider.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/configuration/CheckoutConfigurationProvider.kt index c73d7b9039..9fcaa629d7 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/configuration/CheckoutConfigurationProvider.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/configuration/CheckoutConfigurationProvider.kt @@ -6,7 +6,6 @@ import com.adyen.checkout.bacs.BacsDirectDebitConfiguration import com.adyen.checkout.bcmc.BcmcConfiguration import com.adyen.checkout.blik.BlikConfiguration import com.adyen.checkout.card.AddressConfiguration -import com.adyen.checkout.card.CVCVisibility import com.adyen.checkout.card.CardBrand import com.adyen.checkout.card.CardConfiguration import com.adyen.checkout.card.CardType @@ -75,7 +74,6 @@ internal class CheckoutConfigurationProvider @Inject constructor( .setInstallmentConfigurations(getInstallmentConfiguration()) .setAmount(amount) .setAnalyticsConfiguration(getAnalyticsConfiguration()) - .setCvcVisibility(CVCVisibility.SHOW_FIRST) .build() private fun getCashAppPayConfiguration(): CashAppPayConfiguration = From a72c92c1546725f41df4a135d09887fc83057dbd Mon Sep 17 00:00:00 2001 From: ozgur <6615094+ozgur00@users.noreply.github.com> Date: Tue, 24 Oct 2023 14:20:57 +0200 Subject: [PATCH 19/22] Get supported brands from payment method object in BCMC COAND-795 --- .../provider/BcmcComponentProvider.kt | 5 +++-- .../ui/model/BcmcComponentParamsMapper.kt | 22 +++++++++++++------ .../ui/model/BcmcComponentParamsMapperTest.kt | 16 +++++++++----- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/provider/BcmcComponentProvider.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/provider/BcmcComponentProvider.kt index 377a2ad937..b651e49510 100644 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/provider/BcmcComponentProvider.kt +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/provider/BcmcComponentProvider.kt @@ -96,7 +96,7 @@ constructor( ): BcmcComponent { assertSupported(paymentMethod) val bcmcFactory = viewModelFactory(savedStateRegistryOwner, null) { savedStateHandle -> - val componentParams = componentParamsMapper.mapToParams(configuration, null) + val componentParams = componentParamsMapper.mapToParams(configuration, null, paymentMethod) val httpClient = HttpClientFactory.getHttpClient(componentParams.environment) val publicKeyService = PublicKeyService(httpClient) val publicKeyRepository = DefaultPublicKeyRepository(publicKeyService) @@ -176,7 +176,8 @@ constructor( val bcmcFactory = viewModelFactory(savedStateRegistryOwner, null) { savedStateHandle -> val componentParams = componentParamsMapper.mapToParams( bcmcConfiguration = configuration, - sessionParams = SessionParamsFactory.create(checkoutSession) + sessionParams = SessionParamsFactory.create(checkoutSession), + paymentMethod = paymentMethod ) val httpClient = HttpClientFactory.getHttpClient(componentParams.environment) val publicKeyService = PublicKeyService(httpClient) diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapper.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapper.kt index 235810cd13..71b9c516a7 100644 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapper.kt +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapper.kt @@ -16,6 +16,7 @@ import com.adyen.checkout.card.SocialSecurityNumberVisibility import com.adyen.checkout.card.internal.ui.model.CVCVisibility import com.adyen.checkout.card.internal.ui.model.CardComponentParams import com.adyen.checkout.card.internal.ui.model.StoredCVCVisibility +import com.adyen.checkout.components.core.PaymentMethod import com.adyen.checkout.components.core.internal.ui.model.AnalyticsParams import com.adyen.checkout.components.core.internal.ui.model.ComponentParams import com.adyen.checkout.components.core.internal.ui.model.SessionParams @@ -29,14 +30,17 @@ internal class BcmcComponentParamsMapper( fun mapToParams( bcmcConfiguration: BcmcConfiguration, sessionParams: SessionParams?, + paymentMethod: PaymentMethod ): CardComponentParams { return bcmcConfiguration - .mapToParamsInternal() + .mapToParamsInternal( + supportedCardBrands = paymentMethod.brands?.map { CardBrand(it) } + ) .override(overrideComponentParams) .override(sessionParams ?: overrideSessionParams) } - private fun BcmcConfiguration.mapToParamsInternal(): CardComponentParams { + private fun BcmcConfiguration.mapToParamsInternal(supportedCardBrands: List?): CardComponentParams { return CardComponentParams( shopperLocale = shopperLocale, environment = environment, @@ -54,11 +58,7 @@ internal class BcmcComponentParamsMapper( socialSecurityNumberVisibility = SocialSecurityNumberVisibility.HIDE, cvcVisibility = CVCVisibility.HIDE_FIRST, storedCVCVisibility = StoredCVCVisibility.HIDE, - supportedCardBrands = listOf( - CardBrand(cardType = CardType.BCMC), - CardBrand(cardType = CardType.MAESTRO), - CardBrand(cardType = CardType.VISA) - ) + supportedCardBrands = supportedCardBrands ?: DEFAULT_SUPPORTED_CARD_BRANDS ) } @@ -85,4 +85,12 @@ internal class BcmcComponentParamsMapper( amount = sessionParams.amount ?: amount, ) } + + companion object { + private val DEFAULT_SUPPORTED_CARD_BRANDS = listOf( + CardBrand(cardType = CardType.BCMC), + CardBrand(cardType = CardType.MAESTRO), + CardBrand(cardType = CardType.VISA) + ) + } } diff --git a/bcmc/src/test/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperTest.kt b/bcmc/src/test/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperTest.kt index 95989dd39b..b2f4ca0abe 100644 --- a/bcmc/src/test/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperTest.kt +++ b/bcmc/src/test/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperTest.kt @@ -17,6 +17,7 @@ import com.adyen.checkout.card.internal.ui.model.CVCVisibility import com.adyen.checkout.card.internal.ui.model.CardComponentParams import com.adyen.checkout.card.internal.ui.model.StoredCVCVisibility import com.adyen.checkout.components.core.Amount +import com.adyen.checkout.components.core.PaymentMethod import com.adyen.checkout.components.core.internal.ui.model.AnalyticsParams import com.adyen.checkout.components.core.internal.ui.model.AnalyticsParamsLevel import com.adyen.checkout.components.core.internal.ui.model.GenericComponentParams @@ -37,7 +38,8 @@ internal class BcmcComponentParamsMapperTest { val bcmcConfiguration = getBcmcConfigurationBuilder() .build() - val params = BcmcComponentParamsMapper(null, null).mapToParams(bcmcConfiguration, null) + val params = BcmcComponentParamsMapper(null, null) + .mapToParams(bcmcConfiguration, null, PaymentMethod()) val expected = getCardComponentParams() @@ -55,7 +57,8 @@ internal class BcmcComponentParamsMapperTest { .setSubmitButtonVisible(false) .build() - val params = BcmcComponentParamsMapper(null, null).mapToParams(bcmcConfiguration, null) + val params = BcmcComponentParamsMapper(null, null) + .mapToParams(bcmcConfiguration, null, PaymentMethod()) val expected = getCardComponentParams( isHolderNameRequired = true, @@ -87,7 +90,8 @@ internal class BcmcComponentParamsMapperTest { ) ) - val params = BcmcComponentParamsMapper(overrideParams, null).mapToParams(bcmcConfiguration, null) + val params = BcmcComponentParamsMapper(overrideParams, null) + .mapToParams(bcmcConfiguration, null, PaymentMethod()) val expected = getCardComponentParams( shopperLocale = Locale.GERMAN, @@ -123,7 +127,8 @@ internal class BcmcComponentParamsMapperTest { installmentOptions = null, amount = null, returnUrl = "", - ) + ), + PaymentMethod() ) val expected = getCardComponentParams(isStorePaymentFieldVisible = expectedValue) @@ -154,7 +159,8 @@ internal class BcmcComponentParamsMapperTest { installmentOptions = null, amount = sessionsValue, returnUrl = "", - ) + ), + PaymentMethod() ) val expected = getCardComponentParams( From 2a392007c56bd690fcccb6b818e6746ae7afd598 Mon Sep 17 00:00:00 2001 From: ozgur <6615094+ozgur00@users.noreply.github.com> Date: Fri, 27 Oct 2023 11:55:50 +0200 Subject: [PATCH 20/22] Fix reliable selected card logic COAND-795 --- .../card/internal/ui/DefaultCardDelegate.kt | 66 +++++++++---------- .../card/internal/ui/StoredCardDelegate.kt | 22 +++---- .../card/internal/util/CardValidationUtils.kt | 10 ++- .../internal/util/DetectedCardTypesUtils.kt | 3 +- .../internal/util/CardValidationUtilsTest.kt | 62 +++++++++++------ 5 files changed, 93 insertions(+), 70 deletions(-) diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt index 53f4d62b1b..eb618939ab 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt @@ -312,8 +312,6 @@ class DefaultCardDelegate( detectedCardTypes = filteredDetectedCardTypes ) - val reliableSelectedCard = if (isReliable) selectedOrFirstCardType else null - // perform a Luhn Check if no brands are detected val enableLuhnCheck = selectedOrFirstCardType?.enableLuhnCheck ?: true @@ -328,8 +326,8 @@ class DefaultCardDelegate( enableLuhnCheck = enableLuhnCheck, isBrandSupported = !shouldFailWithUnsupportedBrand ), - expiryDateState = validateExpiryDate(inputData.expiryDate, reliableSelectedCard?.expiryDatePolicy), - securityCodeState = validateSecurityCode(inputData.securityCode, reliableSelectedCard), + expiryDateState = validateExpiryDate(inputData.expiryDate, selectedOrFirstCardType?.expiryDatePolicy), + securityCodeState = validateSecurityCode(inputData.securityCode, selectedOrFirstCardType), holderNameState = validateHolderName(inputData.holderName), socialSecurityNumberState = validateSocialSecurityNumber(inputData.socialSecurityNumber), kcpBirthDateOrTaxNumberState = validateKcpBirthDateOrTaxNumber(inputData.kcpBirthDateOrTaxNumber), @@ -337,14 +335,14 @@ class DefaultCardDelegate( addressState = validateAddress( inputData.address, addressFormUIState, - reliableSelectedCard, + selectedOrFirstCardType, updatedCountryOptions, updatedStateOptions ), installmentState = makeInstallmentFieldState(inputData.installmentOption), shouldStorePaymentMethod = inputData.isStorePaymentMethodSwitchChecked, - cvcUIState = makeCvcUIState(reliableSelectedCard?.cvcPolicy), - expiryDateUIState = makeExpiryDateUIState(reliableSelectedCard?.expiryDatePolicy), + cvcUIState = makeCvcUIState(selectedOrFirstCardType), + expiryDateUIState = makeExpiryDateUIState(selectedOrFirstCardType?.expiryDatePolicy), holderNameUIState = getHolderNameUIState(), showStorePaymentField = showStorePaymentField(), detectedCardTypes = filteredDetectedCardTypes, @@ -481,14 +479,8 @@ class DefaultCardDelegate( securityCode: String, cardType: DetectedCardType? ): FieldState { - return if (isCvcHidden(makeCvcUIState(cardType?.cvcPolicy))) { - FieldState( - securityCode, - Validation.Valid - ) - } else { - CardValidationUtils.validateSecurityCode(securityCode, cardType) - } + val cvcUIState = makeCvcUIState(cardType) + return CardValidationUtils.validateSecurityCode(securityCode, cardType, cvcUIState) } private fun validateHolderName(holderName: String): FieldState { @@ -552,9 +544,7 @@ class DefaultCardDelegate( } private fun isCvcHidden(cvcUIState: InputFieldUIState = outputData.cvcUIState): Boolean { - val hiddenAfterBinLookup = - componentParams.cvcVisibility == CVCVisibility.HIDE_FIRST && cvcUIState == InputFieldUIState.HIDDEN - return componentParams.cvcVisibility == CVCVisibility.ALWAYS_HIDE || hiddenAfterBinLookup + return cvcUIState == InputFieldUIState.HIDDEN } private fun isSocialSecurityNumberRequired(): Boolean { @@ -609,27 +599,35 @@ class DefaultCardDelegate( ) } - private fun makeCvcUIState(cvcPolicy: Brand.FieldPolicy?): InputFieldUIState { - Logger.d(TAG, "makeCvcUIState: $cvcPolicy") + private fun makeCvcUIState(detectedCardType: DetectedCardType?): InputFieldUIState { + Logger.d(TAG, "makeCvcUIState: ${detectedCardType?.cvcPolicy}") - return when (componentParams.cvcVisibility) { - CVCVisibility.ALWAYS_SHOW -> { - when (cvcPolicy) { - Brand.FieldPolicy.OPTIONAL -> InputFieldUIState.OPTIONAL - Brand.FieldPolicy.HIDDEN -> InputFieldUIState.HIDDEN - else -> InputFieldUIState.REQUIRED + return if (detectedCardType?.isReliable == true) { + when (componentParams.cvcVisibility) { + CVCVisibility.ALWAYS_SHOW -> { + when (detectedCardType.cvcPolicy) { + Brand.FieldPolicy.OPTIONAL -> InputFieldUIState.OPTIONAL + Brand.FieldPolicy.HIDDEN -> InputFieldUIState.HIDDEN + else -> InputFieldUIState.REQUIRED + } } - } - CVCVisibility.HIDE_FIRST -> { - when (cvcPolicy) { - Brand.FieldPolicy.REQUIRED -> InputFieldUIState.REQUIRED - Brand.FieldPolicy.OPTIONAL -> InputFieldUIState.OPTIONAL - else -> InputFieldUIState.HIDDEN + CVCVisibility.HIDE_FIRST -> { + when (detectedCardType.cvcPolicy) { + Brand.FieldPolicy.REQUIRED -> InputFieldUIState.REQUIRED + Brand.FieldPolicy.OPTIONAL -> InputFieldUIState.OPTIONAL + else -> InputFieldUIState.HIDDEN + } } - } - CVCVisibility.ALWAYS_HIDE -> InputFieldUIState.HIDDEN + CVCVisibility.ALWAYS_HIDE -> InputFieldUIState.HIDDEN + } + } else { + when (componentParams.cvcVisibility) { + CVCVisibility.ALWAYS_SHOW -> InputFieldUIState.REQUIRED + CVCVisibility.HIDE_FIRST -> InputFieldUIState.HIDDEN + CVCVisibility.ALWAYS_HIDE -> InputFieldUIState.HIDDEN + } } } diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/StoredCardDelegate.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/StoredCardDelegate.kt index 98cbbb50fd..5f9919b0e2 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/StoredCardDelegate.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/StoredCardDelegate.kt @@ -83,7 +83,8 @@ internal class StoredCardDelegate( isReliable = true, enableLuhnCheck = true, cvcPolicy = when { - isCvcHidden() -> Brand.FieldPolicy.HIDDEN + componentParams.storedCVCVisibility == StoredCVCVisibility.HIDE || + noCvcBrands.contains(cardType) -> Brand.FieldPolicy.HIDDEN else -> Brand.FieldPolicy.REQUIRED }, expiryDatePolicy = Brand.FieldPolicy.REQUIRED, @@ -307,18 +308,12 @@ internal class StoredCardDelegate( } private fun validateSecurityCode(securityCode: String, detectedCardType: DetectedCardType): FieldState { - return if (isCvcHidden()) { - FieldState( - securityCode, - Validation.Valid - ) - } else { - CardValidationUtils.validateSecurityCode(securityCode, detectedCardType) - } + val cvcUiState = makeCvcUIState(detectedCardType.cvcPolicy) + return CardValidationUtils.validateSecurityCode(securityCode, detectedCardType, cvcUiState) } private fun isCvcHidden(): Boolean { - return componentParams.storedCVCVisibility == StoredCVCVisibility.HIDE || noCvcBrands.contains(cardType) + return outputData.cvcUIState == InputFieldUIState.HIDDEN } private fun mapComponentState( @@ -383,9 +378,10 @@ internal class StoredCardDelegate( private fun makeCvcUIState(cvcPolicy: Brand.FieldPolicy): InputFieldUIState { Logger.d(TAG, "makeCvcUIState: $cvcPolicy") - return when (componentParams.storedCVCVisibility) { - StoredCVCVisibility.SHOW -> InputFieldUIState.REQUIRED - StoredCVCVisibility.HIDE -> InputFieldUIState.HIDDEN + return when (cvcPolicy) { + Brand.FieldPolicy.REQUIRED -> InputFieldUIState.REQUIRED + Brand.FieldPolicy.OPTIONAL -> InputFieldUIState.OPTIONAL + Brand.FieldPolicy.HIDDEN -> InputFieldUIState.HIDDEN } } diff --git a/card/src/main/java/com/adyen/checkout/card/internal/util/CardValidationUtils.kt b/card/src/main/java/com/adyen/checkout/card/internal/util/CardValidationUtils.kt index cf19c3a052..4bd3ea891c 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/util/CardValidationUtils.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/util/CardValidationUtils.kt @@ -15,6 +15,7 @@ import com.adyen.checkout.card.R import com.adyen.checkout.card.internal.data.model.Brand import com.adyen.checkout.card.internal.data.model.DetectedCardType import com.adyen.checkout.card.internal.ui.model.ExpiryDate +import com.adyen.checkout.card.internal.ui.model.InputFieldUIState import com.adyen.checkout.components.core.internal.ui.model.FieldState import com.adyen.checkout.components.core.internal.ui.model.Validation import com.adyen.checkout.core.internal.util.StringUtil @@ -137,13 +138,18 @@ object CardValidationUtils { /** * Validate Security Code. */ - internal fun validateSecurityCode(securityCode: String, detectedCardType: DetectedCardType?): FieldState { + internal fun validateSecurityCode( + securityCode: String, + detectedCardType: DetectedCardType?, + cvcUIState: InputFieldUIState + ): FieldState { val normalizedSecurityCode = StringUtil.normalize(securityCode) val length = normalizedSecurityCode.length val invalidState = Validation.Invalid(R.string.checkout_security_code_not_valid) val validation = when { + cvcUIState == InputFieldUIState.HIDDEN -> Validation.Valid !StringUtil.isDigitsAndSeparatorsOnly(normalizedSecurityCode) -> invalidState - detectedCardType?.cvcPolicy?.isRequired() == false && length == 0 -> Validation.Valid + cvcUIState == InputFieldUIState.OPTIONAL && length == 0 -> Validation.Valid detectedCardType?.cardBrand == CardBrand(cardType = CardType.AMERICAN_EXPRESS) && length == AMEX_SECURITY_CODE_SIZE -> Validation.Valid diff --git a/card/src/main/java/com/adyen/checkout/card/internal/util/DetectedCardTypesUtils.kt b/card/src/main/java/com/adyen/checkout/card/internal/util/DetectedCardTypesUtils.kt index d0c351610e..33493c2c5f 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/util/DetectedCardTypesUtils.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/util/DetectedCardTypesUtils.kt @@ -22,7 +22,8 @@ internal object DetectedCardTypesUtils { } fun getSelectedOrFirstDetectedCardType(detectedCardTypes: List): DetectedCardType? { - return getSelectedCardType(detectedCardTypes) ?: detectedCardTypes.firstOrNull() + val selectedCardType = getSelectedCardType(detectedCardTypes) + return selectedCardType ?: detectedCardTypes.firstOrNull() } fun getSelectedCardType(detectedCardTypes: List): DetectedCardType? { diff --git a/card/src/test/java/com/adyen/checkout/card/internal/util/CardValidationUtilsTest.kt b/card/src/test/java/com/adyen/checkout/card/internal/util/CardValidationUtilsTest.kt index 02b4b3c6fc..c2c1f1d68d 100644 --- a/card/src/test/java/com/adyen/checkout/card/internal/util/CardValidationUtilsTest.kt +++ b/card/src/test/java/com/adyen/checkout/card/internal/util/CardValidationUtilsTest.kt @@ -14,6 +14,7 @@ import com.adyen.checkout.card.R import com.adyen.checkout.card.internal.data.model.Brand import com.adyen.checkout.card.internal.data.model.DetectedCardType import com.adyen.checkout.card.internal.ui.model.ExpiryDate +import com.adyen.checkout.card.internal.ui.model.InputFieldUIState import com.adyen.checkout.components.core.internal.ui.model.FieldState import com.adyen.checkout.components.core.internal.ui.model.Validation import org.junit.jupiter.api.Assertions.assertEquals @@ -353,42 +354,51 @@ internal class CardValidationUtilsTest { @Test fun `cvc is empty then result should be invalid`() { val cvc = "" - val actual = CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType()) + val actual = + CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType(), InputFieldUIState.REQUIRED) assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual) } @Test fun `cvc is 1 digit then result should be invalid`() { val cvc = "7" - val actual = CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType()) + val actual = + CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType(), InputFieldUIState.REQUIRED) assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual) } @Test fun `cvc is 2 digits then result should be invalid`() { val cvc = "12" - val actual = CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType()) + val actual = + CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType(), InputFieldUIState.REQUIRED) assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual) } @Test fun `cvc is 3 digits then result should be valid`() { val cvc = "737" - val actual = CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType()) + val actual = + CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType(), InputFieldUIState.REQUIRED) assertEquals(FieldState(cvc, Validation.Valid), actual) } @Test fun `cvc is 4 digits then result should be invalid`() { val cvc = "8689" - val actual = CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType()) + val actual = + CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType(), InputFieldUIState.REQUIRED) assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual) } @Test fun `cvc is 6 digits then result should be invalid`() { val cvc = "457835" - val actual = CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType()) + val actual = CardValidationUtils.validateSecurityCode( + cvc, + getDetectedCardType(), + InputFieldUIState.REQUIRED + ) assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual) } @@ -397,7 +407,8 @@ internal class CardValidationUtilsTest { val cvc = "737" val actual = CardValidationUtils.validateSecurityCode( cvc, - getDetectedCardType(cardBrand = CardBrand(CardType.AMERICAN_EXPRESS)) + getDetectedCardType(cardBrand = CardBrand(CardType.AMERICAN_EXPRESS)), + cvcUIState = InputFieldUIState.REQUIRED ) assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual) } @@ -407,7 +418,8 @@ internal class CardValidationUtilsTest { val cvc = "8689" val actual = CardValidationUtils.validateSecurityCode( cvc, - getDetectedCardType(cardBrand = CardBrand(CardType.AMERICAN_EXPRESS)) + getDetectedCardType(cardBrand = CardBrand(CardType.AMERICAN_EXPRESS)), + cvcUIState = InputFieldUIState.REQUIRED ) assertEquals(FieldState(cvc, Validation.Valid), actual) } @@ -415,7 +427,8 @@ internal class CardValidationUtilsTest { @Test fun `cvc has invalid characters then result should be invalid`() { val cvc = "1%y" - val actual = CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType()) + val actual = + CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType(), InputFieldUIState.REQUIRED) assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual) } @@ -424,7 +437,8 @@ internal class CardValidationUtilsTest { val cvc = "546" val actual = CardValidationUtils.validateSecurityCode( cvc, - getDetectedCardType(cvcPolicy = Brand.FieldPolicy.REQUIRED) + getDetectedCardType(cvcPolicy = Brand.FieldPolicy.REQUIRED), + cvcUIState = InputFieldUIState.REQUIRED ) assertEquals(FieldState(cvc, Validation.Valid), actual) } @@ -434,7 +448,8 @@ internal class CardValidationUtilsTest { val cvc = "345" val actual = CardValidationUtils.validateSecurityCode( cvc, - getDetectedCardType(cvcPolicy = Brand.FieldPolicy.OPTIONAL) + getDetectedCardType(cvcPolicy = Brand.FieldPolicy.OPTIONAL), + cvcUIState = InputFieldUIState.OPTIONAL ) assertEquals(FieldState(cvc, Validation.Valid), actual) } @@ -444,7 +459,8 @@ internal class CardValidationUtilsTest { val cvc = "156" val actual = CardValidationUtils.validateSecurityCode( cvc, - getDetectedCardType(cvcPolicy = Brand.FieldPolicy.HIDDEN) + getDetectedCardType(cvcPolicy = Brand.FieldPolicy.HIDDEN), + cvcUIState = InputFieldUIState.HIDDEN ) assertEquals(FieldState(cvc, Validation.Valid), actual) } @@ -454,7 +470,8 @@ internal class CardValidationUtilsTest { val cvc = "77" val actual = CardValidationUtils.validateSecurityCode( cvc, - getDetectedCardType(cvcPolicy = Brand.FieldPolicy.REQUIRED) + getDetectedCardType(cvcPolicy = Brand.FieldPolicy.REQUIRED), + InputFieldUIState.REQUIRED ) assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual) } @@ -464,19 +481,21 @@ internal class CardValidationUtilsTest { val cvc = "9" val actual = CardValidationUtils.validateSecurityCode( cvc, - getDetectedCardType(cvcPolicy = Brand.FieldPolicy.OPTIONAL) + getDetectedCardType(cvcPolicy = Brand.FieldPolicy.OPTIONAL), + InputFieldUIState.OPTIONAL ) assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual) } @Test - fun `cvc is invalid with field policy hidden then result should be invalid`() { + fun `cvc is invalid with field policy hidden then result should be valid`() { val cvc = "1358" val actual = CardValidationUtils.validateSecurityCode( cvc, - getDetectedCardType(cvcPolicy = Brand.FieldPolicy.HIDDEN) + getDetectedCardType(cvcPolicy = Brand.FieldPolicy.HIDDEN), + cvcUIState = InputFieldUIState.HIDDEN ) - assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual) + assertEquals(FieldState(cvc, Validation.Valid), actual) } @Test @@ -484,7 +503,8 @@ internal class CardValidationUtilsTest { val cvc = "" val actual = CardValidationUtils.validateSecurityCode( cvc, - getDetectedCardType(cvcPolicy = Brand.FieldPolicy.REQUIRED) + getDetectedCardType(cvcPolicy = Brand.FieldPolicy.REQUIRED), + cvcUIState = InputFieldUIState.REQUIRED ) assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual) } @@ -494,7 +514,8 @@ internal class CardValidationUtilsTest { val cvc = "" val actual = CardValidationUtils.validateSecurityCode( cvc, - getDetectedCardType(cvcPolicy = Brand.FieldPolicy.OPTIONAL) + getDetectedCardType(cvcPolicy = Brand.FieldPolicy.OPTIONAL), + cvcUIState = InputFieldUIState.OPTIONAL ) assertEquals(FieldState(cvc, Validation.Valid), actual) } @@ -504,7 +525,8 @@ internal class CardValidationUtilsTest { val cvc = "" val actual = CardValidationUtils.validateSecurityCode( cvc, - getDetectedCardType(cvcPolicy = Brand.FieldPolicy.HIDDEN) + getDetectedCardType(cvcPolicy = Brand.FieldPolicy.HIDDEN), + cvcUIState = InputFieldUIState.HIDDEN ) assertEquals(FieldState(cvc, Validation.Valid), actual) } From 8e12f470ccf6d3f3583d2155f24393ea17e46f85 Mon Sep 17 00:00:00 2001 From: ozgur <6615094+ozgur00@users.noreply.github.com> Date: Tue, 31 Oct 2023 14:18:53 +0100 Subject: [PATCH 21/22] Hide card brand logo list for bcmc COAND-795 --- .../adyen/checkout/card/internal/ui/DefaultCardDelegate.kt | 4 +++- .../checkout/card/internal/ui/DefaultCardDelegateTest.kt | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt index eb618939ab..58aaca5762 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt @@ -364,7 +364,9 @@ class DefaultCardDelegate( private fun isCardListVisible( cardBrands: List, detectedCardTypes: List - ): Boolean = cardBrands.isNotEmpty() && detectedCardTypes.isEmpty() + ): Boolean = cardBrands.isNotEmpty() && + detectedCardTypes.isEmpty() && + paymentMethod.type == PaymentMethodTypes.SCHEME override fun getPaymentMethodType(): String { return paymentMethod.type ?: PaymentMethodTypes.UNKNOWN diff --git a/card/src/test/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegateTest.kt b/card/src/test/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegateTest.kt index fcd71591f9..80e0655a82 100644 --- a/card/src/test/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegateTest.kt +++ b/card/src/test/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegateTest.kt @@ -1156,7 +1156,7 @@ internal class DefaultCardDelegateTest( cardEncrypter: BaseCardEncrypter = this.cardEncrypter, genericEncrypter: BaseGenericEncrypter = this.genericEncrypter, configuration: CardConfiguration = getDefaultCardConfigurationBuilder().build(), - paymentMethod: PaymentMethod = PaymentMethod(), + paymentMethod: PaymentMethod = PaymentMethod(type = PaymentMethodTypes.SCHEME), analyticsRepository: AnalyticsRepository = this.analyticsRepository, submitHandler: SubmitHandler = this.submitHandler, order: OrderRequest? = TEST_ORDER, From 43cabcc9ff839554e8a26e22dc9c6827befbcfbc Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Fri, 10 Nov 2023 11:07:47 +0100 Subject: [PATCH 22/22] Revert "Refactor BCMC" --- .../com/adyen/checkout/bcmc/BcmcComponent.kt | 90 +++- .../adyen/checkout/bcmc/BcmcComponentState.kt | 15 +- .../provider/BcmcComponentProvider.kt | 155 +++--- .../checkout/bcmc/internal/ui/BcmcDelegate.kt | 43 ++ .../bcmc/internal/ui/BcmcViewProvider.kt | 4 +- .../bcmc/internal/ui/DefaultBcmcDelegate.kt | 296 +++++++++++ .../internal/ui/model/BcmcComponentParams.kt | 29 ++ .../ui/model/BcmcComponentParamsMapper.kt | 47 +- .../bcmc/internal/ui/model/BcmcInputData.kt | 18 + .../bcmc/internal/ui/model/BcmcOutputData.kt | 27 + .../bcmc/internal/ui/view/BcmcView.kt | 206 ++++++++ .../adyen/checkout/bcmc/BcmcComponentTest.kt | 43 +- .../internal/ui/DefaultBcmcDelegateTest.kt | 487 ++++++++++++++++++ .../ui/model/BcmcComponentParamsMapperTest.kt | 57 +- .../com/adyen/checkout/card/CardComponent.kt | 9 +- .../internal/data/api/BinLookupService.kt | 4 +- .../api/DefaultDetectCardTypeRepository.kt | 18 +- .../data/api/DetectCardTypeRepository.kt | 5 +- .../internal/data/model/BinLookupRequest.kt | 12 +- .../internal/data/model/BinLookupResponse.kt | 4 +- .../internal/data/model/DetectedCardType.kt | 4 +- .../checkout/card/internal/ui/CardDelegate.kt | 4 +- .../card/internal/ui/DefaultCardDelegate.kt | 74 +-- .../card/internal/ui/StoredCardDelegate.kt | 24 +- .../card/internal/ui/model/CVCVisibility.kt | 21 - .../internal/ui/model/CardComponentParams.kt | 8 +- .../ui/model/CardComponentParamsMapper.kt | 20 +- .../card/internal/ui/model/CardInputData.kt | 4 +- .../card/internal/ui/model/CardListItem.kt | 4 +- .../card/internal/ui/model/CardOutputData.kt | 4 +- .../internal/ui/model/InputFieldUIState.kt | 5 +- .../internal/ui/model/InstallmentOption.kt | 5 +- .../ui/model/InstallmentOptionParams.kt | 4 +- .../internal/ui/model/InstallmentParams.kt | 4 +- .../card/internal/ui/view/CardView.kt | 11 +- .../ui/view/InstallmentListAdapter.kt | 5 +- .../card/internal/util/CardValidationUtils.kt | 10 +- .../internal/util/DetectedCardTypesUtils.kt | 3 +- .../data/api/TestDetectCardTypeRepository.kt | 1 - .../internal/ui/DefaultCardDelegateTest.kt | 29 +- .../ui/model/CardComponentParamsMapperTest.kt | 14 +- .../internal/util/CardValidationUtilsTest.kt | 62 +-- .../test/extensions/ViewModelExtensions.kt | 6 +- 43 files changed, 1422 insertions(+), 473 deletions(-) create mode 100644 bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/BcmcDelegate.kt create mode 100644 bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/DefaultBcmcDelegate.kt create mode 100644 bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParams.kt create mode 100644 bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcInputData.kt create mode 100644 bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcOutputData.kt create mode 100644 bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/view/BcmcView.kt create mode 100644 bcmc/src/test/java/com/adyen/checkout/bcmc/internal/ui/DefaultBcmcDelegateTest.kt delete mode 100644 card/src/main/java/com/adyen/checkout/card/internal/ui/model/CVCVisibility.kt diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcComponent.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcComponent.kt index 7f1a786332..e784956fdf 100644 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcComponent.kt +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcComponent.kt @@ -1,36 +1,106 @@ /* - * Copyright (c) 2023 Adyen N.V. + * Copyright (c) 2019 Adyen N.V. * * This file is open source and available under the MIT license. See the LICENSE file for more info. * - * Created by ozgur on 22/8/2023. + * Created by arman on 18/9/2019. */ - package com.adyen.checkout.bcmc +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.adyen.checkout.action.core.internal.ActionHandlingComponent import com.adyen.checkout.action.core.internal.DefaultActionHandlingComponent import com.adyen.checkout.action.core.internal.ui.GenericActionDelegate import com.adyen.checkout.bcmc.internal.provider.BcmcComponentProvider -import com.adyen.checkout.card.CardComponent -import com.adyen.checkout.card.internal.ui.CardDelegate +import com.adyen.checkout.bcmc.internal.ui.BcmcDelegate +import com.adyen.checkout.card.CardBrand +import com.adyen.checkout.card.CardType import com.adyen.checkout.components.core.PaymentMethodTypes +import com.adyen.checkout.components.core.internal.ButtonComponent import com.adyen.checkout.components.core.internal.ComponentEventHandler import com.adyen.checkout.components.core.internal.PaymentComponent +import com.adyen.checkout.components.core.internal.PaymentComponentEvent +import com.adyen.checkout.components.core.internal.toActionCallback +import com.adyen.checkout.components.core.internal.ui.ComponentDelegate +import com.adyen.checkout.core.internal.util.LogUtil +import com.adyen.checkout.core.internal.util.Logger +import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate +import com.adyen.checkout.ui.core.internal.ui.ComponentViewType +import com.adyen.checkout.ui.core.internal.ui.ViewableComponent +import com.adyen.checkout.ui.core.internal.util.mergeViewFlows +import kotlinx.coroutines.flow.Flow /** * A [PaymentComponent] that supports the [PaymentMethodTypes.BCMC] payment method. */ -class BcmcComponent( - cardDelegate: CardDelegate, - genericActionDelegate: GenericActionDelegate, - actionHandlingComponent: DefaultActionHandlingComponent, +class BcmcComponent internal constructor( + private val bcmcDelegate: BcmcDelegate, + private val genericActionDelegate: GenericActionDelegate, + private val actionHandlingComponent: DefaultActionHandlingComponent, internal val componentEventHandler: ComponentEventHandler, -) : CardComponent(cardDelegate, genericActionDelegate, actionHandlingComponent, componentEventHandler) { +) : ViewModel(), + PaymentComponent, + ViewableComponent, + ButtonComponent, + ActionHandlingComponent by actionHandlingComponent { + + override val delegate: ComponentDelegate get() = actionHandlingComponent.activeDelegate + + override val viewFlow: Flow = mergeViewFlows( + viewModelScope, + bcmcDelegate.viewFlow, + genericActionDelegate.viewFlow, + ) + + init { + bcmcDelegate.initialize(viewModelScope) + genericActionDelegate.initialize(viewModelScope) + componentEventHandler.initialize(viewModelScope) + } + + internal fun observe( + lifecycleOwner: LifecycleOwner, + callback: (PaymentComponentEvent) -> Unit + ) { + bcmcDelegate.observe(lifecycleOwner, viewModelScope, callback) + genericActionDelegate.observe(lifecycleOwner, viewModelScope, callback.toActionCallback()) + } + + internal fun removeObserver() { + bcmcDelegate.removeObserver() + genericActionDelegate.removeObserver() + } + + override fun isConfirmationRequired(): Boolean = bcmcDelegate.isConfirmationRequired() + + override fun submit() { + (delegate as? ButtonDelegate)?.onSubmit() ?: Logger.e(TAG, "Component is currently not submittable, ignoring.") + } + + override fun setInteractionBlocked(isInteractionBlocked: Boolean) { + (delegate as? BcmcDelegate)?.setInteractionBlocked(isInteractionBlocked) + ?: Logger.e(TAG, "Payment component is not interactable, ignoring.") + } + + override fun onCleared() { + super.onCleared() + Logger.d(TAG, "onCleared") + bcmcDelegate.onCleared() + genericActionDelegate.onCleared() + componentEventHandler.onCleared() + } + companion object { + private val TAG = LogUtil.getTag() + @JvmField val PROVIDER = BcmcComponentProvider() @JvmField val PAYMENT_METHOD_TYPES = listOf(PaymentMethodTypes.BCMC) + + internal val SUPPORTED_CARD_TYPE = CardBrand(cardType = CardType.BCMC) } } diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcComponentState.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcComponentState.kt index 1cfdb7d0d6..53e71d76ba 100644 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcComponentState.kt +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/BcmcComponentState.kt @@ -3,11 +3,20 @@ * * This file is open source and available under the MIT license. See the LICENSE file for more info. * - * Created by ozgur on 27/9/2023. + * Created by ozgur on 20/2/2023. */ package com.adyen.checkout.bcmc -import com.adyen.checkout.card.CardComponentState +import com.adyen.checkout.components.core.PaymentComponentData +import com.adyen.checkout.components.core.PaymentComponentState +import com.adyen.checkout.components.core.paymentmethod.CardPaymentMethod -typealias BcmcComponentState = CardComponentState +/** + * Represents the state of [BcmcComponent]. + */ +data class BcmcComponentState( + override val data: PaymentComponentData, + override val isInputValid: Boolean, + override val isReady: Boolean +) : PaymentComponentState diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/provider/BcmcComponentProvider.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/provider/BcmcComponentProvider.kt index b651e49510..8badfe4aa2 100644 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/provider/BcmcComponentProvider.kt +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/provider/BcmcComponentProvider.kt @@ -1,9 +1,9 @@ /* - * Copyright (c) 2023 Adyen N.V. + * Copyright (c) 2019 Adyen N.V. * * This file is open source and available under the MIT license. See the LICENSE file for more info. * - * Created by ozgur on 22/8/2023. + * Created by arman on 18/9/2019. */ package com.adyen.checkout.bcmc.internal.provider @@ -19,11 +19,9 @@ import com.adyen.checkout.action.core.internal.provider.GenericActionComponentPr import com.adyen.checkout.bcmc.BcmcComponent import com.adyen.checkout.bcmc.BcmcComponentState import com.adyen.checkout.bcmc.BcmcConfiguration +import com.adyen.checkout.bcmc.internal.ui.DefaultBcmcDelegate import com.adyen.checkout.bcmc.internal.ui.model.BcmcComponentParamsMapper -import com.adyen.checkout.card.internal.data.api.BinLookupService -import com.adyen.checkout.card.internal.data.api.DefaultDetectCardTypeRepository import com.adyen.checkout.card.internal.ui.CardValidationMapper -import com.adyen.checkout.card.internal.ui.DefaultCardDelegate import com.adyen.checkout.components.core.ComponentCallback import com.adyen.checkout.components.core.Order import com.adyen.checkout.components.core.PaymentMethod @@ -56,8 +54,6 @@ import com.adyen.checkout.sessions.core.internal.data.api.SessionRepository import com.adyen.checkout.sessions.core.internal.data.api.SessionService import com.adyen.checkout.sessions.core.internal.provider.SessionPaymentComponentProvider import com.adyen.checkout.sessions.core.internal.ui.model.SessionParamsFactory -import com.adyen.checkout.ui.core.internal.data.api.AddressService -import com.adyen.checkout.ui.core.internal.data.api.DefaultAddressRepository import com.adyen.checkout.ui.core.internal.ui.SubmitHandler class BcmcComponentProvider @@ -82,7 +78,6 @@ constructor( private val componentParamsMapper = BcmcComponentParamsMapper(overrideComponentParams, overrideSessionParams) - @Suppress("LongMethod") override fun get( savedStateRegistryOwner: SavedStateRegistryOwner, viewModelStoreOwner: ViewModelStoreOwner, @@ -95,45 +90,39 @@ constructor( key: String?, ): BcmcComponent { assertSupported(paymentMethod) - val bcmcFactory = viewModelFactory(savedStateRegistryOwner, null) { savedStateHandle -> - val componentParams = componentParamsMapper.mapToParams(configuration, null, paymentMethod) - val httpClient = HttpClientFactory.getHttpClient(componentParams.environment) - val publicKeyService = PublicKeyService(httpClient) - val publicKeyRepository = DefaultPublicKeyRepository(publicKeyService) - val cardValidationMapper = CardValidationMapper() - val dateGenerator = DateGenerator() - val clientSideEncrypter = ClientSideEncrypter() - val genericEncrypter = DefaultGenericEncrypter(clientSideEncrypter, dateGenerator) - val cardEncrypter = DefaultCardEncrypter(genericEncrypter) - val addressService = AddressService(httpClient) - val addressRepository = DefaultAddressRepository(addressService) - val binLookupService = BinLookupService(httpClient) - val detectCardTypeRepository = DefaultDetectCardTypeRepository(cardEncrypter, binLookupService) - val analyticsRepository = analyticsRepository ?: DefaultAnalyticsRepository( - analyticsRepositoryData = AnalyticsRepositoryData( - application = application, - componentParams = componentParams, - paymentMethod = paymentMethod, - ), - analyticsService = AnalyticsService( - HttpClientFactory.getAnalyticsHttpClient(componentParams.environment) - ), - analyticsMapper = AnalyticsMapper(), - ) + val componentParams = componentParamsMapper.mapToParams(configuration, null) + val httpClient = HttpClientFactory.getHttpClient(componentParams.environment) + val publicKeyService = PublicKeyService(httpClient) + val publicKeyRepository = DefaultPublicKeyRepository(publicKeyService) + val cardValidationMapper = CardValidationMapper() + val dateGenerator = DateGenerator() + val clientSideEncrypter = ClientSideEncrypter() + val genericEncrypter = DefaultGenericEncrypter(clientSideEncrypter, dateGenerator) + val cardEncrypter = DefaultCardEncrypter(genericEncrypter) + + val analyticsRepository = analyticsRepository ?: DefaultAnalyticsRepository( + analyticsRepositoryData = AnalyticsRepositoryData( + application = application, + componentParams = componentParams, + paymentMethod = paymentMethod, + ), + analyticsService = AnalyticsService( + HttpClientFactory.getAnalyticsHttpClient(componentParams.environment) + ), + analyticsMapper = AnalyticsMapper(), + ) - val cardDelegate = DefaultCardDelegate( + val bcmcFactory = viewModelFactory(savedStateRegistryOwner, null) { savedStateHandle -> + val bcmcDelegate = DefaultBcmcDelegate( observerRepository = PaymentObserverRepository(), - publicKeyRepository = publicKeyRepository, - componentParams = componentParams, paymentMethod = paymentMethod, order = order, - analyticsRepository = analyticsRepository, - addressRepository = addressRepository, - detectCardTypeRepository = detectCardTypeRepository, + publicKeyRepository = publicKeyRepository, + componentParams = componentParams, cardValidationMapper = cardValidationMapper, cardEncrypter = cardEncrypter, - genericEncrypter = genericEncrypter, + analyticsRepository = analyticsRepository, submitHandler = SubmitHandler(savedStateHandle) ) @@ -144,16 +133,13 @@ constructor( ) BcmcComponent( - cardDelegate = cardDelegate, + bcmcDelegate = bcmcDelegate, genericActionDelegate = genericActionDelegate, - actionHandlingComponent = DefaultActionHandlingComponent(genericActionDelegate, cardDelegate), + actionHandlingComponent = DefaultActionHandlingComponent(genericActionDelegate, bcmcDelegate), componentEventHandler = DefaultComponentEventHandler(), ) } - return ViewModelProvider( - viewModelStoreOwner, - bcmcFactory - )[key, BcmcComponent::class.java].also { component -> + return ViewModelProvider(viewModelStoreOwner, bcmcFactory)[key, BcmcComponent::class.java].also { component -> component.observe(lifecycleOwner) { component.componentEventHandler.onPaymentComponentEvent(it, componentCallback) } @@ -173,50 +159,43 @@ constructor( key: String? ): BcmcComponent { assertSupported(paymentMethod) - val bcmcFactory = viewModelFactory(savedStateRegistryOwner, null) { savedStateHandle -> - val componentParams = componentParamsMapper.mapToParams( - bcmcConfiguration = configuration, - sessionParams = SessionParamsFactory.create(checkoutSession), - paymentMethod = paymentMethod - ) - val httpClient = HttpClientFactory.getHttpClient(componentParams.environment) - val publicKeyService = PublicKeyService(httpClient) - val publicKeyRepository = DefaultPublicKeyRepository(publicKeyService) - val cardValidationMapper = CardValidationMapper() - val dateGenerator = DateGenerator() - val clientSideEncrypter = ClientSideEncrypter() - val genericEncrypter = DefaultGenericEncrypter(clientSideEncrypter, dateGenerator) - val cardEncrypter = DefaultCardEncrypter(genericEncrypter) - val addressService = AddressService(httpClient) - val addressRepository = DefaultAddressRepository(addressService) - val binLookupService = BinLookupService(httpClient) - val detectCardTypeRepository = DefaultDetectCardTypeRepository(cardEncrypter, binLookupService) - val analyticsRepository = analyticsRepository ?: DefaultAnalyticsRepository( - analyticsRepositoryData = AnalyticsRepositoryData( - application = application, - componentParams = componentParams, - paymentMethod = paymentMethod, - sessionId = checkoutSession.sessionSetupResponse.id, - ), - analyticsService = AnalyticsService( - HttpClientFactory.getAnalyticsHttpClient(componentParams.environment) - ), - analyticsMapper = AnalyticsMapper(), - ) + val componentParams = componentParamsMapper.mapToParams( + bcmcConfiguration = configuration, + sessionParams = SessionParamsFactory.create(checkoutSession) + ) + val httpClient = HttpClientFactory.getHttpClient(componentParams.environment) + val publicKeyService = PublicKeyService(httpClient) + val publicKeyRepository = DefaultPublicKeyRepository(publicKeyService) + val cardValidationMapper = CardValidationMapper() + val dateGenerator = DateGenerator() + val clientSideEncrypter = ClientSideEncrypter() + val genericEncrypter = DefaultGenericEncrypter(clientSideEncrypter, dateGenerator) + val cardEncrypter = DefaultCardEncrypter(genericEncrypter) + + val analyticsRepository = analyticsRepository ?: DefaultAnalyticsRepository( + analyticsRepositoryData = AnalyticsRepositoryData( + application = application, + componentParams = componentParams, + paymentMethod = paymentMethod, + sessionId = checkoutSession.sessionSetupResponse.id, + ), + analyticsService = AnalyticsService( + HttpClientFactory.getAnalyticsHttpClient(componentParams.environment) + ), + analyticsMapper = AnalyticsMapper(), + ) - val cardDelegate = DefaultCardDelegate( + val bcmcFactory = viewModelFactory(savedStateRegistryOwner, null) { savedStateHandle -> + val bcmcDelegate = DefaultBcmcDelegate( observerRepository = PaymentObserverRepository(), - publicKeyRepository = publicKeyRepository, - componentParams = componentParams, paymentMethod = paymentMethod, order = checkoutSession.order, - analyticsRepository = analyticsRepository, - addressRepository = addressRepository, - detectCardTypeRepository = detectCardTypeRepository, + publicKeyRepository = publicKeyRepository, + componentParams = componentParams, cardValidationMapper = cardValidationMapper, cardEncrypter = cardEncrypter, - genericEncrypter = genericEncrypter, + analyticsRepository = analyticsRepository, submitHandler = SubmitHandler(savedStateHandle) ) @@ -246,17 +225,13 @@ constructor( ) BcmcComponent( - cardDelegate = cardDelegate, + bcmcDelegate = bcmcDelegate, genericActionDelegate = genericActionDelegate, - actionHandlingComponent = DefaultActionHandlingComponent(genericActionDelegate, cardDelegate), + actionHandlingComponent = DefaultActionHandlingComponent(genericActionDelegate, bcmcDelegate), componentEventHandler = sessionComponentEventHandler, ) } - - return ViewModelProvider( - viewModelStoreOwner, - bcmcFactory - )[key, BcmcComponent::class.java].also { component -> + return ViewModelProvider(viewModelStoreOwner, bcmcFactory)[key, BcmcComponent::class.java].also { component -> component.observe(lifecycleOwner) { component.componentEventHandler.onPaymentComponentEvent(it, componentCallback) } diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/BcmcDelegate.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/BcmcDelegate.kt new file mode 100644 index 0000000000..00cb78d269 --- /dev/null +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/BcmcDelegate.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by oscars on 8/7/2022. + */ + +package com.adyen.checkout.bcmc.internal.ui + +import com.adyen.checkout.bcmc.BcmcComponentState +import com.adyen.checkout.bcmc.internal.ui.model.BcmcComponentParams +import com.adyen.checkout.bcmc.internal.ui.model.BcmcInputData +import com.adyen.checkout.bcmc.internal.ui.model.BcmcOutputData +import com.adyen.checkout.components.core.internal.ui.PaymentComponentDelegate +import com.adyen.checkout.core.exception.CheckoutException +import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate +import com.adyen.checkout.ui.core.internal.ui.UIStateDelegate +import com.adyen.checkout.ui.core.internal.ui.ViewProvidingDelegate +import kotlinx.coroutines.flow.Flow + +internal interface BcmcDelegate : + PaymentComponentDelegate, + ViewProvidingDelegate, + ButtonDelegate, + UIStateDelegate { + + override val componentParams: BcmcComponentParams + + val outputData: BcmcOutputData + + val outputDataFlow: Flow + + val componentStateFlow: Flow + + val exceptionFlow: Flow + + fun isCardNumberSupported(cardNumber: String?): Boolean + + fun updateInputData(update: BcmcInputData.() -> Unit) + + fun setInteractionBlocked(isInteractionBlocked: Boolean) +} diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/BcmcViewProvider.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/BcmcViewProvider.kt index ed7b663145..ad42c0230f 100644 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/BcmcViewProvider.kt +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/BcmcViewProvider.kt @@ -9,7 +9,7 @@ package com.adyen.checkout.bcmc.internal.ui import android.content.Context -import com.adyen.checkout.card.internal.ui.view.CardView +import com.adyen.checkout.bcmc.internal.ui.view.BcmcView import com.adyen.checkout.ui.core.internal.ui.AmountButtonComponentViewType import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType import com.adyen.checkout.ui.core.internal.ui.ComponentView @@ -22,7 +22,7 @@ internal object BcmcViewProvider : ViewProvider { viewType: ComponentViewType, context: Context, ): ComponentView = when (viewType) { - BcmcComponentViewType -> CardView(context) + BcmcComponentViewType -> BcmcView(context) else -> throw IllegalArgumentException("Unsupported view type") } } diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/DefaultBcmcDelegate.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/DefaultBcmcDelegate.kt new file mode 100644 index 0000000000..36d3877425 --- /dev/null +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/DefaultBcmcDelegate.kt @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2022 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by oscars on 8/7/2022. + */ + +package com.adyen.checkout.bcmc.internal.ui + +import androidx.annotation.VisibleForTesting +import androidx.lifecycle.LifecycleOwner +import com.adyen.checkout.bcmc.BcmcComponent +import com.adyen.checkout.bcmc.BcmcComponentState +import com.adyen.checkout.bcmc.internal.ui.model.BcmcComponentParams +import com.adyen.checkout.bcmc.internal.ui.model.BcmcInputData +import com.adyen.checkout.bcmc.internal.ui.model.BcmcOutputData +import com.adyen.checkout.card.CardBrand +import com.adyen.checkout.card.R +import com.adyen.checkout.card.internal.data.model.Brand +import com.adyen.checkout.card.internal.ui.CardValidationMapper +import com.adyen.checkout.card.internal.ui.model.ExpiryDate +import com.adyen.checkout.card.internal.util.CardValidationUtils +import com.adyen.checkout.components.core.Order +import com.adyen.checkout.components.core.PaymentComponentData +import com.adyen.checkout.components.core.PaymentMethod +import com.adyen.checkout.components.core.PaymentMethodTypes +import com.adyen.checkout.components.core.internal.PaymentComponentEvent +import com.adyen.checkout.components.core.internal.PaymentObserverRepository +import com.adyen.checkout.components.core.internal.data.api.AnalyticsRepository +import com.adyen.checkout.components.core.internal.data.api.PublicKeyRepository +import com.adyen.checkout.components.core.internal.ui.model.FieldState +import com.adyen.checkout.components.core.internal.ui.model.Validation +import com.adyen.checkout.components.core.internal.util.bufferedChannel +import com.adyen.checkout.components.core.paymentmethod.CardPaymentMethod +import com.adyen.checkout.core.exception.CheckoutException +import com.adyen.checkout.core.exception.ComponentException +import com.adyen.checkout.core.internal.util.LogUtil +import com.adyen.checkout.core.internal.util.Logger +import com.adyen.checkout.core.internal.util.runCompileOnly +import com.adyen.checkout.cse.EncryptedCard +import com.adyen.checkout.cse.EncryptionException +import com.adyen.checkout.cse.UnencryptedCard +import com.adyen.checkout.cse.internal.BaseCardEncrypter +import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType +import com.adyen.checkout.ui.core.internal.ui.ComponentViewType +import com.adyen.checkout.ui.core.internal.ui.PaymentComponentUIEvent +import com.adyen.checkout.ui.core.internal.ui.PaymentComponentUIState +import com.adyen.checkout.ui.core.internal.ui.SubmitHandler +import com.adyen.threeds2.ThreeDS2Service +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.launch + +@Suppress("TooManyFunctions", "LongParameterList") +internal class DefaultBcmcDelegate( + private val observerRepository: PaymentObserverRepository, + private val paymentMethod: PaymentMethod, + private val order: Order?, + private val analyticsRepository: AnalyticsRepository, + private val publicKeyRepository: PublicKeyRepository, + override val componentParams: BcmcComponentParams, + private val cardValidationMapper: CardValidationMapper, + private val cardEncrypter: BaseCardEncrypter, + private val submitHandler: SubmitHandler +) : BcmcDelegate { + + private val inputData = BcmcInputData() + + private val _outputDataFlow = MutableStateFlow(createOutputData()) + override val outputDataFlow: Flow = _outputDataFlow + + private val _componentStateFlow = MutableStateFlow(createComponentState()) + override val componentStateFlow: Flow = _componentStateFlow + + private val exceptionChannel: Channel = bufferedChannel() + override val exceptionFlow: Flow = exceptionChannel.receiveAsFlow() + + override val outputData get() = _outputDataFlow.value + + private val _viewFlow: MutableStateFlow = MutableStateFlow(BcmcComponentViewType) + override val viewFlow: Flow = _viewFlow + + override val submitFlow: Flow = submitHandler.submitFlow + + override val uiStateFlow: Flow = submitHandler.uiStateFlow + + override val uiEventFlow: Flow = submitHandler.uiEventFlow + + private var publicKey: String? = null + + override fun initialize(coroutineScope: CoroutineScope) { + submitHandler.initialize(coroutineScope, componentStateFlow) + setupAnalytics(coroutineScope) + fetchPublicKey(coroutineScope) + } + + private fun setupAnalytics(coroutineScope: CoroutineScope) { + Logger.v(TAG, "setupAnalytics") + coroutineScope.launch { + analyticsRepository.setupAnalytics() + } + } + + private fun fetchPublicKey(coroutineScope: CoroutineScope) { + Logger.d(TAG, "fetchPublicKey") + coroutineScope.launch { + publicKeyRepository.fetchPublicKey( + environment = componentParams.environment, + clientKey = componentParams.clientKey + ).fold( + onSuccess = { key -> + Logger.d(TAG, "Public key fetched") + publicKey = key + updateComponentState(outputData) + }, + onFailure = { e -> + Logger.e(TAG, "Unable to fetch public key") + exceptionChannel.trySend(ComponentException("Unable to fetch publicKey.", e)) + } + ) + } + } + + override fun observe( + lifecycleOwner: LifecycleOwner, + coroutineScope: CoroutineScope, + callback: (PaymentComponentEvent) -> Unit + ) { + observerRepository.addObservers( + stateFlow = componentStateFlow, + exceptionFlow = exceptionFlow, + submitFlow = submitFlow, + lifecycleOwner = lifecycleOwner, + coroutineScope = coroutineScope, + callback = callback + ) + } + + override fun removeObserver() { + observerRepository.removeObservers() + } + + override fun updateInputData(update: BcmcInputData.() -> Unit) { + inputData.update() + onInputDataChanged() + } + + private fun onInputDataChanged() { + val outputData = createOutputData() + + _outputDataFlow.tryEmit(outputData) + + updateComponentState(outputData) + } + + private fun createOutputData() = BcmcOutputData( + cardNumberField = validateCardNumber(inputData.cardNumber), + expiryDateField = CardValidationUtils.validateExpiryDate(inputData.expiryDate, Brand.FieldPolicy.REQUIRED), + cardHolderNameField = validateHolderName(inputData.cardHolderName), + shouldStorePaymentMethod = inputData.isStorePaymentMethodSwitchChecked, + showStorePaymentField = showStorePaymentField(), + ) + + private fun validateCardNumber(cardNumber: String): FieldState { + val validation = + CardValidationUtils.validateCardNumber(cardNumber, enableLuhnCheck = true, isBrandSupported = true) + return cardValidationMapper.mapCardNumberValidation(cardNumber, validation) + } + + private fun validateHolderName(holderName: String): FieldState { + return if (componentParams.isHolderNameRequired && holderName.isBlank()) { + FieldState( + holderName, + Validation.Invalid(R.string.checkout_holder_name_not_valid) + ) + } else { + FieldState( + holderName, + Validation.Valid + ) + } + } + + private fun showStorePaymentField(): Boolean { + return componentParams.isStorePaymentFieldVisible + } + + @VisibleForTesting + internal fun updateComponentState(outputData: BcmcOutputData) { + Logger.v(TAG, "updateComponentState") + val componentState = createComponentState(outputData) + _componentStateFlow.tryEmit(componentState) + } + + @Suppress("ReturnCount") + private fun createComponentState( + outputData: BcmcOutputData = this.outputData + ): BcmcComponentState { + val publicKey = publicKey + + // If data is not valid we just return empty object, encryption would fail and we don't pass unencrypted data. + if (!outputData.isValid || publicKey == null) { + return BcmcComponentState( + data = PaymentComponentData(null, null, null), + isInputValid = outputData.isValid, + isReady = publicKey != null, + ) + } + + val encryptedCard = encryptCardData(outputData, publicKey) ?: return BcmcComponentState( + data = PaymentComponentData(null, null, null), + isInputValid = false, + isReady = true, + ) + + // BCMC payment method is scheme type. + val cardPaymentMethod = CardPaymentMethod( + type = CardPaymentMethod.PAYMENT_METHOD_TYPE, + checkoutAttemptId = analyticsRepository.getCheckoutAttemptId(), + encryptedCardNumber = encryptedCard.encryptedCardNumber, + encryptedExpiryMonth = encryptedCard.encryptedExpiryMonth, + encryptedExpiryYear = encryptedCard.encryptedExpiryYear, + threeDS2SdkVersion = runCompileOnly { ThreeDS2Service.INSTANCE.sdkVersion }, + brand = PaymentMethodTypes.BCMC + ).apply { + if (componentParams.isHolderNameRequired) { + holderName = outputData.cardHolderNameField.value + } + } + + val paymentComponentData = PaymentComponentData( + order = order, + paymentMethod = cardPaymentMethod, + storePaymentMethod = if (showStorePaymentField()) outputData.shouldStorePaymentMethod else null, + shopperReference = componentParams.shopperReference, + amount = componentParams.amount, + ) + + return BcmcComponentState(paymentComponentData, isInputValid = true, isReady = true) + } + + override fun onSubmit() { + val state = _componentStateFlow.value + submitHandler.onSubmit(state) + } + + private fun encryptCardData( + outputData: BcmcOutputData, + publicKey: String, + ): EncryptedCard? = try { + val unencryptedCardBuilder = UnencryptedCard.Builder() + .setNumber(outputData.cardNumberField.value) + + val expiryDateResult = outputData.expiryDateField.value + if (expiryDateResult != ExpiryDate.EMPTY_DATE) { + unencryptedCardBuilder.setExpiryDate( + expiryMonth = expiryDateResult.expiryMonth.toString(), + expiryYear = expiryDateResult.expiryYear.toString() + ) + } + + cardEncrypter.encryptFields(unencryptedCardBuilder.build(), publicKey) + } catch (e: EncryptionException) { + exceptionChannel.trySend(e) + null + } + + override fun getPaymentMethodType(): String { + return paymentMethod.type ?: PaymentMethodTypes.UNKNOWN + } + + override fun isCardNumberSupported(cardNumber: String?): Boolean { + if (cardNumber.isNullOrEmpty()) return false + return CardBrand.estimate(cardNumber).contains(BcmcComponent.SUPPORTED_CARD_TYPE) + } + + override fun isConfirmationRequired(): Boolean = _viewFlow.value is ButtonComponentViewType + + override fun shouldShowSubmitButton(): Boolean = isConfirmationRequired() && componentParams.isSubmitButtonVisible + + override fun setInteractionBlocked(isInteractionBlocked: Boolean) { + submitHandler.setInteractionBlocked(isInteractionBlocked) + } + + override fun onCleared() { + removeObserver() + } + + companion object { + private val TAG = LogUtil.getTag() + } +} diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParams.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParams.kt new file mode 100644 index 0000000000..a888d5a2bb --- /dev/null +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParams.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by josephj on 15/11/2022. + */ + +package com.adyen.checkout.bcmc.internal.ui.model + +import com.adyen.checkout.components.core.Amount +import com.adyen.checkout.components.core.internal.ui.model.AnalyticsParams +import com.adyen.checkout.components.core.internal.ui.model.ButtonParams +import com.adyen.checkout.components.core.internal.ui.model.ComponentParams +import com.adyen.checkout.core.Environment +import java.util.Locale + +internal data class BcmcComponentParams( + override val shopperLocale: Locale, + override val environment: Environment, + override val clientKey: String, + override val analyticsParams: AnalyticsParams, + override val isCreatedByDropIn: Boolean, + override val amount: Amount?, + override val isSubmitButtonVisible: Boolean, + val isHolderNameRequired: Boolean, + val shopperReference: String?, + val isStorePaymentFieldVisible: Boolean, +) : ComponentParams, ButtonParams diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapper.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapper.kt index 71b9c516a7..440c775bb6 100644 --- a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapper.kt +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapper.kt @@ -1,26 +1,17 @@ /* - * Copyright (c) 2023 Adyen N.V. + * Copyright (c) 2022 Adyen N.V. * * This file is open source and available under the MIT license. See the LICENSE file for more info. * - * Created by ozgur on 22/8/2023. + * Created by josephj on 17/11/2022. */ package com.adyen.checkout.bcmc.internal.ui.model import com.adyen.checkout.bcmc.BcmcConfiguration -import com.adyen.checkout.card.CardBrand -import com.adyen.checkout.card.CardType -import com.adyen.checkout.card.KCPAuthVisibility -import com.adyen.checkout.card.SocialSecurityNumberVisibility -import com.adyen.checkout.card.internal.ui.model.CVCVisibility -import com.adyen.checkout.card.internal.ui.model.CardComponentParams -import com.adyen.checkout.card.internal.ui.model.StoredCVCVisibility -import com.adyen.checkout.components.core.PaymentMethod import com.adyen.checkout.components.core.internal.ui.model.AnalyticsParams import com.adyen.checkout.components.core.internal.ui.model.ComponentParams import com.adyen.checkout.components.core.internal.ui.model.SessionParams -import com.adyen.checkout.ui.core.internal.ui.model.AddressParams internal class BcmcComponentParamsMapper( private val overrideComponentParams: ComponentParams?, @@ -30,18 +21,15 @@ internal class BcmcComponentParamsMapper( fun mapToParams( bcmcConfiguration: BcmcConfiguration, sessionParams: SessionParams?, - paymentMethod: PaymentMethod - ): CardComponentParams { + ): BcmcComponentParams { return bcmcConfiguration - .mapToParamsInternal( - supportedCardBrands = paymentMethod.brands?.map { CardBrand(it) } - ) + .mapToParamsInternal() .override(overrideComponentParams) .override(sessionParams ?: overrideSessionParams) } - private fun BcmcConfiguration.mapToParamsInternal(supportedCardBrands: List?): CardComponentParams { - return CardComponentParams( + private fun BcmcConfiguration.mapToParamsInternal(): BcmcComponentParams { + return BcmcComponentParams( shopperLocale = shopperLocale, environment = environment, clientKey = clientKey, @@ -52,19 +40,12 @@ internal class BcmcComponentParamsMapper( isHolderNameRequired = isHolderNameRequired ?: false, shopperReference = shopperReference, isStorePaymentFieldVisible = isStorePaymentFieldVisible ?: false, - addressParams = AddressParams.None, - installmentParams = null, - kcpAuthVisibility = KCPAuthVisibility.HIDE, - socialSecurityNumberVisibility = SocialSecurityNumberVisibility.HIDE, - cvcVisibility = CVCVisibility.HIDE_FIRST, - storedCVCVisibility = StoredCVCVisibility.HIDE, - supportedCardBrands = supportedCardBrands ?: DEFAULT_SUPPORTED_CARD_BRANDS ) } - private fun CardComponentParams.override( + private fun BcmcComponentParams.override( overrideComponentParams: ComponentParams? - ): CardComponentParams { + ): BcmcComponentParams { if (overrideComponentParams == null) return this return copy( shopperLocale = overrideComponentParams.shopperLocale, @@ -76,21 +57,13 @@ internal class BcmcComponentParamsMapper( ) } - private fun CardComponentParams.override( + private fun BcmcComponentParams.override( sessionParams: SessionParams? = null - ): CardComponentParams { + ): BcmcComponentParams { if (sessionParams == null) return this return copy( isStorePaymentFieldVisible = sessionParams.enableStoreDetails ?: isStorePaymentFieldVisible, amount = sessionParams.amount ?: amount, ) } - - companion object { - private val DEFAULT_SUPPORTED_CARD_BRANDS = listOf( - CardBrand(cardType = CardType.BCMC), - CardBrand(cardType = CardType.MAESTRO), - CardBrand(cardType = CardType.VISA) - ) - } } diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcInputData.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcInputData.kt new file mode 100644 index 0000000000..d8768ef55c --- /dev/null +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcInputData.kt @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2019 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by caiof on 27/8/2020. + */ +package com.adyen.checkout.bcmc.internal.ui.model + +import com.adyen.checkout.card.internal.ui.model.ExpiryDate +import com.adyen.checkout.components.core.internal.ui.model.InputData + +internal data class BcmcInputData( + var cardNumber: String = "", + var expiryDate: ExpiryDate = ExpiryDate.EMPTY_DATE, + var cardHolderName: String = "", + var isStorePaymentMethodSwitchChecked: Boolean = false, +) : InputData diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcOutputData.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcOutputData.kt new file mode 100644 index 0000000000..a8dcc44ba5 --- /dev/null +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcOutputData.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by oscars on 15/2/2023. + */ +package com.adyen.checkout.bcmc.internal.ui.model + +import com.adyen.checkout.card.internal.ui.model.ExpiryDate +import com.adyen.checkout.components.core.internal.ui.model.FieldState +import com.adyen.checkout.components.core.internal.ui.model.OutputData + +internal data class BcmcOutputData internal constructor( + val cardNumberField: FieldState, + val expiryDateField: FieldState, + val cardHolderNameField: FieldState, + val showStorePaymentField: Boolean, + val shouldStorePaymentMethod: Boolean, +) : OutputData { + override val isValid: Boolean + get() = ( + cardNumberField.validation.isValid() && + expiryDateField.validation.isValid() && + cardHolderNameField.validation.isValid() + ) +} diff --git a/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/view/BcmcView.kt b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/view/BcmcView.kt new file mode 100644 index 0000000000..160659964c --- /dev/null +++ b/bcmc/src/main/java/com/adyen/checkout/bcmc/internal/ui/view/BcmcView.kt @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2022 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by oscars on 29/9/2022. + */ + +package com.adyen.checkout.bcmc.internal.ui.view + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.view.View.OnFocusChangeListener +import android.widget.CompoundButton +import android.widget.LinearLayout +import androidx.annotation.StringRes +import androidx.core.view.isVisible +import com.adyen.checkout.bcmc.R +import com.adyen.checkout.bcmc.databinding.BcmcViewBinding +import com.adyen.checkout.bcmc.internal.ui.BcmcDelegate +import com.adyen.checkout.bcmc.internal.ui.model.BcmcOutputData +import com.adyen.checkout.components.core.internal.ui.ComponentDelegate +import com.adyen.checkout.components.core.internal.ui.model.Validation +import com.adyen.checkout.ui.core.internal.ui.ComponentView +import com.adyen.checkout.ui.core.internal.util.hideError +import com.adyen.checkout.ui.core.internal.util.isVisible +import com.adyen.checkout.ui.core.internal.util.setLocalizedHintFromStyle +import com.adyen.checkout.ui.core.internal.util.setLocalizedTextFromStyle +import com.adyen.checkout.ui.core.internal.util.showError +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +@Suppress("TooManyFunctions") +internal class BcmcView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : LinearLayout(context, attrs, defStyleAttr), + ComponentView { + + private val binding = BcmcViewBinding.inflate(LayoutInflater.from(context), this) + + private lateinit var localizedContext: Context + + private lateinit var delegate: BcmcDelegate + + init { + orientation = VERTICAL + val padding = resources.getDimension(R.dimen.standard_margin).toInt() + setPadding(padding, padding, padding, 0) + } + + override fun initView(delegate: ComponentDelegate, coroutineScope: CoroutineScope, localizedContext: Context) { + require(delegate is BcmcDelegate) { "Unsupported delegate type" } + this.delegate = delegate + + this.localizedContext = localizedContext + initLocalizedStrings(localizedContext) + + observeDelegate(delegate, coroutineScope) + + initCardNumberInput() + initExpiryDateInput() + initCardHolderInput() + initStorePaymentMethodSwitch() + } + + private fun initLocalizedStrings(localizedContext: Context) { + with(binding) { + textInputLayoutCardNumber.setLocalizedHintFromStyle( + R.style.AdyenCheckout_Card_CardNumberInput, + localizedContext + ) + textInputLayoutExpiryDate.setLocalizedHintFromStyle( + R.style.AdyenCheckout_Card_ExpiryDateInput, + localizedContext + ) + binding.textInputLayoutCardHolder.setLocalizedHintFromStyle( + R.style.AdyenCheckout_Card_HolderNameInput, + localizedContext + ) + switchStorePaymentMethod.setLocalizedTextFromStyle( + R.style.AdyenCheckout_Card_StorePaymentSwitch, + localizedContext + ) + } + } + + private fun observeDelegate(delegate: BcmcDelegate, coroutineScope: CoroutineScope) { + delegate.outputDataFlow + .onEach { outputDataChanged(it) } + .launchIn(coroutineScope) + } + + private fun outputDataChanged(bcmcOutputData: BcmcOutputData) { + setStorePaymentSwitchVisibility(bcmcOutputData.showStorePaymentField) + } + + private fun setStorePaymentSwitchVisibility(showStorePaymentField: Boolean) { + binding.switchStorePaymentMethod.isVisible = showStorePaymentField + } + + private fun initExpiryDateInput() { + binding.editTextExpiryDate.setOnChangeListener { + delegate.updateInputData { expiryDate = binding.editTextExpiryDate.date } + binding.textInputLayoutExpiryDate.hideError() + } + + binding.editTextExpiryDate.onFocusChangeListener = OnFocusChangeListener { _: View?, hasFocus: Boolean -> + val expiryDateValidation = delegate.outputData.expiryDateField.validation + if (hasFocus) { + binding.textInputLayoutExpiryDate.hideError() + } else if (expiryDateValidation is Validation.Invalid) { + val errorReasonResId = expiryDateValidation.reason + binding.textInputLayoutExpiryDate.showError(localizedContext.getString(errorReasonResId)) + } + } + } + + private fun initCardNumberInput() { + binding.editTextCardNumber.setOnChangeListener { + delegate.updateInputData { cardNumber = binding.editTextCardNumber.rawValue } + setCardNumberError(null) + } + + binding.editTextCardNumber.onFocusChangeListener = OnFocusChangeListener { _: View?, hasFocus: Boolean -> + val cardNumberValidation = delegate.outputData.cardNumberField.validation + if (hasFocus) { + setCardNumberError(null) + } else if (cardNumberValidation is Validation.Invalid) { + val errorReasonResId = cardNumberValidation.reason + setCardNumberError(errorReasonResId) + } + } + } + + private fun initCardHolderInput() { + binding.textInputLayoutCardHolder.isVisible = delegate.componentParams.isHolderNameRequired + binding.editTextCardHolder.setOnChangeListener { + delegate.updateInputData { cardHolderName = binding.editTextCardHolder.rawValue } + binding.textInputLayoutCardHolder.hideError() + } + + binding.editTextCardHolder.onFocusChangeListener = OnFocusChangeListener { _, hasFocus -> + val cardHolderValidation = delegate.outputData.cardHolderNameField.validation + if (hasFocus) { + binding.textInputLayoutCardHolder.hideError() + } else if (cardHolderValidation is Validation.Invalid) { + val errorReasonResId = cardHolderValidation.reason + binding.textInputLayoutCardHolder.showError(localizedContext.getString(errorReasonResId)) + } + } + } + + private fun initStorePaymentMethodSwitch() { + binding.switchStorePaymentMethod.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + delegate.updateInputData { isStorePaymentMethodSwitchChecked = isChecked } + } + } + + override fun highlightValidationErrors() { + val outputData = delegate.outputData + + var isErrorFocused = false + val cardNumberValidation = outputData.cardNumberField.validation + if (cardNumberValidation is Validation.Invalid) { + isErrorFocused = true + binding.editTextCardNumber.requestFocus() + val errorReasonResId = cardNumberValidation.reason + setCardNumberError(errorReasonResId) + } + + val expiryFieldValidation = outputData.expiryDateField.validation + if (expiryFieldValidation is Validation.Invalid) { + if (!isErrorFocused) { + binding.textInputLayoutExpiryDate.requestFocus() + } + val errorReasonResId = expiryFieldValidation.reason + binding.textInputLayoutExpiryDate.showError(localizedContext.getString(errorReasonResId)) + } + + val cardHolderNameValidation = outputData.cardHolderNameField.validation + if (cardHolderNameValidation is Validation.Invalid) { + if (!isErrorFocused) { + binding.textInputLayoutCardHolder.requestFocus() + } + val errorReasonResId = cardHolderNameValidation.reason + binding.textInputLayoutCardHolder.showError(localizedContext.getString(errorReasonResId)) + } + } + + private fun setCardNumberError(@StringRes stringResId: Int?) { + if (stringResId == null) { + binding.textInputLayoutCardNumber.hideError() + binding.cardBrandLogoImageView.isVisible = true + } else { + binding.textInputLayoutCardNumber.showError(localizedContext.getString(stringResId)) + binding.cardBrandLogoImageView.isVisible = false + } + } + + override fun getView(): View = this +} diff --git a/bcmc/src/test/java/com/adyen/checkout/bcmc/BcmcComponentTest.kt b/bcmc/src/test/java/com/adyen/checkout/bcmc/BcmcComponentTest.kt index e28059bd49..92cacce9c3 100644 --- a/bcmc/src/test/java/com/adyen/checkout/bcmc/BcmcComponentTest.kt +++ b/bcmc/src/test/java/com/adyen/checkout/bcmc/BcmcComponentTest.kt @@ -14,8 +14,7 @@ import app.cash.turbine.test import com.adyen.checkout.action.core.internal.DefaultActionHandlingComponent import com.adyen.checkout.action.core.internal.ui.GenericActionDelegate import com.adyen.checkout.bcmc.internal.ui.BcmcComponentViewType -import com.adyen.checkout.card.CardComponentState -import com.adyen.checkout.card.internal.ui.CardDelegate +import com.adyen.checkout.bcmc.internal.ui.BcmcDelegate import com.adyen.checkout.components.core.internal.ComponentEventHandler import com.adyen.checkout.components.core.internal.PaymentComponentEvent import com.adyen.checkout.core.AdyenLogger @@ -43,7 +42,7 @@ import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) @ExtendWith(MockitoExtension::class, TestDispatcherExtension::class) internal class BcmcComponentTest( - @Mock private val cardDelegate: CardDelegate, + @Mock private val bcmcDelegate: BcmcDelegate, @Mock private val genericActionDelegate: GenericActionDelegate, @Mock private val actionHandlingComponent: DefaultActionHandlingComponent, @Mock private val componentEventHandler: ComponentEventHandler, @@ -53,11 +52,11 @@ internal class BcmcComponentTest( @BeforeEach fun before() { - whenever(cardDelegate.viewFlow) doReturn MutableStateFlow(BcmcComponentViewType) + whenever(bcmcDelegate.viewFlow) doReturn MutableStateFlow(BcmcComponentViewType) whenever(genericActionDelegate.viewFlow) doReturn MutableStateFlow(null) component = BcmcComponent( - cardDelegate, + bcmcDelegate, genericActionDelegate, actionHandlingComponent, componentEventHandler, @@ -67,7 +66,7 @@ internal class BcmcComponentTest( @Test fun `when component is created then delegates are initialized`() { - verify(cardDelegate).initialize(component.viewModelScope) + verify(bcmcDelegate).initialize(component.viewModelScope) verify(genericActionDelegate).initialize(component.viewModelScope) verify(componentEventHandler).initialize(component.viewModelScope) } @@ -76,7 +75,7 @@ internal class BcmcComponentTest( fun `when component is cleared then delegates are cleared`() { component.invokeOnCleared() - verify(cardDelegate).onCleared() + verify(bcmcDelegate).onCleared() verify(genericActionDelegate).onCleared() verify(componentEventHandler).onCleared() } @@ -84,11 +83,11 @@ internal class BcmcComponentTest( @Test fun `when observe is called then observe in delegates is called`() { val lifecycleOwner = mock() - val callback: (PaymentComponentEvent) -> Unit = {} + val callback: (PaymentComponentEvent) -> Unit = {} component.observe(lifecycleOwner, callback) - verify(cardDelegate).observe(lifecycleOwner, component.viewModelScope, callback) + verify(bcmcDelegate).observe(lifecycleOwner, component.viewModelScope, callback) verify(genericActionDelegate).observe(eq(lifecycleOwner), eq(component.viewModelScope), any()) } @@ -96,7 +95,7 @@ internal class BcmcComponentTest( fun `when removeObserver is called then removeObserver in delegates is called`() { component.removeObserver() - verify(cardDelegate).removeObserver() + verify(bcmcDelegate).removeObserver() verify(genericActionDelegate).removeObserver() } @@ -111,13 +110,8 @@ internal class BcmcComponentTest( @Test fun `when bcmc delegate view flow emits a value then component view flow should match that value`() = runTest { val bcmcDelegateViewFlow = MutableStateFlow(TestComponentViewType.VIEW_TYPE_1) - whenever(cardDelegate.viewFlow) doReturn bcmcDelegateViewFlow - component = BcmcComponent( - cardDelegate = cardDelegate, - genericActionDelegate = genericActionDelegate, - actionHandlingComponent = actionHandlingComponent, - componentEventHandler = componentEventHandler - ) + whenever(bcmcDelegate.viewFlow) doReturn bcmcDelegateViewFlow + component = BcmcComponent(bcmcDelegate, genericActionDelegate, actionHandlingComponent, componentEventHandler) component.viewFlow.test { assertEquals(TestComponentViewType.VIEW_TYPE_1, awaitItem()) @@ -133,12 +127,7 @@ internal class BcmcComponentTest( fun `when action delegate view flow emits a value then component view flow should match that value`() = runTest { val actionDelegateViewFlow = MutableStateFlow(TestComponentViewType.VIEW_TYPE_1) whenever(genericActionDelegate.viewFlow) doReturn actionDelegateViewFlow - component = BcmcComponent( - cardDelegate = cardDelegate, - genericActionDelegate = genericActionDelegate, - actionHandlingComponent = actionHandlingComponent, - componentEventHandler = componentEventHandler - ) + component = BcmcComponent(bcmcDelegate, genericActionDelegate, actionHandlingComponent, componentEventHandler) component.viewFlow.test { // this value should match the value of the main delegate and not the action delegate @@ -155,20 +144,20 @@ internal class BcmcComponentTest( @Test fun `when isConfirmationRequired, then delegate is called`() { component.isConfirmationRequired() - verify(cardDelegate).isConfirmationRequired() + verify(bcmcDelegate).isConfirmationRequired() } @Test fun `when submit is called and active delegate is the payment delegate, then delegate onSubmit is called`() { - whenever(component.delegate).thenReturn(cardDelegate) + whenever(component.delegate).thenReturn(bcmcDelegate) component.submit() - verify(cardDelegate).onSubmit() + verify(bcmcDelegate).onSubmit() } @Test fun `when submit is called and active delegate is the action delegate, then delegate onSubmit is not called`() { whenever(component.delegate).thenReturn(genericActionDelegate) component.submit() - verify(cardDelegate, never()).onSubmit() + verify(bcmcDelegate, never()).onSubmit() } } diff --git a/bcmc/src/test/java/com/adyen/checkout/bcmc/internal/ui/DefaultBcmcDelegateTest.kt b/bcmc/src/test/java/com/adyen/checkout/bcmc/internal/ui/DefaultBcmcDelegateTest.kt new file mode 100644 index 0000000000..5c40543c2b --- /dev/null +++ b/bcmc/src/test/java/com/adyen/checkout/bcmc/internal/ui/DefaultBcmcDelegateTest.kt @@ -0,0 +1,487 @@ +/* + * Copyright (c) 2022 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by atef on 22/8/2022. + */ + +package com.adyen.checkout.bcmc.internal.ui + +import app.cash.turbine.test +import com.adyen.checkout.bcmc.BcmcComponentState +import com.adyen.checkout.bcmc.BcmcConfiguration +import com.adyen.checkout.bcmc.internal.ui.model.BcmcComponentParamsMapper +import com.adyen.checkout.bcmc.internal.ui.model.BcmcOutputData +import com.adyen.checkout.card.R +import com.adyen.checkout.card.internal.ui.CardValidationMapper +import com.adyen.checkout.card.internal.ui.model.ExpiryDate +import com.adyen.checkout.components.core.Amount +import com.adyen.checkout.components.core.OrderRequest +import com.adyen.checkout.components.core.PaymentMethod +import com.adyen.checkout.components.core.PaymentMethodTypes +import com.adyen.checkout.components.core.internal.PaymentObserverRepository +import com.adyen.checkout.components.core.internal.data.api.AnalyticsRepository +import com.adyen.checkout.components.core.internal.test.TestPublicKeyRepository +import com.adyen.checkout.components.core.internal.ui.model.FieldState +import com.adyen.checkout.components.core.internal.ui.model.Validation +import com.adyen.checkout.core.Environment +import com.adyen.checkout.cse.internal.test.TestCardEncrypter +import com.adyen.checkout.test.TestDispatcherExtension +import com.adyen.checkout.ui.core.internal.ui.SubmitHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments.arguments +import org.junit.jupiter.params.provider.MethodSource +import org.mockito.Mock +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import java.util.Locale + +@OptIn(ExperimentalCoroutinesApi::class) +@ExtendWith(MockitoExtension::class, TestDispatcherExtension::class) +internal class DefaultBcmcDelegateTest( + @Mock private val analyticsRepository: AnalyticsRepository, + @Mock private val submitHandler: SubmitHandler, +) { + + private lateinit var testPublicKeyRepository: TestPublicKeyRepository + private lateinit var cardEncrypter: TestCardEncrypter + private lateinit var cardValidationMapper: CardValidationMapper + private lateinit var delegate: DefaultBcmcDelegate + + @BeforeEach + fun setup() { + testPublicKeyRepository = TestPublicKeyRepository() + cardEncrypter = TestCardEncrypter() + cardValidationMapper = CardValidationMapper() + delegate = createBcmcDelegate() + } + + @Test + fun `when fetching the public key fails, then an error is propagated`() = runTest { + testPublicKeyRepository.shouldReturnError = true + + delegate.exceptionFlow.test { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + val exception = expectMostRecentItem() + assertEquals(testPublicKeyRepository.errorResult.exceptionOrNull(), exception.cause) + + cancelAndIgnoreRemainingEvents() + } + } + + @Nested + @DisplayName("when input data changes and") + inner class InputDataChangedTest { + @Test + fun `card number is empty, then output should be invalid`() = runTest { + delegate.outputDataFlow.test { + delegate.updateInputData { + cardNumber = "" + expiryDate = TEST_EXPIRY_DATE + } + + with(expectMostRecentItem()) { + assertTrue(cardNumberField.validation is Validation.Invalid) + assertTrue(expiryDateField.validation is Validation.Valid) + assertFalse(isValid) + } + } + } + + @Test + fun `card number is invalid, then output should be invalid`() = runTest { + delegate.outputDataFlow.test { + delegate.updateInputData { + cardNumber = "12345678" + expiryDate = TEST_EXPIRY_DATE + } + + with(expectMostRecentItem()) { + assertTrue(cardNumberField.validation is Validation.Invalid) + assertTrue(expiryDateField.validation is Validation.Valid) + assertFalse(isValid) + } + } + } + + @Test + fun `expiry date is invalid, then output should be invalid`() = + runTest { + delegate.outputDataFlow.test { + delegate.updateInputData { + cardNumber = TEST_CARD_NUMBER + expiryDate = ExpiryDate.INVALID_DATE + } + + with(expectMostRecentItem()) { + assertTrue(cardNumberField.validation is Validation.Valid) + assertTrue(expiryDateField.validation is Validation.Invalid) + assertFalse(isValid) + } + } + } + + @Test + fun `expiry date is empty, then output should be invalid`() = runTest { + delegate.outputDataFlow.test { + delegate.updateInputData { + cardNumber = TEST_CARD_NUMBER + expiryDate = ExpiryDate.EMPTY_DATE + } + + with(expectMostRecentItem()) { + assertTrue(cardNumberField.validation is Validation.Valid) + assertTrue(expiryDateField.validation is Validation.Invalid) + assertFalse(isValid) + } + } + } + + @Test + fun `input is valid, then output should be valid`() = runTest { + delegate.outputDataFlow.test { + delegate.updateInputData { + cardNumber = TEST_CARD_NUMBER + expiryDate = TEST_EXPIRY_DATE + } + + with(expectMostRecentItem()) { + assertTrue(cardNumberField.validation is Validation.Valid) + assertTrue(expiryDateField.validation is Validation.Valid) + assertTrue(isValid) + } + } + } + } + + @Nested + @DisplayName("when creating component state and") + inner class CreateComponentStateTest { + + @Test + fun `component is not initialized, then component state should not be ready`() = runTest { + delegate.componentStateFlow.test { + delegate.updateComponentState( + createOutputData( + cardNumber = FieldState(TEST_CARD_NUMBER, Validation.Valid), + expiryDate = FieldState(TEST_EXPIRY_DATE, Validation.Valid), + cardHolder = FieldState("Name", Validation.Valid) + ) + ) + + with(expectMostRecentItem()) { + assertFalse(isReady) + } + } + } + + @Test + fun `encryption fails, then component state should be invalid`() = runTest { + cardEncrypter.shouldThrowException = true + + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + delegate.componentStateFlow.test { + delegate.updateComponentState( + createOutputData( + cardNumber = FieldState(TEST_CARD_NUMBER, Validation.Valid), + expiryDate = FieldState(TEST_EXPIRY_DATE, Validation.Valid), + cardHolder = FieldState("Name", Validation.Valid) + ) + ) + + with(expectMostRecentItem()) { + assertTrue(isReady) + assertFalse(isInputValid) + } + } + } + + @Test + fun `card number in output data is invalid, then component state should be invalid`() = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + delegate.componentStateFlow.test { + delegate.updateComponentState( + createOutputData( + cardNumber = FieldState( + "12345678", + Validation.Invalid(R.string.checkout_card_number_not_valid) + ), + expiryDate = FieldState(TEST_EXPIRY_DATE, Validation.Valid), + cardHolder = FieldState("Name", Validation.Valid) + ) + ) + + with(expectMostRecentItem()) { + assertFalse(isValid) + assertFalse(isInputValid) + } + } + } + + @Test + fun `expiry date in output is invalid, then component state should be invalid`() = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + delegate.componentStateFlow.test { + delegate.updateComponentState( + createOutputData( + cardNumber = FieldState(TEST_CARD_NUMBER, Validation.Valid), + expiryDate = FieldState( + ExpiryDate.INVALID_DATE, + Validation.Invalid(R.string.checkout_expiry_date_not_valid) + ), + cardHolder = FieldState("Name", Validation.Valid), + ) + ) + + with(expectMostRecentItem()) { + assertFalse(isValid) + assertFalse(isInputValid) + } + } + } + + @Test + fun `holder name in output is invalid, then component state should be invalid`() = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + delegate.componentStateFlow.test { + delegate.updateComponentState( + createOutputData( + cardNumber = FieldState(TEST_CARD_NUMBER, Validation.Valid), + expiryDate = FieldState(TEST_EXPIRY_DATE, Validation.Valid), + cardHolder = FieldState("", Validation.Invalid(R.string.checkout_holder_name_not_valid)), + ) + ) + + with(expectMostRecentItem()) { + assertFalse(isValid) + assertFalse(isInputValid) + } + } + } + + @Test + fun `output data is valid, then component state should be valid`() = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + delegate.componentStateFlow.test { + delegate.updateComponentState( + createOutputData( + cardNumber = FieldState(TEST_CARD_NUMBER, Validation.Valid), + expiryDate = FieldState(TEST_EXPIRY_DATE, Validation.Valid), + cardHolder = FieldState("Name", Validation.Valid) + ) + ) + with(expectMostRecentItem()) { + assertTrue(isValid) + assertTrue(isInputValid) + assertEquals(TEST_ORDER, data.order) + assertEquals(PaymentMethodTypes.BCMC, data.paymentMethod?.brand) + } + } + } + + @ParameterizedTest + @MethodSource("com.adyen.checkout.bcmc.internal.ui.DefaultBcmcDelegateTest#shouldStorePaymentMethodSource") + fun `storePaymentMethod in component state should match store switch visibility and state`( + isStorePaymentMethodSwitchVisible: Boolean, + isStorePaymentMethodSwitchChecked: Boolean, + expectedStorePaymentMethod: Boolean?, + ) = runTest { + val configuration = getDefaultBcmcConfigurationBuilder() + .setShowStorePaymentField(isStorePaymentMethodSwitchVisible) + .build() + delegate = createBcmcDelegate(configuration = configuration) + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + delegate.componentStateFlow.test { + delegate.updateInputData { + cardNumber = TEST_CARD_NUMBER + expiryDate = TEST_EXPIRY_DATE + this.isStorePaymentMethodSwitchChecked = isStorePaymentMethodSwitchChecked + } + + val componentState = expectMostRecentItem() + assertEquals(expectedStorePaymentMethod, componentState.data.storePaymentMethod) + } + } + + @ParameterizedTest + @MethodSource("com.adyen.checkout.bcmc.internal.ui.DefaultBcmcDelegateTest#amountSource") + fun `when input data is valid then amount is propagated in component state if set`( + configurationValue: Amount?, + expectedComponentStateValue: Amount?, + ) = runTest { + if (configurationValue != null) { + val configuration = getDefaultBcmcConfigurationBuilder() + .setAmount(configurationValue) + .build() + delegate = createBcmcDelegate(configuration = configuration) + } + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + delegate.componentStateFlow.test { + delegate.updateInputData { + cardNumber = TEST_CARD_NUMBER + expiryDate = TEST_EXPIRY_DATE + } + assertEquals(expectedComponentStateValue, expectMostRecentItem().data.amount) + } + } + } + + @Test + fun `when delegate is initialized then analytics event is sent`() = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + verify(analyticsRepository).setupAnalytics() + } + + @Nested + inner class SubmitButtonVisibilityTest { + + @Test + fun `when submit button is configured to be hidden, then it should not show`() { + delegate = createBcmcDelegate( + configuration = getDefaultBcmcConfigurationBuilder() + .setSubmitButtonVisible(false) + .build() + ) + + assertFalse(delegate.shouldShowSubmitButton()) + } + + @Test + fun `when submit button is configured to be visible, then it should show`() { + delegate = createBcmcDelegate( + configuration = getDefaultBcmcConfigurationBuilder() + .setSubmitButtonVisible(true) + .build() + ) + + assertTrue(delegate.shouldShowSubmitButton()) + } + } + + @Nested + inner class SubmitHandlerTest { + + @Test + fun `when delegate is initialized then submit handler event is initialized`() = runTest { + val coroutineScope = CoroutineScope(UnconfinedTestDispatcher()) + delegate.initialize(coroutineScope) + verify(submitHandler).initialize(coroutineScope, delegate.componentStateFlow) + } + + @Test + fun `when delegate setInteractionBlocked is called then submit handler setInteractionBlocked is called`() = + runTest { + delegate.setInteractionBlocked(true) + verify(submitHandler).setInteractionBlocked(true) + } + + @Test + fun `when delegate onSubmit is called then submit handler onSubmit is called`() = runTest { + delegate.componentStateFlow.test { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + delegate.onSubmit() + verify(submitHandler).onSubmit(expectMostRecentItem()) + } + } + } + + @Nested + inner class AnalyticsTest { + + @Test + fun `when component state is valid then PaymentMethodDetails should contain checkoutAttemptId`() = runTest { + whenever(analyticsRepository.getCheckoutAttemptId()) doReturn TEST_CHECKOUT_ATTEMPT_ID + + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + delegate.componentStateFlow.test { + delegate.updateInputData { + cardNumber = TEST_CARD_NUMBER + expiryDate = TEST_EXPIRY_DATE + } + + assertEquals(TEST_CHECKOUT_ATTEMPT_ID, expectMostRecentItem().data.paymentMethod?.checkoutAttemptId) + } + } + } + + private fun createOutputData( + cardNumber: FieldState, + expiryDate: FieldState, + cardHolder: FieldState, + showStorePaymentField: Boolean = false, + shouldStorePaymentMethod: Boolean = false + ): BcmcOutputData { + return BcmcOutputData( + cardNumberField = cardNumber, + expiryDateField = expiryDate, + cardHolderNameField = cardHolder, + showStorePaymentField = showStorePaymentField, + shouldStorePaymentMethod = shouldStorePaymentMethod, + ) + } + + private fun createBcmcDelegate( + configuration: BcmcConfiguration = getDefaultBcmcConfigurationBuilder().build() + ) = DefaultBcmcDelegate( + observerRepository = PaymentObserverRepository(), + paymentMethod = PaymentMethod(), + order = TEST_ORDER, + publicKeyRepository = testPublicKeyRepository, + componentParams = BcmcComponentParamsMapper(null, null).mapToParams(configuration, null), + cardValidationMapper = cardValidationMapper, + cardEncrypter = cardEncrypter, + analyticsRepository = analyticsRepository, + submitHandler = submitHandler, + ) + + private fun getDefaultBcmcConfigurationBuilder() = BcmcConfiguration.Builder( + Locale.US, + Environment.TEST, + TEST_CLIENT_KEY + ) + + companion object { + private const val TEST_CLIENT_KEY = "test_qwertyuiopasdfghjklzxcvbnmqwerty" + private const val TEST_CARD_NUMBER = "5555444433331111" + private val TEST_EXPIRY_DATE = ExpiryDate(3, 2030) + private val TEST_ORDER = OrderRequest("PSP", "ORDER_DATA") + private const val TEST_CHECKOUT_ATTEMPT_ID = "TEST_CHECKOUT_ATTEMPT_ID" + + @JvmStatic + fun shouldStorePaymentMethodSource() = listOf( + // isStorePaymentMethodSwitchVisible, isStorePaymentMethodSwitchChecked, expectedStorePaymentMethod + arguments(false, false, null), + arguments(false, true, null), + arguments(true, false, false), + arguments(true, true, true), + ) + + @JvmStatic + fun amountSource() = listOf( + // configurationValue, expectedComponentStateValue + arguments(Amount("EUR", 100), Amount("EUR", 100)), + arguments(Amount("USD", 0), Amount("USD", 0)), + arguments(null, null), + arguments(null, null), + ) + } +} diff --git a/bcmc/src/test/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperTest.kt b/bcmc/src/test/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperTest.kt index b2f4ca0abe..88784bed80 100644 --- a/bcmc/src/test/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperTest.kt +++ b/bcmc/src/test/java/com/adyen/checkout/bcmc/internal/ui/model/BcmcComponentParamsMapperTest.kt @@ -9,21 +9,12 @@ package com.adyen.checkout.bcmc.internal.ui.model import com.adyen.checkout.bcmc.BcmcConfiguration -import com.adyen.checkout.card.CardBrand -import com.adyen.checkout.card.CardType -import com.adyen.checkout.card.KCPAuthVisibility -import com.adyen.checkout.card.SocialSecurityNumberVisibility -import com.adyen.checkout.card.internal.ui.model.CVCVisibility -import com.adyen.checkout.card.internal.ui.model.CardComponentParams -import com.adyen.checkout.card.internal.ui.model.StoredCVCVisibility import com.adyen.checkout.components.core.Amount -import com.adyen.checkout.components.core.PaymentMethod import com.adyen.checkout.components.core.internal.ui.model.AnalyticsParams import com.adyen.checkout.components.core.internal.ui.model.AnalyticsParamsLevel import com.adyen.checkout.components.core.internal.ui.model.GenericComponentParams import com.adyen.checkout.components.core.internal.ui.model.SessionParams import com.adyen.checkout.core.Environment -import com.adyen.checkout.ui.core.internal.ui.model.AddressParams import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest @@ -38,10 +29,9 @@ internal class BcmcComponentParamsMapperTest { val bcmcConfiguration = getBcmcConfigurationBuilder() .build() - val params = BcmcComponentParamsMapper(null, null) - .mapToParams(bcmcConfiguration, null, PaymentMethod()) + val params = BcmcComponentParamsMapper(null, null).mapToParams(bcmcConfiguration, null) - val expected = getCardComponentParams() + val expected = getBcmcComponentParams() assertEquals(expected, params) } @@ -57,15 +47,13 @@ internal class BcmcComponentParamsMapperTest { .setSubmitButtonVisible(false) .build() - val params = BcmcComponentParamsMapper(null, null) - .mapToParams(bcmcConfiguration, null, PaymentMethod()) + val params = BcmcComponentParamsMapper(null, null).mapToParams(bcmcConfiguration, null) - val expected = getCardComponentParams( + val expected = getBcmcComponentParams( isHolderNameRequired = true, shopperReference = shopperReference, isStorePaymentFieldVisible = true, - isSubmitButtonVisible = false, - cvcVisibility = CVCVisibility.HIDE_FIRST + isSubmitButtonVisible = false ) assertEquals(expected, params) @@ -90,10 +78,9 @@ internal class BcmcComponentParamsMapperTest { ) ) - val params = BcmcComponentParamsMapper(overrideParams, null) - .mapToParams(bcmcConfiguration, null, PaymentMethod()) + val params = BcmcComponentParamsMapper(overrideParams, null).mapToParams(bcmcConfiguration, null) - val expected = getCardComponentParams( + val expected = getBcmcComponentParams( shopperLocale = Locale.GERMAN, environment = Environment.EUROPE, clientKey = TEST_CLIENT_KEY_2, @@ -127,11 +114,10 @@ internal class BcmcComponentParamsMapperTest { installmentOptions = null, amount = null, returnUrl = "", - ), - PaymentMethod() + ) ) - val expected = getCardComponentParams(isStorePaymentFieldVisible = expectedValue) + val expected = getBcmcComponentParams(isStorePaymentFieldVisible = expectedValue) assertEquals(expected, params) } @@ -150,7 +136,7 @@ internal class BcmcComponentParamsMapperTest { // this is in practice DropInComponentParams, but we don't have access to it in this module and any // ComponentParams class can work - val overrideParams = dropInValue?.let { getCardComponentParams(amount = it) } + val overrideParams = dropInValue?.let { getBcmcComponentParams(amount = it) } val params = BcmcComponentParamsMapper(overrideParams, null).mapToParams( bcmcConfiguration, @@ -159,11 +145,10 @@ internal class BcmcComponentParamsMapperTest { installmentOptions = null, amount = sessionsValue, returnUrl = "", - ), - PaymentMethod() + ) ) - val expected = getCardComponentParams( + val expected = getBcmcComponentParams( amount = expectedValue ) @@ -177,7 +162,7 @@ internal class BcmcComponentParamsMapperTest { ) @Suppress("LongParameterList") - private fun getCardComponentParams( + private fun getBcmcComponentParams( shopperLocale: Locale = Locale.US, environment: Environment = Environment.TEST, clientKey: String = TEST_CLIENT_KEY_1, @@ -188,8 +173,7 @@ internal class BcmcComponentParamsMapperTest { isHolderNameRequired: Boolean = false, shopperReference: String? = null, isStorePaymentFieldVisible: Boolean = false, - cvcVisibility: CVCVisibility = CVCVisibility.HIDE_FIRST, - ) = CardComponentParams( + ) = BcmcComponentParams( shopperLocale = shopperLocale, environment = environment, clientKey = clientKey, @@ -199,18 +183,7 @@ internal class BcmcComponentParamsMapperTest { isSubmitButtonVisible = isSubmitButtonVisible, isHolderNameRequired = isHolderNameRequired, shopperReference = shopperReference, - isStorePaymentFieldVisible = isStorePaymentFieldVisible, - cvcVisibility = cvcVisibility, - addressParams = AddressParams.None, - installmentParams = null, - socialSecurityNumberVisibility = SocialSecurityNumberVisibility.HIDE, - kcpAuthVisibility = KCPAuthVisibility.HIDE, - storedCVCVisibility = StoredCVCVisibility.HIDE, - supportedCardBrands = listOf( - CardBrand(cardType = CardType.BCMC), - CardBrand(cardType = CardType.MAESTRO), - CardBrand(cardType = CardType.VISA) - ) + isStorePaymentFieldVisible = isStorePaymentFieldVisible ) companion object { diff --git a/card/src/main/java/com/adyen/checkout/card/CardComponent.kt b/card/src/main/java/com/adyen/checkout/card/CardComponent.kt index 108bc5fa14..699046e406 100644 --- a/card/src/main/java/com/adyen/checkout/card/CardComponent.kt +++ b/card/src/main/java/com/adyen/checkout/card/CardComponent.kt @@ -8,7 +8,6 @@ package com.adyen.checkout.card -import androidx.annotation.RestrictTo import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -35,7 +34,7 @@ import kotlinx.coroutines.flow.Flow /** * A [PaymentComponent] that supports the [PaymentMethodTypes.SCHEME] payment method. */ -open class CardComponent constructor( +class CardComponent internal constructor( private val cardDelegate: CardDelegate, private val genericActionDelegate: GenericActionDelegate, private val actionHandlingComponent: DefaultActionHandlingComponent, @@ -61,8 +60,7 @@ open class CardComponent constructor( componentEventHandler.initialize(viewModelScope) } - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - fun observe( + internal fun observe( lifecycleOwner: LifecycleOwner, callback: (PaymentComponentEvent) -> Unit ) { @@ -75,8 +73,7 @@ open class CardComponent constructor( ) } - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - fun removeObserver() { + internal fun removeObserver() { cardDelegate.removeObserver() genericActionDelegate.removeObserver() } diff --git a/card/src/main/java/com/adyen/checkout/card/internal/data/api/BinLookupService.kt b/card/src/main/java/com/adyen/checkout/card/internal/data/api/BinLookupService.kt index 47affe25db..e479d517fa 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/data/api/BinLookupService.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/data/api/BinLookupService.kt @@ -8,7 +8,6 @@ package com.adyen.checkout.card.internal.data.api -import androidx.annotation.RestrictTo import com.adyen.checkout.card.internal.data.model.BinLookupRequest import com.adyen.checkout.card.internal.data.model.BinLookupResponse import com.adyen.checkout.core.internal.data.api.HttpClient @@ -16,8 +15,7 @@ import com.adyen.checkout.core.internal.data.api.post import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -class BinLookupService( +internal class BinLookupService( private val httpClient: HttpClient, ) { diff --git a/card/src/main/java/com/adyen/checkout/card/internal/data/api/DefaultDetectCardTypeRepository.kt b/card/src/main/java/com/adyen/checkout/card/internal/data/api/DefaultDetectCardTypeRepository.kt index a0ba09c501..25f7e55c92 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/data/api/DefaultDetectCardTypeRepository.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/data/api/DefaultDetectCardTypeRepository.kt @@ -8,7 +8,6 @@ package com.adyen.checkout.card.internal.data.api -import androidx.annotation.RestrictTo import com.adyen.checkout.card.CardBrand import com.adyen.checkout.card.CardType import com.adyen.checkout.card.internal.data.model.BinLookupRequest @@ -29,8 +28,7 @@ import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import java.util.UUID -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -class DefaultDetectCardTypeRepository( +internal class DefaultDetectCardTypeRepository( private val cardEncrypter: BaseCardEncrypter, private val binLookupService: BinLookupService, ) : DetectCardTypeRepository { @@ -47,7 +45,6 @@ class DefaultDetectCardTypeRepository( supportedCardBrands: List, clientKey: String, coroutineScope: CoroutineScope, - type: String? ) { Logger.d(TAG, "detectCardType") if (shouldFetchReliableTypes(cardNumber)) { @@ -67,8 +64,7 @@ class DefaultDetectCardTypeRepository( publicKey, supportedCardBrands, clientKey, - coroutineScope, - type + coroutineScope ) } } @@ -83,7 +79,6 @@ class DefaultDetectCardTypeRepository( supportedCardBrands: List, clientKey: String, coroutineScope: CoroutineScope, - type: String? ) { if (publicKey != null) { Logger.d(TAG, "Launching Bin Lookup") @@ -94,8 +89,7 @@ class DefaultDetectCardTypeRepository( cardNumber, publicKey, supportedCardBrands, - clientKey, - type + clientKey )?.let { _detectedCardTypesFlow.send(it) } @@ -145,11 +139,10 @@ class DefaultDetectCardTypeRepository( publicKey: String, supportedCardBrands: List, clientKey: String, - type: String? ): List? { val key = hashBin(cardNumber) cachedBinLookup[key] = BinLookupResult.Loading - val binLookupResponse = makeBinLookup(cardNumber, publicKey, supportedCardBrands, clientKey, type) + val binLookupResponse = makeBinLookup(cardNumber, publicKey, supportedCardBrands, clientKey) return if (binLookupResponse == null) { cachedBinLookup.remove(key) @@ -166,12 +159,11 @@ class DefaultDetectCardTypeRepository( publicKey: String, supportedCardBrands: List, clientKey: String, - type: String? ): BinLookupResponse? { return runSuspendCatching { val encryptedBin = cardEncrypter.encryptBin(cardNumber, publicKey) val cardBrands = supportedCardBrands.map { it.txVariant } - val request = BinLookupRequest(encryptedBin, UUID.randomUUID().toString(), cardBrands, type) + val request = BinLookupRequest(encryptedBin, UUID.randomUUID().toString(), cardBrands) binLookupService.makeBinLookup( request = request, diff --git a/card/src/main/java/com/adyen/checkout/card/internal/data/api/DetectCardTypeRepository.kt b/card/src/main/java/com/adyen/checkout/card/internal/data/api/DetectCardTypeRepository.kt index ea81cf6544..d86fec995e 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/data/api/DetectCardTypeRepository.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/data/api/DetectCardTypeRepository.kt @@ -8,14 +8,12 @@ package com.adyen.checkout.card.internal.data.api -import androidx.annotation.RestrictTo import com.adyen.checkout.card.CardBrand import com.adyen.checkout.card.internal.data.model.DetectedCardType import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -interface DetectCardTypeRepository { +internal interface DetectCardTypeRepository { val detectedCardTypesFlow: Flow> @@ -26,6 +24,5 @@ interface DetectCardTypeRepository { supportedCardBrands: List, clientKey: String, coroutineScope: CoroutineScope, - type: String? = null ) } diff --git a/card/src/main/java/com/adyen/checkout/card/internal/data/model/BinLookupRequest.kt b/card/src/main/java/com/adyen/checkout/card/internal/data/model/BinLookupRequest.kt index e36fbc5694..5c297542b5 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/data/model/BinLookupRequest.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/data/model/BinLookupRequest.kt @@ -8,7 +8,6 @@ package com.adyen.checkout.card.internal.data.model -import androidx.annotation.RestrictTo import com.adyen.checkout.core.exception.ModelSerializationException import com.adyen.checkout.core.internal.data.model.JsonUtils import com.adyen.checkout.core.internal.data.model.ModelObject @@ -19,19 +18,16 @@ import org.json.JSONException import org.json.JSONObject @Parcelize -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -data class BinLookupRequest( +internal data class BinLookupRequest( val encryptedBin: String? = null, val requestId: String? = null, - val supportedBrands: List? = null, - val type: String? = null, + val supportedBrands: List? = null ) : ModelObject() { companion object { private const val ENCRYPTED_BIN = "encryptedBin" private const val REQUEST_ID = "requestId" private const val SUPPORTED_BRANDS = "supportedBrands" - private const val TYPE = "type" @JvmField val SERIALIZER: Serializer = object : Serializer { @@ -41,7 +37,6 @@ data class BinLookupRequest( jsonObject.putOpt(ENCRYPTED_BIN, modelObject.encryptedBin) jsonObject.putOpt(REQUEST_ID, modelObject.requestId) jsonObject.putOpt(SUPPORTED_BRANDS, JsonUtils.serializeOptStringList(modelObject.supportedBrands)) - jsonObject.putOpt(TYPE, modelObject.type) } catch (e: JSONException) { throw ModelSerializationException(BinLookupRequest::class.java, e) } @@ -53,8 +48,7 @@ data class BinLookupRequest( BinLookupRequest( encryptedBin = jsonObject.getStringOrNull(ENCRYPTED_BIN), requestId = jsonObject.getStringOrNull(REQUEST_ID), - supportedBrands = jsonObject.optStringList(SUPPORTED_BRANDS), - type = jsonObject.getStringOrNull(TYPE), + supportedBrands = jsonObject.optStringList(SUPPORTED_BRANDS) ) } catch (e: JSONException) { throw ModelSerializationException(BinLookupRequest::class.java, e) diff --git a/card/src/main/java/com/adyen/checkout/card/internal/data/model/BinLookupResponse.kt b/card/src/main/java/com/adyen/checkout/card/internal/data/model/BinLookupResponse.kt index 5908a98da4..0bb04e0218 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/data/model/BinLookupResponse.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/data/model/BinLookupResponse.kt @@ -8,7 +8,6 @@ package com.adyen.checkout.card.internal.data.model -import androidx.annotation.RestrictTo import com.adyen.checkout.core.exception.ModelSerializationException import com.adyen.checkout.core.internal.data.model.ModelObject import com.adyen.checkout.core.internal.data.model.ModelUtils @@ -18,8 +17,7 @@ import org.json.JSONException import org.json.JSONObject @Parcelize -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -data class BinLookupResponse( +internal data class BinLookupResponse( val brands: List? = null, val issuingCountryCode: String? = null, val requestId: String? = null diff --git a/card/src/main/java/com/adyen/checkout/card/internal/data/model/DetectedCardType.kt b/card/src/main/java/com/adyen/checkout/card/internal/data/model/DetectedCardType.kt index f2fdfe5c1c..faf99e221e 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/data/model/DetectedCardType.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/data/model/DetectedCardType.kt @@ -8,11 +8,9 @@ package com.adyen.checkout.card.internal.data.model -import androidx.annotation.RestrictTo import com.adyen.checkout.card.CardBrand -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -data class DetectedCardType( +internal data class DetectedCardType( val cardBrand: CardBrand, val isReliable: Boolean, val enableLuhnCheck: Boolean, diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/CardDelegate.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/CardDelegate.kt index 5d3a8b0c8d..db4d5669f1 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/CardDelegate.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/CardDelegate.kt @@ -8,7 +8,6 @@ package com.adyen.checkout.card.internal.ui -import androidx.annotation.RestrictTo import com.adyen.checkout.card.BinLookupData import com.adyen.checkout.card.CardComponentState import com.adyen.checkout.card.internal.ui.model.CardInputData @@ -21,8 +20,7 @@ import com.adyen.checkout.ui.core.internal.ui.UIStateDelegate import com.adyen.checkout.ui.core.internal.ui.ViewProvidingDelegate import kotlinx.coroutines.flow.Flow -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -interface CardDelegate : +internal interface CardDelegate : PaymentComponentDelegate, ViewProvidingDelegate, ButtonDelegate, diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt index 58aaca5762..786f81cf77 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt @@ -8,7 +8,6 @@ package com.adyen.checkout.card.internal.ui -import androidx.annotation.RestrictTo import androidx.annotation.VisibleForTesting import androidx.lifecycle.LifecycleOwner import com.adyen.checkout.card.BinLookupData @@ -20,7 +19,6 @@ import com.adyen.checkout.card.SocialSecurityNumberVisibility import com.adyen.checkout.card.internal.data.api.DetectCardTypeRepository import com.adyen.checkout.card.internal.data.model.Brand import com.adyen.checkout.card.internal.data.model.DetectedCardType -import com.adyen.checkout.card.internal.ui.model.CVCVisibility import com.adyen.checkout.card.internal.ui.model.CardComponentParams import com.adyen.checkout.card.internal.ui.model.CardInputData import com.adyen.checkout.card.internal.ui.model.CardListItem @@ -85,9 +83,8 @@ import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -@Suppress("LongParameterList", "TooManyFunctions", "LargeClass") -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -class DefaultCardDelegate( +@Suppress("LongParameterList", "TooManyFunctions") +internal class DefaultCardDelegate( private val observerRepository: PaymentObserverRepository, private val publicKeyRepository: PublicKeyRepository, override val componentParams: CardComponentParams, @@ -225,8 +222,7 @@ class DefaultCardDelegate( publicKey = publicKey, supportedCardBrands = componentParams.supportedCardBrands, clientKey = componentParams.clientKey, - coroutineScope = coroutineScope, - type = paymentMethod.type + coroutineScope = coroutineScope ) requestStateList(inputData.address.country) } @@ -312,6 +308,8 @@ class DefaultCardDelegate( detectedCardTypes = filteredDetectedCardTypes ) + val reliableSelectedCard = if (isReliable) selectedOrFirstCardType else null + // perform a Luhn Check if no brands are detected val enableLuhnCheck = selectedOrFirstCardType?.enableLuhnCheck ?: true @@ -335,13 +333,13 @@ class DefaultCardDelegate( addressState = validateAddress( inputData.address, addressFormUIState, - selectedOrFirstCardType, + reliableSelectedCard, updatedCountryOptions, updatedStateOptions ), installmentState = makeInstallmentFieldState(inputData.installmentOption), shouldStorePaymentMethod = inputData.isStorePaymentMethodSwitchChecked, - cvcUIState = makeCvcUIState(selectedOrFirstCardType), + cvcUIState = makeCvcUIState(selectedOrFirstCardType?.cvcPolicy), expiryDateUIState = makeExpiryDateUIState(selectedOrFirstCardType?.expiryDatePolicy), holderNameUIState = getHolderNameUIState(), showStorePaymentField = showStorePaymentField(), @@ -364,9 +362,7 @@ class DefaultCardDelegate( private fun isCardListVisible( cardBrands: List, detectedCardTypes: List - ): Boolean = cardBrands.isNotEmpty() && - detectedCardTypes.isEmpty() && - paymentMethod.type == PaymentMethodTypes.SCHEME + ): Boolean = cardBrands.isNotEmpty() && detectedCardTypes.isEmpty() override fun getPaymentMethodType(): String { return paymentMethod.type ?: PaymentMethodTypes.UNKNOWN @@ -481,8 +477,14 @@ class DefaultCardDelegate( securityCode: String, cardType: DetectedCardType? ): FieldState { - val cvcUIState = makeCvcUIState(cardType) - return CardValidationUtils.validateSecurityCode(securityCode, cardType, cvcUIState) + return if (componentParams.isHideCvc) { + FieldState( + securityCode, + Validation.Valid + ) + } else { + CardValidationUtils.validateSecurityCode(securityCode, cardType) + } } private fun validateHolderName(holderName: String): FieldState { @@ -545,8 +547,8 @@ class DefaultCardDelegate( ) } - private fun isCvcHidden(cvcUIState: InputFieldUIState = outputData.cvcUIState): Boolean { - return cvcUIState == InputFieldUIState.HIDDEN + private fun isCvcHidden(): Boolean { + return componentParams.isHideCvc } private fun isSocialSecurityNumberRequired(): Boolean { @@ -601,42 +603,18 @@ class DefaultCardDelegate( ) } - private fun makeCvcUIState(detectedCardType: DetectedCardType?): InputFieldUIState { - Logger.d(TAG, "makeCvcUIState: ${detectedCardType?.cvcPolicy}") - - return if (detectedCardType?.isReliable == true) { - when (componentParams.cvcVisibility) { - CVCVisibility.ALWAYS_SHOW -> { - when (detectedCardType.cvcPolicy) { - Brand.FieldPolicy.OPTIONAL -> InputFieldUIState.OPTIONAL - Brand.FieldPolicy.HIDDEN -> InputFieldUIState.HIDDEN - else -> InputFieldUIState.REQUIRED - } - } - - CVCVisibility.HIDE_FIRST -> { - when (detectedCardType.cvcPolicy) { - Brand.FieldPolicy.REQUIRED -> InputFieldUIState.REQUIRED - Brand.FieldPolicy.OPTIONAL -> InputFieldUIState.OPTIONAL - else -> InputFieldUIState.HIDDEN - } - } - - CVCVisibility.ALWAYS_HIDE -> InputFieldUIState.HIDDEN - } - } else { - when (componentParams.cvcVisibility) { - CVCVisibility.ALWAYS_SHOW -> InputFieldUIState.REQUIRED - CVCVisibility.HIDE_FIRST -> InputFieldUIState.HIDDEN - CVCVisibility.ALWAYS_HIDE -> InputFieldUIState.HIDDEN - } + private fun makeCvcUIState(cvcPolicy: Brand.FieldPolicy?): InputFieldUIState { + Logger.d(TAG, "makeCvcUIState: $cvcPolicy") + return when { + isCvcHidden() -> InputFieldUIState.HIDDEN + cvcPolicy?.isRequired() == false -> InputFieldUIState.OPTIONAL + else -> InputFieldUIState.REQUIRED } } private fun makeExpiryDateUIState(expiryDatePolicy: Brand.FieldPolicy?): InputFieldUIState { - return when (expiryDatePolicy) { - Brand.FieldPolicy.OPTIONAL -> InputFieldUIState.OPTIONAL - Brand.FieldPolicy.HIDDEN -> InputFieldUIState.HIDDEN + return when { + expiryDatePolicy?.isRequired() == false -> InputFieldUIState.OPTIONAL else -> InputFieldUIState.REQUIRED } } diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/StoredCardDelegate.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/StoredCardDelegate.kt index 5f9919b0e2..238c6175c2 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/StoredCardDelegate.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/StoredCardDelegate.kt @@ -21,7 +21,6 @@ import com.adyen.checkout.card.internal.ui.model.CardInputData import com.adyen.checkout.card.internal.ui.model.CardOutputData import com.adyen.checkout.card.internal.ui.model.ExpiryDate import com.adyen.checkout.card.internal.ui.model.InputFieldUIState -import com.adyen.checkout.card.internal.ui.model.StoredCVCVisibility import com.adyen.checkout.card.internal.util.CardValidationUtils import com.adyen.checkout.components.core.OrderRequest import com.adyen.checkout.components.core.PaymentComponentData @@ -83,8 +82,7 @@ internal class StoredCardDelegate( isReliable = true, enableLuhnCheck = true, cvcPolicy = when { - componentParams.storedCVCVisibility == StoredCVCVisibility.HIDE || - noCvcBrands.contains(cardType) -> Brand.FieldPolicy.HIDDEN + componentParams.isHideCvcStoredCard || noCvcBrands.contains(cardType) -> Brand.FieldPolicy.HIDDEN else -> Brand.FieldPolicy.REQUIRED }, expiryDatePolicy = Brand.FieldPolicy.REQUIRED, @@ -308,12 +306,18 @@ internal class StoredCardDelegate( } private fun validateSecurityCode(securityCode: String, detectedCardType: DetectedCardType): FieldState { - val cvcUiState = makeCvcUIState(detectedCardType.cvcPolicy) - return CardValidationUtils.validateSecurityCode(securityCode, detectedCardType, cvcUiState) + return if (componentParams.isHideCvcStoredCard || noCvcBrands.contains(detectedCardType.cardBrand)) { + FieldState( + securityCode, + Validation.Valid + ) + } else { + CardValidationUtils.validateSecurityCode(securityCode, detectedCardType) + } } private fun isCvcHidden(): Boolean { - return outputData.cvcUIState == InputFieldUIState.HIDDEN + return componentParams.isHideCvcStoredCard || noCvcBrands.contains(cardType) } private fun mapComponentState( @@ -378,10 +382,10 @@ internal class StoredCardDelegate( private fun makeCvcUIState(cvcPolicy: Brand.FieldPolicy): InputFieldUIState { Logger.d(TAG, "makeCvcUIState: $cvcPolicy") - return when (cvcPolicy) { - Brand.FieldPolicy.REQUIRED -> InputFieldUIState.REQUIRED - Brand.FieldPolicy.OPTIONAL -> InputFieldUIState.OPTIONAL - Brand.FieldPolicy.HIDDEN -> InputFieldUIState.HIDDEN + return when { + isCvcHidden() -> InputFieldUIState.HIDDEN + !cvcPolicy.isRequired() -> InputFieldUIState.OPTIONAL + else -> InputFieldUIState.REQUIRED } } diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CVCVisibility.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CVCVisibility.kt deleted file mode 100644 index 1a975b9fed..0000000000 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CVCVisibility.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) 2023 Adyen N.V. - * - * This file is open source and available under the MIT license. See the LICENSE file for more info. - * - * Created by ozgur on 5/9/2023. - */ - -package com.adyen.checkout.card.internal.ui.model - -import androidx.annotation.RestrictTo - -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -enum class CVCVisibility { - ALWAYS_SHOW, HIDE_FIRST, ALWAYS_HIDE -} - -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -enum class StoredCVCVisibility { - SHOW, HIDE -} diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParams.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParams.kt index 36890caba5..13c47c9467 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParams.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParams.kt @@ -8,7 +8,6 @@ package com.adyen.checkout.card.internal.ui.model -import androidx.annotation.RestrictTo import com.adyen.checkout.card.CardBrand import com.adyen.checkout.card.KCPAuthVisibility import com.adyen.checkout.card.SocialSecurityNumberVisibility @@ -20,8 +19,7 @@ import com.adyen.checkout.core.Environment import com.adyen.checkout.ui.core.internal.ui.model.AddressParams import java.util.Locale -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -data class CardComponentParams( +internal data class CardComponentParams( override val shopperLocale: Locale, override val environment: Environment, override val clientKey: String, @@ -33,10 +31,10 @@ data class CardComponentParams( val supportedCardBrands: List, val shopperReference: String?, val isStorePaymentFieldVisible: Boolean, + val isHideCvc: Boolean, + val isHideCvcStoredCard: Boolean, val socialSecurityNumberVisibility: SocialSecurityNumberVisibility, val kcpAuthVisibility: KCPAuthVisibility, val installmentParams: InstallmentParams?, val addressParams: AddressParams, - val cvcVisibility: CVCVisibility, - val storedCVCVisibility: StoredCVCVisibility ) : ComponentParams, ButtonParams diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParamsMapper.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParamsMapper.kt index 161279da93..7a4ffbf88d 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParamsMapper.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardComponentParamsMapper.kt @@ -80,20 +80,12 @@ internal class CardComponentParamsMapper( supportedCardBrands = supportedCardBrands, shopperReference = shopperReference, isStorePaymentFieldVisible = isStorePaymentFieldVisible ?: true, + isHideCvc = isHideCvc ?: false, + isHideCvcStoredCard = isHideCvcStoredCard ?: false, socialSecurityNumberVisibility = socialSecurityNumberVisibility ?: SocialSecurityNumberVisibility.HIDE, kcpAuthVisibility = kcpAuthVisibility ?: KCPAuthVisibility.HIDE, installmentParams = installmentsParamsMapper.mapToInstallmentParams(installmentConfiguration), - addressParams = addressConfiguration?.mapToAddressParam() ?: AddressParams.None, - cvcVisibility = if (isHideCvc == true) { - CVCVisibility.ALWAYS_HIDE - } else { - CVCVisibility.ALWAYS_SHOW - }, - storedCVCVisibility = if (isHideCvcStoredCard == true) { - StoredCVCVisibility.HIDE - } else { - StoredCVCVisibility.SHOW - } + addressParams = addressConfiguration?.mapToAddressParam() ?: AddressParams.None ) } @@ -110,14 +102,12 @@ internal class CardComponentParamsMapper( Logger.v(TAG, "Reading supportedCardTypes from configuration") supportedCardBrands } - paymentMethod.brands.orEmpty().isNotEmpty() -> { Logger.v(TAG, "Reading supportedCardTypes from API brands") paymentMethod.brands.orEmpty().map { CardBrand(txVariant = it) } } - else -> { Logger.v(TAG, "Falling back to CardConfiguration.DEFAULT_SUPPORTED_CARDS_LIST") CardConfiguration.DEFAULT_SUPPORTED_CARDS_LIST @@ -156,11 +146,9 @@ internal class CardComponentParamsMapper( addressFieldPolicy.mapToAddressParamFieldPolicy() ) } - AddressConfiguration.None -> { AddressParams.None } - is AddressConfiguration.PostalCode -> { AddressParams.PostalCode(addressFieldPolicy.mapToAddressParamFieldPolicy()) } @@ -172,11 +160,9 @@ internal class CardComponentParamsMapper( is AddressConfiguration.CardAddressFieldPolicy.Optional -> { AddressFieldPolicyParams.Optional } - is AddressConfiguration.CardAddressFieldPolicy.OptionalForCardTypes -> { AddressFieldPolicyParams.OptionalForCardTypes(brands) } - is AddressConfiguration.CardAddressFieldPolicy.Required -> { AddressFieldPolicyParams.Required } diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardInputData.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardInputData.kt index 52f0d917d0..0c57b4568c 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardInputData.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardInputData.kt @@ -7,13 +7,11 @@ */ package com.adyen.checkout.card.internal.ui.model -import androidx.annotation.RestrictTo import com.adyen.checkout.card.internal.ui.view.InstallmentModel import com.adyen.checkout.components.core.internal.ui.model.InputData import com.adyen.checkout.ui.core.internal.ui.model.AddressInputModel -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -data class CardInputData( +internal data class CardInputData( var cardNumber: String = "", var expiryDate: ExpiryDate = ExpiryDate.EMPTY_DATE, var securityCode: String = "", diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardListItem.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardListItem.kt index f0497afe5b..e8e50c3503 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardListItem.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardListItem.kt @@ -8,12 +8,10 @@ package com.adyen.checkout.card.internal.ui.model -import androidx.annotation.RestrictTo import com.adyen.checkout.card.CardBrand import com.adyen.checkout.core.Environment -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -data class CardListItem( +internal data class CardListItem( val cardBrand: CardBrand, val isDetected: Boolean, // We need the environment to load the logo diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardOutputData.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardOutputData.kt index 4a5858f99c..703b1659c6 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardOutputData.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/CardOutputData.kt @@ -7,7 +7,6 @@ */ package com.adyen.checkout.card.internal.ui.model -import androidx.annotation.RestrictTo import androidx.annotation.StringRes import com.adyen.checkout.card.internal.data.model.DetectedCardType import com.adyen.checkout.card.internal.ui.view.InstallmentModel @@ -16,8 +15,7 @@ import com.adyen.checkout.components.core.internal.ui.model.OutputData import com.adyen.checkout.ui.core.internal.ui.AddressFormUIState import com.adyen.checkout.ui.core.internal.ui.model.AddressOutputData -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -data class CardOutputData( +internal data class CardOutputData( val cardNumberState: FieldState, val expiryDateState: FieldState, val securityCodeState: FieldState, diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InputFieldUIState.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InputFieldUIState.kt index 852a0bf8c6..fa9a69c40a 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InputFieldUIState.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InputFieldUIState.kt @@ -8,9 +8,6 @@ package com.adyen.checkout.card.internal.ui.model -import androidx.annotation.RestrictTo - -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -enum class InputFieldUIState { +internal enum class InputFieldUIState { REQUIRED, OPTIONAL, HIDDEN } diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentOption.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentOption.kt index c42e6a0e89..ac7c2899af 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentOption.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentOption.kt @@ -8,10 +8,7 @@ package com.adyen.checkout.card.internal.ui.model -import androidx.annotation.RestrictTo - -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -enum class InstallmentOption(val type: String?) { +internal enum class InstallmentOption(val type: String?) { ONE_TIME(null), REGULAR("regular"), REVOLVING("revolving") diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentOptionParams.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentOptionParams.kt index 663a7aa753..b020847632 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentOptionParams.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentOptionParams.kt @@ -8,7 +8,6 @@ package com.adyen.checkout.card.internal.ui.model -import androidx.annotation.RestrictTo import com.adyen.checkout.card.CardBrand /** @@ -16,8 +15,7 @@ import com.adyen.checkout.card.CardBrand * * Note: All values specified in [values] must be greater than 1. */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -sealed class InstallmentOptionParams { +internal sealed class InstallmentOptionParams { abstract val values: List abstract val includeRevolving: Boolean diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentParams.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentParams.kt index 972ad84762..b4a3878930 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentParams.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/model/InstallmentParams.kt @@ -8,7 +8,6 @@ package com.adyen.checkout.card.internal.ui.model -import androidx.annotation.RestrictTo import com.adyen.checkout.card.CardBrand /** @@ -23,8 +22,7 @@ import com.adyen.checkout.card.CardBrand * @param defaultOptions Installment Options to be used for all card types. * @param cardBasedOptions Installment Options to be used for specific card types. */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -data class InstallmentParams( +internal data class InstallmentParams( val defaultOptions: InstallmentOptionParams.DefaultInstallmentOptions? = null, val cardBasedOptions: List = emptyList() ) diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/view/CardView.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/view/CardView.kt index ba178b1cff..c9bd84b4e8 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/view/CardView.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/view/CardView.kt @@ -18,7 +18,6 @@ import android.view.View.OnFocusChangeListener import android.view.WindowManager import android.widget.AdapterView import android.widget.LinearLayout -import androidx.annotation.RestrictTo import androidx.annotation.StringRes import androidx.core.view.isVisible import com.adyen.checkout.card.CardBrand @@ -56,8 +55,7 @@ import kotlinx.coroutines.flow.onEach * CardView for [CardComponent]. */ @Suppress("TooManyFunctions", "LargeClass") -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -class CardView @JvmOverloads constructor( +internal class CardView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 @@ -598,14 +596,12 @@ class CardView @JvmOverloads constructor( localizedContext ) } - InputFieldUIState.OPTIONAL -> { binding.textInputLayoutSecurityCode.isVisible = true binding.textInputLayoutSecurityCode.hint = localizedContext.getString( R.string.checkout_card_security_code_optional_hint ) } - InputFieldUIState.HIDDEN -> { binding.textInputLayoutSecurityCode.isVisible = false // We don't expect the hidden status to change back to isVisible, so we don't worry about putting the @@ -626,14 +622,12 @@ class CardView @JvmOverloads constructor( localizedContext ) } - InputFieldUIState.OPTIONAL -> { binding.textInputLayoutExpiryDate.isVisible = true binding.textInputLayoutExpiryDate.hint = localizedContext.getString( R.string.checkout_card_expiry_date_optional_hint ) } - InputFieldUIState.HIDDEN -> { binding.textInputLayoutExpiryDate.isVisible = false val params = binding.textInputLayoutSecurityCode.layoutParams as LayoutParams @@ -671,12 +665,10 @@ class CardView @JvmOverloads constructor( binding.addressFormInput.isVisible = true binding.textInputLayoutPostalCode.isVisible = false } - AddressFormUIState.POSTAL_CODE -> { binding.addressFormInput.isVisible = false binding.textInputLayoutPostalCode.isVisible = true } - AddressFormUIState.NONE -> { binding.addressFormInput.isVisible = false binding.textInputLayoutPostalCode.isVisible = false @@ -695,7 +687,6 @@ class CardView @JvmOverloads constructor( } binding.textInputLayoutPostalCode.setLocalizedHintFromStyle(postalCodeStyleResId, localizedContext) } - else -> { // no ops } diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/view/InstallmentListAdapter.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/view/InstallmentListAdapter.kt index 74415926a3..68c45ec2a0 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/view/InstallmentListAdapter.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/view/InstallmentListAdapter.kt @@ -15,7 +15,6 @@ import android.view.ViewGroup import android.widget.BaseAdapter import android.widget.Filter import android.widget.Filterable -import androidx.annotation.RestrictTo import androidx.annotation.StringRes import androidx.recyclerview.widget.RecyclerView import com.adyen.checkout.card.databinding.InstallmentViewBinding @@ -23,7 +22,6 @@ import com.adyen.checkout.card.internal.ui.model.InstallmentOption import com.adyen.checkout.card.internal.util.InstallmentUtils // We need context to inflate the views and localizedContext to fetch the strings -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) internal class InstallmentListAdapter( private val context: Context, private val localizedContext: Context @@ -66,8 +64,7 @@ internal class InstallmentListAdapter( } } -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -data class InstallmentModel( +internal data class InstallmentModel( @StringRes val textResId: Int, val value: Int?, val option: InstallmentOption diff --git a/card/src/main/java/com/adyen/checkout/card/internal/util/CardValidationUtils.kt b/card/src/main/java/com/adyen/checkout/card/internal/util/CardValidationUtils.kt index 4bd3ea891c..cf19c3a052 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/util/CardValidationUtils.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/util/CardValidationUtils.kt @@ -15,7 +15,6 @@ import com.adyen.checkout.card.R import com.adyen.checkout.card.internal.data.model.Brand import com.adyen.checkout.card.internal.data.model.DetectedCardType import com.adyen.checkout.card.internal.ui.model.ExpiryDate -import com.adyen.checkout.card.internal.ui.model.InputFieldUIState import com.adyen.checkout.components.core.internal.ui.model.FieldState import com.adyen.checkout.components.core.internal.ui.model.Validation import com.adyen.checkout.core.internal.util.StringUtil @@ -138,18 +137,13 @@ object CardValidationUtils { /** * Validate Security Code. */ - internal fun validateSecurityCode( - securityCode: String, - detectedCardType: DetectedCardType?, - cvcUIState: InputFieldUIState - ): FieldState { + internal fun validateSecurityCode(securityCode: String, detectedCardType: DetectedCardType?): FieldState { val normalizedSecurityCode = StringUtil.normalize(securityCode) val length = normalizedSecurityCode.length val invalidState = Validation.Invalid(R.string.checkout_security_code_not_valid) val validation = when { - cvcUIState == InputFieldUIState.HIDDEN -> Validation.Valid !StringUtil.isDigitsAndSeparatorsOnly(normalizedSecurityCode) -> invalidState - cvcUIState == InputFieldUIState.OPTIONAL && length == 0 -> Validation.Valid + detectedCardType?.cvcPolicy?.isRequired() == false && length == 0 -> Validation.Valid detectedCardType?.cardBrand == CardBrand(cardType = CardType.AMERICAN_EXPRESS) && length == AMEX_SECURITY_CODE_SIZE -> Validation.Valid diff --git a/card/src/main/java/com/adyen/checkout/card/internal/util/DetectedCardTypesUtils.kt b/card/src/main/java/com/adyen/checkout/card/internal/util/DetectedCardTypesUtils.kt index 33493c2c5f..d0c351610e 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/util/DetectedCardTypesUtils.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/util/DetectedCardTypesUtils.kt @@ -22,8 +22,7 @@ internal object DetectedCardTypesUtils { } fun getSelectedOrFirstDetectedCardType(detectedCardTypes: List): DetectedCardType? { - val selectedCardType = getSelectedCardType(detectedCardTypes) - return selectedCardType ?: detectedCardTypes.firstOrNull() + return getSelectedCardType(detectedCardTypes) ?: detectedCardTypes.firstOrNull() } fun getSelectedCardType(detectedCardTypes: List): DetectedCardType? { diff --git a/card/src/test/java/com/adyen/checkout/card/internal/data/api/TestDetectCardTypeRepository.kt b/card/src/test/java/com/adyen/checkout/card/internal/data/api/TestDetectCardTypeRepository.kt index cc3d20ee22..832ad9677b 100644 --- a/card/src/test/java/com/adyen/checkout/card/internal/data/api/TestDetectCardTypeRepository.kt +++ b/card/src/test/java/com/adyen/checkout/card/internal/data/api/TestDetectCardTypeRepository.kt @@ -34,7 +34,6 @@ internal class TestDetectCardTypeRepository : DetectCardTypeRepository { supportedCardBrands: List, clientKey: String, coroutineScope: CoroutineScope, - type: String? ) { val detectedCardTypes = when (detectionResult) { TestDetectedCardType.ERROR -> null diff --git a/card/src/test/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegateTest.kt b/card/src/test/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegateTest.kt index 80e0655a82..de44d012c0 100644 --- a/card/src/test/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegateTest.kt +++ b/card/src/test/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegateTest.kt @@ -447,7 +447,7 @@ internal class DefaultCardDelegateTest( assertTrue(expiryDateState.validation is Validation.Valid) assertTrue(securityCodeState.validation is Validation.Valid) assertEquals(InputFieldUIState.OPTIONAL, cvcUIState) - assertEquals(InputFieldUIState.HIDDEN, expiryDateUIState) + assertEquals(InputFieldUIState.OPTIONAL, expiryDateUIState) assertTrue(isDualBranded) } } @@ -1105,24 +1105,23 @@ internal class DefaultCardDelegateTest( } @Test - fun `when card number is detected over network, then callback should be called with reliable result`() = - runTest { - detectCardTypeRepository.detectionResult = TestDetectedCardType.FETCHED_FROM_NETWORK + fun `when card number is detected over network, then callback should be called with reliable result`() = runTest { + detectCardTypeRepository.detectionResult = TestDetectedCardType.FETCHED_FROM_NETWORK - delegate.setOnBinLookupListener { data -> - launch(this.coroutineContext) { - with(data.first()) { - assertEquals("mc", brand) - assertEquals("mccredit", paymentMethodVariant) - assertTrue(isReliable) - } + delegate.setOnBinLookupListener { data -> + launch(this.coroutineContext) { + with(data.first()) { + assertEquals("mc", brand) + assertEquals("mccredit", paymentMethodVariant) + assertTrue(isReliable) } } + } - delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - delegate.updateInputData { cardNumber = "5555444" } - } + delegate.updateInputData { cardNumber = "5555444" } + } @Test fun `when callback is called multiple times, then it should only trigger if the data changed`() = runTest { @@ -1156,7 +1155,7 @@ internal class DefaultCardDelegateTest( cardEncrypter: BaseCardEncrypter = this.cardEncrypter, genericEncrypter: BaseGenericEncrypter = this.genericEncrypter, configuration: CardConfiguration = getDefaultCardConfigurationBuilder().build(), - paymentMethod: PaymentMethod = PaymentMethod(type = PaymentMethodTypes.SCHEME), + paymentMethod: PaymentMethod = PaymentMethod(), analyticsRepository: AnalyticsRepository = this.analyticsRepository, submitHandler: SubmitHandler = this.submitHandler, order: OrderRequest? = TEST_ORDER, diff --git a/card/src/test/java/com/adyen/checkout/card/internal/ui/model/CardComponentParamsMapperTest.kt b/card/src/test/java/com/adyen/checkout/card/internal/ui/model/CardComponentParamsMapperTest.kt index 55415eaf44..83e65786a0 100644 --- a/card/src/test/java/com/adyen/checkout/card/internal/ui/model/CardComponentParamsMapperTest.kt +++ b/card/src/test/java/com/adyen/checkout/card/internal/ui/model/CardComponentParamsMapperTest.kt @@ -107,8 +107,8 @@ internal class CardComponentParamsMapperTest { shopperReference = shopperReference, isStorePaymentFieldVisible = false, isSubmitButtonVisible = false, - cvcVisibility = CVCVisibility.ALWAYS_HIDE, - storedCVCVisibility = StoredCVCVisibility.HIDE, + isHideCvc = true, + isHideCvcStoredCard = true, socialSecurityNumberVisibility = SocialSecurityNumberVisibility.SHOW, kcpAuthVisibility = KCPAuthVisibility.SHOW, installmentParams = expectedInstallmentParams, @@ -451,12 +451,12 @@ internal class CardComponentParamsMapperTest { supportedCardBrands: List = CardConfiguration.DEFAULT_SUPPORTED_CARDS_LIST, shopperReference: String? = null, isStorePaymentFieldVisible: Boolean = true, + isHideCvc: Boolean = false, + isHideCvcStoredCard: Boolean = false, socialSecurityNumberVisibility: SocialSecurityNumberVisibility = SocialSecurityNumberVisibility.HIDE, kcpAuthVisibility: KCPAuthVisibility = KCPAuthVisibility.HIDE, installmentParams: InstallmentParams? = null, addressParams: AddressParams = AddressParams.None, - cvcVisibility: CVCVisibility = CVCVisibility.ALWAYS_SHOW, - storedCVCVisibility: StoredCVCVisibility = StoredCVCVisibility.SHOW ) = CardComponentParams( shopperLocale = shopperLocale, environment = environment, @@ -468,13 +468,13 @@ internal class CardComponentParamsMapperTest { supportedCardBrands = supportedCardBrands, shopperReference = shopperReference, isStorePaymentFieldVisible = isStorePaymentFieldVisible, + isHideCvc = isHideCvc, + isHideCvcStoredCard = isHideCvcStoredCard, socialSecurityNumberVisibility = socialSecurityNumberVisibility, kcpAuthVisibility = kcpAuthVisibility, installmentParams = installmentParams, addressParams = addressParams, - amount = amount, - cvcVisibility = cvcVisibility, - storedCVCVisibility = storedCVCVisibility, + amount = amount ) companion object { diff --git a/card/src/test/java/com/adyen/checkout/card/internal/util/CardValidationUtilsTest.kt b/card/src/test/java/com/adyen/checkout/card/internal/util/CardValidationUtilsTest.kt index c2c1f1d68d..02b4b3c6fc 100644 --- a/card/src/test/java/com/adyen/checkout/card/internal/util/CardValidationUtilsTest.kt +++ b/card/src/test/java/com/adyen/checkout/card/internal/util/CardValidationUtilsTest.kt @@ -14,7 +14,6 @@ import com.adyen.checkout.card.R import com.adyen.checkout.card.internal.data.model.Brand import com.adyen.checkout.card.internal.data.model.DetectedCardType import com.adyen.checkout.card.internal.ui.model.ExpiryDate -import com.adyen.checkout.card.internal.ui.model.InputFieldUIState import com.adyen.checkout.components.core.internal.ui.model.FieldState import com.adyen.checkout.components.core.internal.ui.model.Validation import org.junit.jupiter.api.Assertions.assertEquals @@ -354,51 +353,42 @@ internal class CardValidationUtilsTest { @Test fun `cvc is empty then result should be invalid`() { val cvc = "" - val actual = - CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType(), InputFieldUIState.REQUIRED) + val actual = CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType()) assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual) } @Test fun `cvc is 1 digit then result should be invalid`() { val cvc = "7" - val actual = - CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType(), InputFieldUIState.REQUIRED) + val actual = CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType()) assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual) } @Test fun `cvc is 2 digits then result should be invalid`() { val cvc = "12" - val actual = - CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType(), InputFieldUIState.REQUIRED) + val actual = CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType()) assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual) } @Test fun `cvc is 3 digits then result should be valid`() { val cvc = "737" - val actual = - CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType(), InputFieldUIState.REQUIRED) + val actual = CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType()) assertEquals(FieldState(cvc, Validation.Valid), actual) } @Test fun `cvc is 4 digits then result should be invalid`() { val cvc = "8689" - val actual = - CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType(), InputFieldUIState.REQUIRED) + val actual = CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType()) assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual) } @Test fun `cvc is 6 digits then result should be invalid`() { val cvc = "457835" - val actual = CardValidationUtils.validateSecurityCode( - cvc, - getDetectedCardType(), - InputFieldUIState.REQUIRED - ) + val actual = CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType()) assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual) } @@ -407,8 +397,7 @@ internal class CardValidationUtilsTest { val cvc = "737" val actual = CardValidationUtils.validateSecurityCode( cvc, - getDetectedCardType(cardBrand = CardBrand(CardType.AMERICAN_EXPRESS)), - cvcUIState = InputFieldUIState.REQUIRED + getDetectedCardType(cardBrand = CardBrand(CardType.AMERICAN_EXPRESS)) ) assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual) } @@ -418,8 +407,7 @@ internal class CardValidationUtilsTest { val cvc = "8689" val actual = CardValidationUtils.validateSecurityCode( cvc, - getDetectedCardType(cardBrand = CardBrand(CardType.AMERICAN_EXPRESS)), - cvcUIState = InputFieldUIState.REQUIRED + getDetectedCardType(cardBrand = CardBrand(CardType.AMERICAN_EXPRESS)) ) assertEquals(FieldState(cvc, Validation.Valid), actual) } @@ -427,8 +415,7 @@ internal class CardValidationUtilsTest { @Test fun `cvc has invalid characters then result should be invalid`() { val cvc = "1%y" - val actual = - CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType(), InputFieldUIState.REQUIRED) + val actual = CardValidationUtils.validateSecurityCode(cvc, getDetectedCardType()) assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual) } @@ -437,8 +424,7 @@ internal class CardValidationUtilsTest { val cvc = "546" val actual = CardValidationUtils.validateSecurityCode( cvc, - getDetectedCardType(cvcPolicy = Brand.FieldPolicy.REQUIRED), - cvcUIState = InputFieldUIState.REQUIRED + getDetectedCardType(cvcPolicy = Brand.FieldPolicy.REQUIRED) ) assertEquals(FieldState(cvc, Validation.Valid), actual) } @@ -448,8 +434,7 @@ internal class CardValidationUtilsTest { val cvc = "345" val actual = CardValidationUtils.validateSecurityCode( cvc, - getDetectedCardType(cvcPolicy = Brand.FieldPolicy.OPTIONAL), - cvcUIState = InputFieldUIState.OPTIONAL + getDetectedCardType(cvcPolicy = Brand.FieldPolicy.OPTIONAL) ) assertEquals(FieldState(cvc, Validation.Valid), actual) } @@ -459,8 +444,7 @@ internal class CardValidationUtilsTest { val cvc = "156" val actual = CardValidationUtils.validateSecurityCode( cvc, - getDetectedCardType(cvcPolicy = Brand.FieldPolicy.HIDDEN), - cvcUIState = InputFieldUIState.HIDDEN + getDetectedCardType(cvcPolicy = Brand.FieldPolicy.HIDDEN) ) assertEquals(FieldState(cvc, Validation.Valid), actual) } @@ -470,8 +454,7 @@ internal class CardValidationUtilsTest { val cvc = "77" val actual = CardValidationUtils.validateSecurityCode( cvc, - getDetectedCardType(cvcPolicy = Brand.FieldPolicy.REQUIRED), - InputFieldUIState.REQUIRED + getDetectedCardType(cvcPolicy = Brand.FieldPolicy.REQUIRED) ) assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual) } @@ -481,21 +464,19 @@ internal class CardValidationUtilsTest { val cvc = "9" val actual = CardValidationUtils.validateSecurityCode( cvc, - getDetectedCardType(cvcPolicy = Brand.FieldPolicy.OPTIONAL), - InputFieldUIState.OPTIONAL + getDetectedCardType(cvcPolicy = Brand.FieldPolicy.OPTIONAL) ) assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual) } @Test - fun `cvc is invalid with field policy hidden then result should be valid`() { + fun `cvc is invalid with field policy hidden then result should be invalid`() { val cvc = "1358" val actual = CardValidationUtils.validateSecurityCode( cvc, - getDetectedCardType(cvcPolicy = Brand.FieldPolicy.HIDDEN), - cvcUIState = InputFieldUIState.HIDDEN + getDetectedCardType(cvcPolicy = Brand.FieldPolicy.HIDDEN) ) - assertEquals(FieldState(cvc, Validation.Valid), actual) + assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual) } @Test @@ -503,8 +484,7 @@ internal class CardValidationUtilsTest { val cvc = "" val actual = CardValidationUtils.validateSecurityCode( cvc, - getDetectedCardType(cvcPolicy = Brand.FieldPolicy.REQUIRED), - cvcUIState = InputFieldUIState.REQUIRED + getDetectedCardType(cvcPolicy = Brand.FieldPolicy.REQUIRED) ) assertEquals(FieldState(cvc, Validation.Invalid(R.string.checkout_security_code_not_valid)), actual) } @@ -514,8 +494,7 @@ internal class CardValidationUtilsTest { val cvc = "" val actual = CardValidationUtils.validateSecurityCode( cvc, - getDetectedCardType(cvcPolicy = Brand.FieldPolicy.OPTIONAL), - cvcUIState = InputFieldUIState.OPTIONAL + getDetectedCardType(cvcPolicy = Brand.FieldPolicy.OPTIONAL) ) assertEquals(FieldState(cvc, Validation.Valid), actual) } @@ -525,8 +504,7 @@ internal class CardValidationUtilsTest { val cvc = "" val actual = CardValidationUtils.validateSecurityCode( cvc, - getDetectedCardType(cvcPolicy = Brand.FieldPolicy.HIDDEN), - cvcUIState = InputFieldUIState.HIDDEN + getDetectedCardType(cvcPolicy = Brand.FieldPolicy.HIDDEN) ) assertEquals(FieldState(cvc, Validation.Valid), actual) } diff --git a/test-core/src/main/java/com/adyen/checkout/test/extensions/ViewModelExtensions.kt b/test-core/src/main/java/com/adyen/checkout/test/extensions/ViewModelExtensions.kt index 6897897300..032905614a 100644 --- a/test-core/src/main/java/com/adyen/checkout/test/extensions/ViewModelExtensions.kt +++ b/test-core/src/main/java/com/adyen/checkout/test/extensions/ViewModelExtensions.kt @@ -18,11 +18,7 @@ import androidx.lifecycle.ViewModel */ @RestrictTo(RestrictTo.Scope.TESTS) fun ViewModel.invokeOnCleared() { - var clazz = javaClass as Class - while (clazz.declaredMethods.toList().none { it.name == "onCleared" }) { - clazz = clazz.superclass as Class - } - with(clazz.getDeclaredMethod("onCleared")) { + with(javaClass.getDeclaredMethod("onCleared")) { isAccessible = true invoke(this@invokeOnCleared) }