From e3be9303ea86cae060739fcd4ef02e03ff8d39f5 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Tue, 14 May 2024 18:47:29 +0200 Subject: [PATCH 01/44] Add play services coroutines COAND-855 --- googlepay/build.gradle | 1 + gradle/libs.versions.toml | 1 + gradle/verification-metadata.xml | 8 ++++++++ 3 files changed, 10 insertions(+) diff --git a/googlepay/build.gradle b/googlepay/build.gradle index ecd8f4e789..c4596240a1 100644 --- a/googlepay/build.gradle +++ b/googlepay/build.gradle @@ -40,6 +40,7 @@ dependencies { api project(':sessions-core') // Dependencies + implementation libs.google.pay.play.services.coroutines api libs.google.pay.play.services.wallet //Tests diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index af4dad09be..510322fd85 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -108,6 +108,7 @@ compose-viewmodel = { group = "androidx.lifecycle", name = "lifecycle-viewmodel- # this unused dependency is needed so that renovate can update the compose compiler version. More info in: https://github.com/renovatebot/renovate/issues/18354 compose-compiler = { module = "androidx.compose.compiler:compiler", version.ref = "compose-compiler" } google-pay-compose-button = { group = "com.google.pay.button", name = "compose-pay-button", version.ref = "google-pay-compose-button" } +google-pay-play-services-coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-play-services", version.ref = "coroutines" } google-pay-play-services-wallet = { group = "com.google.android.gms", name = "play-services-wallet", version.ref = "play-services-wallet" } hilt = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" } diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index c57a99c478..f294f1de1e 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -15114,6 +15114,14 @@ + + + + + + + + From 555b6708bce91f6ab37e5e3004e9c94955222495 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 15 May 2024 16:15:27 +0200 Subject: [PATCH 02/44] Setup wrapper view and fragment for Google Pay COAND-855 --- googlepay/build.gradle | 4 ++ .../internal/ui/DefaultGooglePayDelegate.kt | 3 ++ .../internal/ui/GooglePayDelegate.kt | 5 +- .../internal/ui/GooglePayFragment.kt | 40 +++++++++++++++ .../googlepay/internal/ui/GooglePayView.kt | 51 +++++++++++++++++++ .../internal/ui/GooglePayViewProvider.kt | 38 ++++++++++++++ .../main/res/layout/fragment_google_pay.xml | 17 +++++++ .../src/main/res/layout/view_google_pay.xml | 23 +++++++++ 8 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt create mode 100644 googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayView.kt create mode 100644 googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt create mode 100644 googlepay/src/main/res/layout/fragment_google_pay.xml create mode 100644 googlepay/src/main/res/layout/view_google_pay.xml diff --git a/googlepay/build.gradle b/googlepay/build.gradle index c4596240a1..60ca3a4987 100644 --- a/googlepay/build.gradle +++ b/googlepay/build.gradle @@ -30,6 +30,10 @@ android { testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' consumerProguardFiles "consumer-rules.pro" } + + buildFeatures { + viewBinding true + } } dependencies { diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index af2b87eb71..ebc8c5f085 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -31,6 +31,7 @@ import com.adyen.checkout.googlepay.GooglePayComponentState import com.adyen.checkout.googlepay.internal.data.model.GooglePayPaymentMethodModel import com.adyen.checkout.googlepay.internal.ui.model.GooglePayComponentParams import com.adyen.checkout.googlepay.internal.util.GooglePayUtils +import com.adyen.checkout.ui.core.internal.ui.ComponentViewType import com.google.android.gms.wallet.AutoResolveHelper import com.google.android.gms.wallet.PaymentData import com.google.android.gms.wallet.Wallet @@ -60,6 +61,8 @@ internal class DefaultGooglePayDelegate( private val submitChannel: Channel = bufferedChannel() override val submitFlow: Flow = submitChannel.receiveAsFlow() + override val viewFlow: Flow = MutableStateFlow(GooglePayComponentViewType) + override fun initialize(coroutineScope: CoroutineScope) { initializeAnalytics(coroutineScope) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt index 2d393b6d19..0ef114151e 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt @@ -14,9 +14,12 @@ import com.adyen.checkout.components.core.internal.ui.PaymentComponentDelegate import com.adyen.checkout.core.exception.CheckoutException import com.adyen.checkout.googlepay.GooglePayButtonParameters import com.adyen.checkout.googlepay.GooglePayComponentState +import com.adyen.checkout.ui.core.internal.ui.ViewProvidingDelegate import kotlinx.coroutines.flow.Flow -internal interface GooglePayDelegate : PaymentComponentDelegate { +internal interface GooglePayDelegate : + PaymentComponentDelegate, + ViewProvidingDelegate { val componentStateFlow: Flow diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt new file mode 100644 index 0000000000..478ad167aa --- /dev/null +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 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/5/2024. + */ + +package com.adyen.checkout.googlepay.internal.ui + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.adyen.checkout.googlepay.databinding.FragmentGooglePayBinding + +internal class GooglePayFragment : Fragment() { + + private var _binding: FragmentGooglePayBinding? = null + private val binding: FragmentGooglePayBinding get() = requireNotNull(_binding) + + private var _delegate: GooglePayDelegate? = null + private val delegate: GooglePayDelegate get() = requireNotNull(_delegate) + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + _binding = FragmentGooglePayBinding.inflate(inflater, container, false) + return binding.root + } + + fun initialize(delegate: GooglePayDelegate) { + _delegate = delegate + } + + override fun onDestroyView() { + _delegate = null + _binding = null + super.onDestroyView() + } +} diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayView.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayView.kt new file mode 100644 index 0000000000..b9972cabe9 --- /dev/null +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayView.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2024 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/5/2024. + */ + +package com.adyen.checkout.googlepay.internal.ui + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.FrameLayout +import com.adyen.checkout.components.core.internal.ui.ComponentDelegate +import com.adyen.checkout.googlepay.databinding.ViewGooglePayBinding +import com.adyen.checkout.ui.core.internal.ui.ComponentView +import kotlinx.coroutines.CoroutineScope + +internal class GooglePayView internal constructor( + layoutInflater: LayoutInflater, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : FrameLayout(layoutInflater.context, attrs, defStyleAttr), ComponentView { + + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 + ) : this(LayoutInflater.from(context), attrs, defStyleAttr) + + private var binding = ViewGooglePayBinding.inflate(layoutInflater, this) + + private var delegate: GooglePayDelegate? = null + + override fun initView(delegate: ComponentDelegate, coroutineScope: CoroutineScope, localizedContext: Context) { + require(delegate is GooglePayDelegate) { "Unsupported delegate type" } + this.delegate = delegate + initializeFragment(delegate) + } + + private fun initializeFragment(delegate: GooglePayDelegate) { + binding.fragmentContainer.getFragment()?.initialize(delegate) + } + + override fun highlightValidationErrors() = Unit + + override fun getView(): View = this +} diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt new file mode 100644 index 0000000000..1ca8a11d61 --- /dev/null +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024 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/5/2024. + */ + +package com.adyen.checkout.googlepay.internal.ui + +import android.content.Context +import android.view.LayoutInflater +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 + +internal object GooglePayViewProvider : ViewProvider { + + override fun getView( + viewType: ComponentViewType, + context: Context, + ): ComponentView = when (viewType) { + GooglePayComponentViewType -> GooglePayView(context) + else -> throw IllegalArgumentException("Unsupported view type") + } + + override fun getView( + viewType: ComponentViewType, + layoutInflater: LayoutInflater + ): ComponentView = when (viewType) { + GooglePayComponentViewType -> GooglePayView(layoutInflater) + else -> throw IllegalArgumentException("Unsupported view type") + } +} + +internal object GooglePayComponentViewType : ComponentViewType { + override val viewProvider: ViewProvider = GooglePayViewProvider +} diff --git a/googlepay/src/main/res/layout/fragment_google_pay.xml b/googlepay/src/main/res/layout/fragment_google_pay.xml new file mode 100644 index 0000000000..1e20bfdcc9 --- /dev/null +++ b/googlepay/src/main/res/layout/fragment_google_pay.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/googlepay/src/main/res/layout/view_google_pay.xml b/googlepay/src/main/res/layout/view_google_pay.xml new file mode 100644 index 0000000000..ce06d9bc88 --- /dev/null +++ b/googlepay/src/main/res/layout/view_google_pay.xml @@ -0,0 +1,23 @@ + + + + + + + + + From d36f0b56741ba6ac21c2affdd69c06f0c194e19b Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 15 May 2024 17:20:33 +0200 Subject: [PATCH 03/44] Implement new Google Pay flow COAND-855 --- .../checkout/googlepay/GooglePayComponent.kt | 14 +++- .../provider/GooglePayComponentProvider.kt | 2 + .../internal/ui/DefaultGooglePayDelegate.kt | 65 +++++++++++++++++++ .../internal/ui/GooglePayDelegate.kt | 10 +++ .../internal/ui/GooglePayFragment.kt | 13 ++-- .../googlepay/internal/util/TaskExtensions.kt | 39 +++++++++++ 6 files changed, 138 insertions(+), 5 deletions(-) create mode 100644 googlepay/src/main/java/com/adyen/checkout/googlepay/internal/util/TaskExtensions.kt diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt index f484ff2b2b..dc14b90952 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt @@ -28,6 +28,7 @@ import com.adyen.checkout.googlepay.internal.provider.GooglePayComponentProvider import com.adyen.checkout.googlepay.internal.ui.GooglePayDelegate 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 /** @@ -47,7 +48,11 @@ class GooglePayComponent internal constructor( override val delegate: ComponentDelegate get() = actionHandlingComponent.activeDelegate - override val viewFlow: Flow = genericActionDelegate.viewFlow + override val viewFlow: Flow = mergeViewFlows( + viewModelScope, + googlePayDelegate.viewFlow, + genericActionDelegate.viewFlow, + ) init { googlePayDelegate.initialize(viewModelScope) @@ -78,6 +83,13 @@ class GooglePayComponent internal constructor( googlePayDelegate.startGooglePayScreen(activity, requestCode) } + /** + * Start the GooglePay screen. + */ + fun startGooglePayScreen() { + googlePayDelegate.startGooglePayScreen() + } + /** * Returns some of the parameters required to initialize the [Google Pay button](https://docs.adyen.com/payment-methods/google-pay/android-component/#2-show-the-google-pay-button). */ diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt index be68f24472..e6e081c479 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt @@ -114,6 +114,7 @@ constructor( order = order, componentParams = componentParams, analyticsManager = analyticsManager, + application = application, ) val genericActionDelegate = @@ -201,6 +202,7 @@ constructor( order = checkoutSession.order, componentParams = componentParams, analyticsManager = analyticsManager, + application = application, ) val genericActionDelegate = diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index ebc8c5f085..16ffbf1b42 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -9,7 +9,9 @@ package com.adyen.checkout.googlepay.internal.ui import android.app.Activity +import android.app.Application import android.content.Intent +import androidx.activity.result.ActivityResultLauncher import androidx.annotation.VisibleForTesting import androidx.lifecycle.LifecycleOwner import com.adyen.checkout.components.core.OrderRequest @@ -31,10 +33,14 @@ import com.adyen.checkout.googlepay.GooglePayComponentState import com.adyen.checkout.googlepay.internal.data.model.GooglePayPaymentMethodModel import com.adyen.checkout.googlepay.internal.ui.model.GooglePayComponentParams import com.adyen.checkout.googlepay.internal.util.GooglePayUtils +import com.adyen.checkout.googlepay.internal.util.awaitTask import com.adyen.checkout.ui.core.internal.ui.ComponentViewType +import com.google.android.gms.common.api.CommonStatusCodes +import com.google.android.gms.tasks.Task import com.google.android.gms.wallet.AutoResolveHelper import com.google.android.gms.wallet.PaymentData import com.google.android.gms.wallet.Wallet +import com.google.android.gms.wallet.contract.ApiTaskResult import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow @@ -42,6 +48,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.launch @Suppress("TooManyFunctions") internal class DefaultGooglePayDelegate( @@ -50,6 +57,7 @@ internal class DefaultGooglePayDelegate( private val order: OrderRequest?, override val componentParams: GooglePayComponentParams, private val analyticsManager: AnalyticsManager, + private val application: Application, ) : GooglePayDelegate { private val _componentStateFlow = MutableStateFlow(createComponentState()) @@ -63,7 +71,14 @@ internal class DefaultGooglePayDelegate( override val viewFlow: Flow = MutableStateFlow(GooglePayComponentViewType) + private var _coroutineScope: CoroutineScope? = null + private val coroutineScope: CoroutineScope get() = requireNotNull(_coroutineScope) + + private lateinit var paymentDataLauncher: ActivityResultLauncher> + override fun initialize(coroutineScope: CoroutineScope) { + _coroutineScope = coroutineScope + initializeAnalytics(coroutineScope) componentStateFlow.onEach { @@ -79,6 +94,10 @@ internal class DefaultGooglePayDelegate( analyticsManager.trackEvent(event) } + override fun setPaymentDataLauncher(paymentDataLauncher: ActivityResultLauncher>) { + this.paymentDataLauncher = paymentDataLauncher + } + private fun onState(state: GooglePayComponentState) { if (state.isValid) { val event = GenericEvents.submit(paymentMethod.type.orEmpty()) @@ -147,6 +166,52 @@ internal class DefaultGooglePayDelegate( AutoResolveHelper.resolveTask(paymentsClient.loadPaymentData(paymentDataRequest), activity, requestCode) } + override fun startGooglePayScreen() { + adyenLog(AdyenLogLevel.DEBUG) { "startGooglePayScreen" } + val paymentsClient = Wallet.getPaymentsClient(application, GooglePayUtils.createWalletOptions(componentParams)) + val paymentDataRequest = GooglePayUtils.createPaymentDataRequest(componentParams) + val paymentDataTask = paymentsClient.loadPaymentData(paymentDataRequest) + coroutineScope.launch { + paymentDataLauncher.launch(paymentDataTask.awaitTask()) + } + } + + override fun handlePaymentResult(paymentDataTaskResult: ApiTaskResult) { + when (val statusCode = paymentDataTaskResult.status.statusCode) { + CommonStatusCodes.SUCCESS -> { + val paymentData = paymentDataTaskResult.result + if (paymentData == null) { + adyenLog(AdyenLogLevel.ERROR) { "Result data is null" } + exceptionChannel.trySend(ComponentException("GooglePay encountered an unexpected error")) + return + } + adyenLog(AdyenLogLevel.INFO) { "GooglePay payment result successful" } + updateComponentState(paymentData) + } + + CommonStatusCodes.CANCELED -> { + adyenLog(AdyenLogLevel.INFO) { "GooglePay payment canceled" } + exceptionChannel.trySend(ComponentException("GooglePay payment canceled")) + } + + AutoResolveHelper.RESULT_ERROR -> { + val statusMessage: String = paymentDataTaskResult.status.statusMessage?.let { ": $it" }.orEmpty() + adyenLog(AdyenLogLevel.ERROR) { "GooglePay encountered an error$statusMessage" } + exceptionChannel.trySend(ComponentException("GooglePay encountered an error$statusMessage")) + } + + CommonStatusCodes.INTERNAL_ERROR -> { + adyenLog(AdyenLogLevel.ERROR) { "GooglePay encountered an internal error" } + exceptionChannel.trySend(ComponentException("GooglePay encountered an internal error")) + } + + else -> { + adyenLog(AdyenLogLevel.ERROR) { "GooglePay encountered an unexpected error, statusCode: $statusCode" } + exceptionChannel.trySend(ComponentException("GooglePay encountered an unexpected error")) + } + } + } + override fun handleActivityResult(resultCode: Int, data: Intent?) { adyenLog(AdyenLogLevel.DEBUG) { "handleActivityResult" } when (resultCode) { diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt index 0ef114151e..0616e26da8 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt @@ -10,11 +10,15 @@ package com.adyen.checkout.googlepay.internal.ui import android.app.Activity import android.content.Intent +import androidx.activity.result.ActivityResultLauncher import com.adyen.checkout.components.core.internal.ui.PaymentComponentDelegate import com.adyen.checkout.core.exception.CheckoutException import com.adyen.checkout.googlepay.GooglePayButtonParameters import com.adyen.checkout.googlepay.GooglePayComponentState import com.adyen.checkout.ui.core.internal.ui.ViewProvidingDelegate +import com.google.android.gms.tasks.Task +import com.google.android.gms.wallet.PaymentData +import com.google.android.gms.wallet.contract.ApiTaskResult import kotlinx.coroutines.flow.Flow internal interface GooglePayDelegate : @@ -27,7 +31,13 @@ internal interface GooglePayDelegate : fun startGooglePayScreen(activity: Activity, requestCode: Int) + fun startGooglePayScreen() + fun handleActivityResult(resultCode: Int, data: Intent?) fun getGooglePayButtonParameters(): GooglePayButtonParameters + + fun handlePaymentResult(paymentDataTaskResult: ApiTaskResult) + + fun setPaymentDataLauncher(paymentDataLauncher: ActivityResultLauncher>) } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt index 478ad167aa..538df961c9 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt @@ -14,14 +14,18 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import com.adyen.checkout.googlepay.databinding.FragmentGooglePayBinding +import com.google.android.gms.wallet.contract.TaskResultContracts internal class GooglePayFragment : Fragment() { private var _binding: FragmentGooglePayBinding? = null private val binding: FragmentGooglePayBinding get() = requireNotNull(_binding) - private var _delegate: GooglePayDelegate? = null - private val delegate: GooglePayDelegate get() = requireNotNull(_delegate) + private var delegate: GooglePayDelegate? = null + + private val googlePayLauncher = registerForActivityResult(TaskResultContracts.GetPaymentDataResult()) { + delegate?.handlePaymentResult(it) + } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = FragmentGooglePayBinding.inflate(inflater, container, false) @@ -29,11 +33,12 @@ internal class GooglePayFragment : Fragment() { } fun initialize(delegate: GooglePayDelegate) { - _delegate = delegate + delegate.setPaymentDataLauncher(googlePayLauncher) + this.delegate = delegate } override fun onDestroyView() { - _delegate = null + delegate = null _binding = null super.onDestroyView() } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/util/TaskExtensions.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/util/TaskExtensions.kt new file mode 100644 index 0000000000..d2a843f58d --- /dev/null +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/util/TaskExtensions.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 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/5/2024. + */ + +package com.adyen.checkout.googlepay.internal.util + +import com.google.android.gms.tasks.CancellationTokenSource +import com.google.android.gms.tasks.Task +import kotlinx.coroutines.suspendCancellableCoroutine +import java.util.concurrent.Executor +import kotlin.coroutines.resume + +internal suspend fun Task.awaitTask(cancellationTokenSource: CancellationTokenSource? = null): Task { + return if (isComplete) { + this + } else { + suspendCancellableCoroutine { cont -> + // Run the callback directly to avoid unnecessarily scheduling on the main thread. + addOnCompleteListener(DirectExecutor, cont::resume) + + cancellationTokenSource?.let { cancellationSource -> + cont.invokeOnCancellation { cancellationSource.cancel() } + } + } + } +} + +/** + * An [Executor] that just directly executes the [Runnable]. + */ +private object DirectExecutor : Executor { + override fun execute(runnable: Runnable) { + runnable.run() + } +} From 1044631da25685e63f2137b4957cee9fca0234ed Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 15 May 2024 17:24:55 +0200 Subject: [PATCH 04/44] Implement new flow in examples COAND-855 --- .../ui/googlepay/GooglePayActivityResult.kt | 18 ---------------- .../example/ui/googlepay/GooglePayFragment.kt | 12 +---------- .../compose/SessionsGooglePayActivity.kt | 7 ------- .../compose/SessionsGooglePayScreen.kt | 21 +------------------ .../compose/SessionsGooglePayState.kt | 2 -- .../compose/SessionsGooglePayViewModel.kt | 10 --------- .../checkout/example/ui/main/MainActivity.kt | 10 --------- 7 files changed, 2 insertions(+), 78 deletions(-) delete mode 100644 example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayActivityResult.kt diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayActivityResult.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayActivityResult.kt deleted file mode 100644 index 019178c53e..0000000000 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayActivityResult.kt +++ /dev/null @@ -1,18 +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 josephj on 13/12/2023. - */ - -package com.adyen.checkout.example.ui.googlepay - -import android.content.Intent -import com.adyen.checkout.example.ui.googlepay.compose.SessionsGooglePayComponentData - -internal data class GooglePayActivityResult( - val componentData: SessionsGooglePayComponentData, - val resultCode: Int, - val data: Intent?, -) diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayFragment.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayFragment.kt index 91609d18a1..4f8feba409 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayFragment.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayFragment.kt @@ -8,7 +8,6 @@ package com.adyen.checkout.example.ui.googlepay -import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -148,16 +147,7 @@ class GooglePayFragment : BottomSheetDialogFragment() { binding.googlePayButton.initialize(buttonOptions) binding.googlePayButton.setOnClickListener { - googlePayComponent?.startGooglePayScreen(requireActivity(), ACTIVITY_RESULT_CODE) - } - } - - // It is required to use onActivityResult with the Google Pay library (AutoResolveHelper). - @Deprecated("Deprecated in Java") - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - if (requestCode == ACTIVITY_RESULT_CODE) { - googlePayComponent?.handleActivityResult(resultCode, data) + googlePayComponent?.startGooglePayScreen() } } diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayActivity.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayActivity.kt index 2ded1978c8..75e92fe6c6 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayActivity.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayActivity.kt @@ -59,13 +59,6 @@ class SessionsGooglePayActivity : AppCompatActivity() { } } - // It is required to use onActivityResult with the Google Pay library (AutoResolveHelper). - @Deprecated("Deprecated in Java") - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - sessionsGooglePayViewModel.onActivityResult(requestCode, resultCode, data) - } - companion object { internal const val RETURN_URL_EXTRA = "RETURN_URL_EXTRA" } diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt index 41a3e65561..53e9f9147f 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt @@ -36,7 +36,6 @@ import androidx.compose.ui.platform.LocalContext import com.adyen.checkout.components.compose.AdyenComponent import com.adyen.checkout.components.compose.get import com.adyen.checkout.example.ui.compose.ResultContent -import com.adyen.checkout.example.ui.googlepay.GooglePayActivityResult import com.adyen.checkout.googlepay.GooglePayComponent import com.google.pay.button.ButtonTheme import com.google.pay.button.ButtonType @@ -71,7 +70,6 @@ internal fun SessionsGooglePayScreen( with(googlePayState) { HandleStartGooglePay(startGooglePay, viewModel::onGooglePayStarted) - HandleActivityResult(activityResultToHandle, viewModel::onActivityResultHandled) HandleAction(actionToHandle, viewModel::onActionConsumed) HandleNewIntent(intentToHandle, viewModel::onNewIntentHandled) } @@ -126,30 +124,13 @@ private fun HandleStartGooglePay( onGooglePayStarted: () -> Unit ) { if (startGooglePayData == null) return - val activity = LocalContext.current as Activity val googlePayComponent = getGooglePayComponent(componentData = startGooglePayData.componentData) LaunchedEffect(startGooglePayData) { - googlePayComponent.startGooglePayScreen( - activity, - startGooglePayData.requestCode, - ) + googlePayComponent.startGooglePayScreen() onGooglePayStarted() } } -@Composable -private fun HandleActivityResult( - activityResultToHandle: GooglePayActivityResult?, - onActivityResultHandled: () -> Unit -) { - if (activityResultToHandle == null) return - val googlePayComponent = getGooglePayComponent(componentData = activityResultToHandle.componentData) - LaunchedEffect(activityResultToHandle) { - googlePayComponent.handleActivityResult(activityResultToHandle.resultCode, activityResultToHandle.data) - onActivityResultHandled() - } -} - @Composable private fun HandleAction( actionToHandle: SessionsGooglePayAction?, diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayState.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayState.kt index af6755c00c..94cbb465f6 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayState.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayState.kt @@ -9,13 +9,11 @@ package com.adyen.checkout.example.ui.googlepay.compose import androidx.compose.runtime.Immutable -import com.adyen.checkout.example.ui.googlepay.GooglePayActivityResult @Immutable internal data class SessionsGooglePayState( val uiState: SessionsGooglePayUIState, val startGooglePay: SessionsStartGooglePayData? = null, - val activityResultToHandle: GooglePayActivityResult? = null, val actionToHandle: SessionsGooglePayAction? = null, val intentToHandle: SessionsGooglePayIntent? = null, ) diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayViewModel.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayViewModel.kt index 3939f75ef3..a0f55c55a7 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayViewModel.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayViewModel.kt @@ -28,7 +28,6 @@ import com.adyen.checkout.example.service.getSessionRequest import com.adyen.checkout.example.service.getSettingsInstallmentOptionsMode import com.adyen.checkout.example.ui.compose.ResultState import com.adyen.checkout.example.ui.configuration.CheckoutConfigurationProvider -import com.adyen.checkout.example.ui.googlepay.GooglePayActivityResult import com.adyen.checkout.googlepay.GooglePayComponent import com.adyen.checkout.googlepay.GooglePayComponentState import com.adyen.checkout.sessions.core.CheckoutSession @@ -199,15 +198,6 @@ internal class SessionsGooglePayViewModel @Inject constructor( updateState { it.copy(actionToHandle = null) } } - fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode != ACTIVITY_RESULT_CODE) return - updateState { it.copy(activityResultToHandle = GooglePayActivityResult(componentData, resultCode, data)) } - } - - fun onActivityResultHandled() { - updateState { it.copy(activityResultToHandle = null) } - } - fun onNewIntent(intent: Intent) { updateState { it.copy(intentToHandle = SessionsGooglePayIntent(componentData, intent)) } } diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainActivity.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainActivity.kt index 37640c65fd..d4d6101634 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainActivity.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainActivity.kt @@ -223,16 +223,6 @@ class MainActivity : AppCompatActivity() { binding.switchSessions.isChecked = isChecked } - // It is required to use onActivityResult with the Google Pay library (AutoResolveHelper). - @Deprecated("Deprecated in Java") - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - if (requestCode == GooglePayFragment.ACTIVITY_RESULT_CODE) { - (supportFragmentManager.findFragmentByTag(GooglePayFragment.TAG) as? GooglePayFragment) - ?.onActivityResult(requestCode, resultCode, data) - } - } - override fun onDestroy() { super.onDestroy() componentItemAdapter = null From bf5d79f308891a24917fb04235f31c7f407d645f Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 15 May 2024 17:55:31 +0200 Subject: [PATCH 05/44] Improve compose example COAND-855 --- .../compose/SessionsGooglePayActivity.kt | 7 +- .../compose/SessionsGooglePayEvents.kt | 16 +++ .../compose/SessionsGooglePayScreen.kt | 104 ++++++------------ .../compose/SessionsGooglePayState.kt | 19 ++-- .../compose/SessionsGooglePayViewModel.kt | 44 ++------ 5 files changed, 81 insertions(+), 109 deletions(-) create mode 100644 example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayEvents.kt diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayActivity.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayActivity.kt index 75e92fe6c6..83f46fcd62 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayActivity.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayActivity.kt @@ -13,6 +13,8 @@ import android.os.Bundle import androidx.activity.compose.setContent import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.core.view.WindowCompat import com.adyen.checkout.example.ui.theme.ExampleTheme import com.adyen.checkout.example.ui.theme.UIThemeRepository @@ -39,12 +41,15 @@ class SessionsGooglePayActivity : AppCompatActivity() { intent = (intent ?: Intent()).putExtra(RETURN_URL_EXTRA, returnUrl) setContent { + val googlePayState by sessionsGooglePayViewModel.googlePayState.collectAsState() + val eventsState by sessionsGooglePayViewModel.stateEvents.collectAsState() val isDarkTheme = uiThemeRepository.isDarkTheme() ExampleTheme(isDarkTheme) { SessionsGooglePayScreen( useDarkTheme = isDarkTheme, onBackPressed = { onBackPressedDispatcher.onBackPressed() }, - viewModel = sessionsGooglePayViewModel, + googlePayState = googlePayState, + eventsState = eventsState, ) } } diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayEvents.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayEvents.kt new file mode 100644 index 0000000000..45df475e39 --- /dev/null +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayEvents.kt @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2024 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/5/2024. + */ + +package com.adyen.checkout.example.ui.googlepay.compose + +internal abstract class SessionsGooglePayEvents internal constructor() { + object None : SessionsGooglePayEvents() + class ComponentData(val data: SessionsGooglePayComponentData) : SessionsGooglePayEvents() + class Action(val action: com.adyen.checkout.components.core.action.Action) : SessionsGooglePayEvents() + class Intent(val intent: android.content.Intent) : SessionsGooglePayEvents() +} diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt index 53e9f9147f..9f1b59d8ae 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt @@ -18,7 +18,7 @@ import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -28,8 +28,6 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -44,8 +42,9 @@ import com.google.pay.button.PayButton @Composable internal fun SessionsGooglePayScreen( useDarkTheme: Boolean, + googlePayState: SessionsGooglePayState, + eventsState: SessionsGooglePayEvents, onBackPressed: () -> Unit, - viewModel: SessionsGooglePayViewModel, ) { Scaffold( modifier = Modifier.windowInsetsPadding(WindowInsets.ime), @@ -54,25 +53,18 @@ internal fun SessionsGooglePayScreen( title = { Text(text = "Google Pay with sessions") }, navigationIcon = { IconButton(onClick = onBackPressed) { - Icon(Icons.Filled.ArrowBack, contentDescription = "Back") + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") } }, ) }, ) { innerPadding -> - val googlePayState by viewModel.googlePayState.collectAsState() SessionsGooglePayContent( googlePayState = googlePayState, - onButtonClicked = viewModel::onButtonClicked, + googlePayEvents = eventsState, useDarkTheme = useDarkTheme, modifier = Modifier.padding(innerPadding), ) - - with(googlePayState) { - HandleStartGooglePay(startGooglePay, viewModel::onGooglePayStarted) - HandleAction(actionToHandle, viewModel::onActionConsumed) - HandleNewIntent(intentToHandle, viewModel::onNewIntentHandled) - } } } @@ -80,84 +72,58 @@ internal fun SessionsGooglePayScreen( @Composable private fun SessionsGooglePayContent( googlePayState: SessionsGooglePayState, - onButtonClicked: () -> Unit, + googlePayEvents: SessionsGooglePayEvents, useDarkTheme: Boolean, modifier: Modifier = Modifier, ) { + val activity = LocalContext.current as Activity + lateinit var googlePayComponent: GooglePayComponent + + when (googlePayEvents) { + is SessionsGooglePayEvents.ComponentData -> { + googlePayComponent = getGooglePayComponent(componentData = googlePayEvents.data) + } + + is SessionsGooglePayEvents.Action -> { + LaunchedEffect(googlePayEvents.action) { + googlePayComponent.handleAction(googlePayEvents.action, activity) + } + } + + is SessionsGooglePayEvents.Intent -> { + LaunchedEffect(googlePayEvents.intent) { + googlePayComponent.handleIntent(googlePayEvents.intent) + } + } + } + Box( modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center, ) { - when (val uiState = googlePayState.uiState) { - SessionsGooglePayUIState.Loading -> { + when (googlePayState) { + SessionsGooglePayState.Loading -> { CircularProgressIndicator() } - is SessionsGooglePayUIState.ShowButton -> { - val googlePayComponent = getGooglePayComponent(componentData = uiState.componentData) + is SessionsGooglePayState.ShowButton -> { + AdyenComponent(googlePayComponent) + PayButton( - onClick = onButtonClicked, + onClick = { googlePayComponent.startGooglePayScreen() }, allowedPaymentMethods = googlePayComponent.getGooglePayButtonParameters().allowedPaymentMethods, theme = if (useDarkTheme) ButtonTheme.Light else ButtonTheme.Dark, type = ButtonType.Pay, ) } - is SessionsGooglePayUIState.ShowComponent -> { - val googlePayComponent = getGooglePayComponent(componentData = uiState.componentData) - AdyenComponent( - googlePayComponent, - modifier, - ) - } - - is SessionsGooglePayUIState.FinalResult -> { - ResultContent(uiState.finalResult) + is SessionsGooglePayState.FinalResult -> { + ResultContent(googlePayState.finalResult) } } } } -@Composable -private fun HandleStartGooglePay( - startGooglePayData: SessionsStartGooglePayData?, - onGooglePayStarted: () -> Unit -) { - if (startGooglePayData == null) return - val googlePayComponent = getGooglePayComponent(componentData = startGooglePayData.componentData) - LaunchedEffect(startGooglePayData) { - googlePayComponent.startGooglePayScreen() - onGooglePayStarted() - } -} - -@Composable -private fun HandleAction( - actionToHandle: SessionsGooglePayAction?, - onActionConsumed: () -> Unit -) { - if (actionToHandle == null) return - val activity = LocalContext.current as Activity - val googlePayComponent = getGooglePayComponent(componentData = actionToHandle.componentData) - LaunchedEffect(actionToHandle) { - googlePayComponent.handleAction(actionToHandle.action, activity) - onActionConsumed() - } -} - -@Composable -private fun HandleNewIntent( - intentToHandle: SessionsGooglePayIntent?, - onNewIntentHandled: () -> Unit -) { - if (intentToHandle == null) return - val googlePayComponent = getGooglePayComponent(componentData = intentToHandle.componentData) - LaunchedEffect(intentToHandle) { - googlePayComponent.handleIntent(intentToHandle.intent) - onNewIntentHandled() - } -} - @Composable private fun getGooglePayComponent(componentData: SessionsGooglePayComponentData): GooglePayComponent { return with(componentData) { diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayState.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayState.kt index 94cbb465f6..5c765a1427 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayState.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayState.kt @@ -9,11 +9,16 @@ package com.adyen.checkout.example.ui.googlepay.compose import androidx.compose.runtime.Immutable +import com.adyen.checkout.example.ui.compose.ResultState -@Immutable -internal data class SessionsGooglePayState( - val uiState: SessionsGooglePayUIState, - val startGooglePay: SessionsStartGooglePayData? = null, - val actionToHandle: SessionsGooglePayAction? = null, - val intentToHandle: SessionsGooglePayIntent? = null, -) +internal sealed class SessionsGooglePayState { + + @Immutable + data object Loading : SessionsGooglePayState() + + @Immutable + data object ShowButton : SessionsGooglePayState() + + @Immutable + data class FinalResult(val finalResult: ResultState) : SessionsGooglePayState() +} diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayViewModel.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayViewModel.kt index a0f55c55a7..5d5df697b4 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayViewModel.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayViewModel.kt @@ -59,12 +59,11 @@ internal class SessionsGooglePayViewModel @Inject constructor( private val checkoutConfiguration = checkoutConfigurationProvider.checkoutConfig - private val _googlePayState = MutableStateFlow(SessionsGooglePayState(SessionsGooglePayUIState.Loading)) + private val _googlePayState = MutableStateFlow(SessionsGooglePayState.Loading) val googlePayState: StateFlow = _googlePayState.asStateFlow() - private var _componentData: SessionsGooglePayComponentData? = null - private val componentData: SessionsGooglePayComponentData - get() = requireNotNull(_componentData) { "component data should not be null" } + private val _stateEvents: MutableStateFlow = MutableStateFlow(SessionsGooglePayEvents.None) + val stateEvents: StateFlow = _stateEvents.asStateFlow() init { viewModelScope.launch { fetchSession() } @@ -85,13 +84,14 @@ internal class SessionsGooglePayViewModel @Inject constructor( return@withContext } - _componentData = SessionsGooglePayComponentData( + val componentData = SessionsGooglePayComponentData( checkoutSession, checkoutConfiguration, paymentMethod, this@SessionsGooglePayViewModel, ) + updateEvent { SessionsGooglePayEvents.ComponentData(componentData) } checkGooglePayAvailability(paymentMethod, checkoutConfiguration) } @@ -143,7 +143,7 @@ internal class SessionsGooglePayViewModel @Inject constructor( override fun onAvailabilityResult(isAvailable: Boolean, paymentMethod: PaymentMethod) { viewModelScope.launch { if (isAvailable) { - updateState { it.copy(uiState = SessionsGooglePayUIState.ShowButton(componentData)) } + updateState { SessionsGooglePayState.ShowButton } } else { onError() } @@ -151,7 +151,7 @@ internal class SessionsGooglePayViewModel @Inject constructor( } override fun onAction(action: Action) { - updateState { it.copy(actionToHandle = SessionsGooglePayAction(componentData, action)) } + updateEvent { SessionsGooglePayEvents.Action(action) } } override fun onError(componentError: ComponentError) { @@ -160,9 +160,7 @@ internal class SessionsGooglePayViewModel @Inject constructor( } override fun onFinished(result: SessionPaymentResult) { - updateState { - it.copy(uiState = SessionsGooglePayUIState.FinalResult(getFinalResultState(result))) - } + updateState { SessionsGooglePayState.FinalResult(getFinalResultState(result)) } } private fun getFinalResultState(result: SessionPaymentResult): ResultState = when (result.resultCode) { @@ -174,40 +172,22 @@ internal class SessionsGooglePayViewModel @Inject constructor( } private fun onError() { - updateState { it.copy(uiState = SessionsGooglePayUIState.FinalResult(ResultState.FAILURE)) } + updateState { SessionsGooglePayState.FinalResult(ResultState.FAILURE) } } private fun updateState(block: (SessionsGooglePayState) -> SessionsGooglePayState) { _googlePayState.update(block) } - fun onButtonClicked() { - updateState { - it.copy( - uiState = SessionsGooglePayUIState.ShowComponent(componentData), - startGooglePay = SessionsStartGooglePayData(componentData, ACTIVITY_RESULT_CODE), - ) - } - } - - fun onGooglePayStarted() { - updateState { it.copy(startGooglePay = null) } - } - - fun onActionConsumed() { - updateState { it.copy(actionToHandle = null) } + private fun updateEvent(block: (SessionsGooglePayEvents) -> SessionsGooglePayEvents) { + _stateEvents.update(block) } fun onNewIntent(intent: Intent) { - updateState { it.copy(intentToHandle = SessionsGooglePayIntent(componentData, intent)) } - } - - fun onNewIntentHandled() { - updateState { it.copy(intentToHandle = null) } + updateEvent { SessionsGooglePayEvents.Intent(intent) } } companion object { private val TAG = getLogTag() - private const val ACTIVITY_RESULT_CODE = 1 } } From 335d62fe8969d14a39acf60b02dd70e1cf8e155a Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Mon, 10 Jun 2024 15:48:27 +0200 Subject: [PATCH 06/44] Use new flow in drop-in COAND-855 --- .../dropin/internal/ui/DropInActivity.kt | 16 ------- .../ui/GooglePayComponentDialogFragment.kt | 42 +++++++++++-------- .../dropin/internal/ui/GooglePayViewModel.kt | 14 +++---- .../layout/fragment_google_pay_component.xml | 15 +++++-- .../internal/ui/GooglePayFragment.kt | 2 +- .../googlepay/internal/ui/GooglePayView.kt | 4 +- .../action/internal/ui/TwintActionView.kt | 4 +- 7 files changed, 47 insertions(+), 50 deletions(-) diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInActivity.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInActivity.kt index 609b6b8783..2228a69807 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInActivity.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/DropInActivity.kt @@ -182,22 +182,6 @@ internal class DropInActivity : return baseContext.createLocalizedContext(locale) } - @Deprecated("Deprecated in Java") - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - checkGooglePayActivityResult(requestCode, resultCode, data) - } - - private fun checkGooglePayActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode != GOOGLE_PAY_REQUEST_CODE) return - val fragment = getFragmentByTag(COMPONENT_FRAGMENT_TAG) as? GooglePayComponentDialogFragment - if (fragment == null) { - adyenLog(AdyenLogLevel.ERROR) { "GooglePayComponentDialogFragment is not loaded" } - return - } - fragment.handleActivityResult(resultCode, data) - } - override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) adyenLog(AdyenLogLevel.DEBUG) { "onNewIntent" } 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 e3f2365467..5a8d811be4 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,15 +8,13 @@ package com.adyen.checkout.dropin.internal.ui -import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.viewModels -import androidx.lifecycle.Lifecycle +import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import com.adyen.checkout.components.core.ActionComponentData import com.adyen.checkout.components.core.ComponentCallback import com.adyen.checkout.components.core.ComponentError @@ -24,17 +22,21 @@ import com.adyen.checkout.components.core.PaymentMethod import com.adyen.checkout.core.AdyenLogLevel import com.adyen.checkout.core.exception.CheckoutException import com.adyen.checkout.core.internal.util.adyenLog -import com.adyen.checkout.dropin.R +import com.adyen.checkout.dropin.databinding.FragmentGooglePayComponentBinding import com.adyen.checkout.dropin.internal.provider.getComponentFor import com.adyen.checkout.googlepay.GooglePayComponent import com.adyen.checkout.googlepay.GooglePayComponentState -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach @Suppress("TooManyFunctions") internal class GooglePayComponentDialogFragment : DropInBottomSheetDialogFragment(), ComponentCallback { + private var _binding: FragmentGooglePayComponentBinding? = null + private val binding: FragmentGooglePayComponentBinding get() = requireNotNull(_binding) + private val googlePayViewModel: GooglePayViewModel by viewModels() private lateinit var paymentMethod: PaymentMethod @@ -44,27 +46,30 @@ internal class GooglePayComponentDialogFragment : adyenLog(AdyenLogLevel.DEBUG) { "onCreate" } super.onCreate(savedInstanceState) arguments?.let { + @Suppress("DEPRECATION") paymentMethod = it.getParcelable(PAYMENT_METHOD) ?: throw IllegalArgumentException("Payment method is null") } - - googlePayViewModel.fragmentLoaded() } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { adyenLog(AdyenLogLevel.DEBUG) { "onCreateView" } - return inflater.inflate(R.layout.fragment_google_pay_component, container, false) + _binding = FragmentGooglePayComponentBinding.inflate(inflater, container, false) + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { adyenLog(AdyenLogLevel.DEBUG) { "onViewCreated" } - viewLifecycleOwner.lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - googlePayViewModel.eventsFlow.collect { handleEvent(it) } - } - } - loadComponent() + + binding.componentView.attach(component, viewLifecycleOwner) + + googlePayViewModel.onFragmentLoaded() + + googlePayViewModel.eventsFlow + .onEach(::handleEvent) + .flowWithLifecycle(viewLifecycleOwner.lifecycle) + .launchIn(viewLifecycleOwner.lifecycleScope) } private fun loadComponent() { @@ -102,7 +107,7 @@ internal class GooglePayComponentDialogFragment : private fun handleEvent(event: GooglePayFragmentEvent) { when (event) { is GooglePayFragmentEvent.StartGooglePay -> { - component.startGooglePayScreen(requireActivity(), DropInActivity.GOOGLE_PAY_REQUEST_CODE) + component.startGooglePayScreen() } } } @@ -127,8 +132,9 @@ internal class GooglePayComponentDialogFragment : return true } - fun handleActivityResult(resultCode: Int, data: Intent?) { - component.handleActivityResult(resultCode, data) + override fun onDestroyView() { + _binding = null + super.onDestroyView() } companion object { diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GooglePayViewModel.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GooglePayViewModel.kt index 49e3654a28..27eb23376a 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GooglePayViewModel.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/ui/GooglePayViewModel.kt @@ -10,7 +10,6 @@ package com.adyen.checkout.dropin.internal.ui import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.adyen.checkout.components.core.internal.SavedStateHandleContainer import com.adyen.checkout.components.core.internal.SavedStateHandleProperty import com.adyen.checkout.components.core.internal.util.bufferedChannel @@ -18,7 +17,6 @@ import com.adyen.checkout.core.AdyenLogLevel import com.adyen.checkout.core.internal.util.adyenLog import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.receiveAsFlow -import kotlinx.coroutines.launch internal class GooglePayViewModel( override val savedStateHandle: SavedStateHandle @@ -29,13 +27,11 @@ internal class GooglePayViewModel( private var isGooglePayStarted: Boolean? by SavedStateHandleProperty(IS_GOOGLE_PAY_STARTED) - fun fragmentLoaded() { + fun onFragmentLoaded() { + adyenLog(AdyenLogLevel.DEBUG) { "onFragmentLoaded" } if (isGooglePayStarted == true) return isGooglePayStarted = true - viewModelScope.launch { - adyenLog(AdyenLogLevel.DEBUG) { "Sending start GooglePay event" } - eventChannel.send(GooglePayFragmentEvent.StartGooglePay) - } + eventChannel.trySend(GooglePayFragmentEvent.StartGooglePay) } companion object { @@ -43,6 +39,6 @@ internal class GooglePayViewModel( } } -internal sealed class GooglePayFragmentEvent { - object StartGooglePay : GooglePayFragmentEvent() +internal abstract class GooglePayFragmentEvent { + data object StartGooglePay : GooglePayFragmentEvent() } diff --git a/drop-in/src/main/res/layout/fragment_google_pay_component.xml b/drop-in/src/main/res/layout/fragment_google_pay_component.xml index 07f50189f3..32cee25328 100644 --- a/drop-in/src/main/res/layout/fragment_google_pay_component.xml +++ b/drop-in/src/main/res/layout/fragment_google_pay_component.xml @@ -1,5 +1,4 @@ - - - \ No newline at end of file + android:layout_height="wrap_content" + android:orientation="vertical"> + + + + diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt index 538df961c9..a7259d0a8e 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt @@ -33,8 +33,8 @@ internal class GooglePayFragment : Fragment() { } fun initialize(delegate: GooglePayDelegate) { - delegate.setPaymentDataLauncher(googlePayLauncher) this.delegate = delegate + delegate.setPaymentDataLauncher(googlePayLauncher) } override fun onDestroyView() { diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayView.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayView.kt index b9972cabe9..25256e3a32 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayView.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayView.kt @@ -18,7 +18,9 @@ import com.adyen.checkout.googlepay.databinding.ViewGooglePayBinding import com.adyen.checkout.ui.core.internal.ui.ComponentView import kotlinx.coroutines.CoroutineScope -internal class GooglePayView internal constructor( +internal class GooglePayView +@JvmOverloads +internal constructor( layoutInflater: LayoutInflater, attrs: AttributeSet? = null, defStyleAttr: Int = 0 diff --git a/twint-action/src/main/java/com/adyen/checkout/twint/action/internal/ui/TwintActionView.kt b/twint-action/src/main/java/com/adyen/checkout/twint/action/internal/ui/TwintActionView.kt index ae7b1e483d..405785de68 100644 --- a/twint-action/src/main/java/com/adyen/checkout/twint/action/internal/ui/TwintActionView.kt +++ b/twint-action/src/main/java/com/adyen/checkout/twint/action/internal/ui/TwintActionView.kt @@ -18,7 +18,9 @@ import com.adyen.checkout.twint.action.databinding.ViewTwintActionBinding import com.adyen.checkout.ui.core.internal.ui.ComponentView import kotlinx.coroutines.CoroutineScope -internal class TwintActionView internal constructor( +internal class TwintActionView +@JvmOverloads +internal constructor( layoutInflater: LayoutInflater, attrs: AttributeSet? = null, defStyleAttr: Int = 0 From fbb5d240153f3cc100476285dd529a61c52bdabc Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Mon, 10 Jun 2024 15:55:29 +0200 Subject: [PATCH 07/44] Deprecate old methods COAND-855 --- .../java/com/adyen/checkout/googlepay/GooglePayComponent.kt | 3 +++ .../googlepay/internal/ui/DefaultGooglePayDelegate.kt | 4 ++-- .../adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt index dc14b90952..f984bae3e9 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt @@ -79,7 +79,9 @@ class GooglePayComponent internal constructor( * @param activity The activity to start the screen and later receive the result. * @param requestCode The code that will be returned on the [Activity.onActivityResult] */ + @Deprecated("Deprecated in favor of startGooglePayScreen()", ReplaceWith("startGooglePayScreen()")) fun startGooglePayScreen(activity: Activity, requestCode: Int) { + @Suppress("DEPRECATION") googlePayDelegate.startGooglePayScreen(activity, requestCode) } @@ -104,6 +106,7 @@ class GooglePayComponent internal constructor( * @param resultCode The result code from the [Activity.onActivityResult] * @param data The data intent from the [Activity.onActivityResult] */ + @Deprecated("When using startGooglePayScreen() this method is no longer needed.", ReplaceWith("")) override fun handleActivityResult(resultCode: Int, data: Intent?) { googlePayDelegate.handleActivityResult(resultCode, data) } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index 16ffbf1b42..593c993411 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -157,12 +157,12 @@ internal class DefaultGooglePayDelegate( ) } + // TODO remove in v6 override fun startGooglePayScreen(activity: Activity, requestCode: Int) { adyenLog(AdyenLogLevel.DEBUG) { "startGooglePayScreen" } val paymentsClient = Wallet.getPaymentsClient(activity, GooglePayUtils.createWalletOptions(componentParams)) val paymentDataRequest = GooglePayUtils.createPaymentDataRequest(componentParams) - // TODO this forces us to use the deprecated onActivityResult. Look into alternatives when/if Google provides - // any later. + @Suppress("DEPRECATION") AutoResolveHelper.resolveTask(paymentsClient.loadPaymentData(paymentDataRequest), activity, requestCode) } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt index 0616e26da8..f8cefa3b66 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt @@ -29,6 +29,7 @@ internal interface GooglePayDelegate : val exceptionFlow: Flow + @Deprecated("Deprecated in favor of startGooglePayScreen()", ReplaceWith("startGooglePayScreen()")) fun startGooglePayScreen(activity: Activity, requestCode: Int) fun startGooglePayScreen() From 5fc6b549704bd34f912e6ad1545ee31fa66e0f75 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Thu, 13 Jun 2024 10:19:03 +0200 Subject: [PATCH 08/44] Use a flow to trigger the payment data launcher COAND-855 --- .../internal/ui/DefaultGooglePayDelegate.kt | 12 ++++-------- .../googlepay/internal/ui/GooglePayDelegate.kt | 4 ++-- .../googlepay/internal/ui/GooglePayFragment.kt | 7 ++++++- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index 593c993411..0ab012cecd 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -11,7 +11,6 @@ package com.adyen.checkout.googlepay.internal.ui import android.app.Activity import android.app.Application import android.content.Intent -import androidx.activity.result.ActivityResultLauncher import androidx.annotation.VisibleForTesting import androidx.lifecycle.LifecycleOwner import com.adyen.checkout.components.core.OrderRequest @@ -74,7 +73,8 @@ internal class DefaultGooglePayDelegate( private var _coroutineScope: CoroutineScope? = null private val coroutineScope: CoroutineScope get() = requireNotNull(_coroutineScope) - private lateinit var paymentDataLauncher: ActivityResultLauncher> + private val payEventChannel: Channel> = bufferedChannel() + override val payEventFlow: Flow> = payEventChannel.receiveAsFlow() override fun initialize(coroutineScope: CoroutineScope) { _coroutineScope = coroutineScope @@ -94,10 +94,6 @@ internal class DefaultGooglePayDelegate( analyticsManager.trackEvent(event) } - override fun setPaymentDataLauncher(paymentDataLauncher: ActivityResultLauncher>) { - this.paymentDataLauncher = paymentDataLauncher - } - private fun onState(state: GooglePayComponentState) { if (state.isValid) { val event = GenericEvents.submit(paymentMethod.type.orEmpty()) @@ -157,7 +153,7 @@ internal class DefaultGooglePayDelegate( ) } - // TODO remove in v6 + @Deprecated("Deprecated in favor of startGooglePayScreen()", replaceWith = ReplaceWith("startGooglePayScreen()")) override fun startGooglePayScreen(activity: Activity, requestCode: Int) { adyenLog(AdyenLogLevel.DEBUG) { "startGooglePayScreen" } val paymentsClient = Wallet.getPaymentsClient(activity, GooglePayUtils.createWalletOptions(componentParams)) @@ -172,7 +168,7 @@ internal class DefaultGooglePayDelegate( val paymentDataRequest = GooglePayUtils.createPaymentDataRequest(componentParams) val paymentDataTask = paymentsClient.loadPaymentData(paymentDataRequest) coroutineScope.launch { - paymentDataLauncher.launch(paymentDataTask.awaitTask()) + payEventChannel.send(paymentDataTask.awaitTask()) } } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt index f8cefa3b66..52f72d5053 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt @@ -29,6 +29,8 @@ internal interface GooglePayDelegate : val exceptionFlow: Flow + val payEventFlow: Flow> + @Deprecated("Deprecated in favor of startGooglePayScreen()", ReplaceWith("startGooglePayScreen()")) fun startGooglePayScreen(activity: Activity, requestCode: Int) @@ -39,6 +41,4 @@ internal interface GooglePayDelegate : fun getGooglePayButtonParameters(): GooglePayButtonParameters fun handlePaymentResult(paymentDataTaskResult: ApiTaskResult) - - fun setPaymentDataLauncher(paymentDataLauncher: ActivityResultLauncher>) } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt index a7259d0a8e..1f54d1b571 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt @@ -13,8 +13,11 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import com.adyen.checkout.googlepay.databinding.FragmentGooglePayBinding import com.google.android.gms.wallet.contract.TaskResultContracts +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach internal class GooglePayFragment : Fragment() { @@ -34,7 +37,9 @@ internal class GooglePayFragment : Fragment() { fun initialize(delegate: GooglePayDelegate) { this.delegate = delegate - delegate.setPaymentDataLauncher(googlePayLauncher) + delegate.payEventFlow + .onEach { googlePayLauncher.launch(it) } + .launchIn(viewLifecycleOwner.lifecycleScope) } override fun onDestroyView() { From a046909e7ca5b41057d29063eb87a8af076f64eb Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Thu, 13 Jun 2024 10:52:13 +0200 Subject: [PATCH 09/44] Use submit() to start Google Pay flow COAND-855 --- .../ui/GooglePayComponentDialogFragment.kt | 2 +- .../example/ui/googlepay/GooglePayFragment.kt | 3 +- .../compose/SessionsGooglePayScreen.kt | 2 +- .../checkout/googlepay/GooglePayComponent.kt | 14 ++- .../internal/ui/DefaultGooglePayDelegate.kt | 12 ++- .../internal/ui/GooglePayDelegate.kt | 9 +- .../googlepay/GooglePayComponentTest.kt | 96 ++++++++++++++++--- .../ui/DefaultGooglePayDelegateTest.kt | 2 + 8 files changed, 112 insertions(+), 28 deletions(-) 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 5a8d811be4..8d0abe3465 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 @@ -107,7 +107,7 @@ internal class GooglePayComponentDialogFragment : private fun handleEvent(event: GooglePayFragmentEvent) { when (event) { is GooglePayFragmentEvent.StartGooglePay -> { - component.startGooglePayScreen() + component.submit() } } } diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayFragment.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayFragment.kt index 4f8feba409..b5a84815b6 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayFragment.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayFragment.kt @@ -147,7 +147,7 @@ class GooglePayFragment : BottomSheetDialogFragment() { binding.googlePayButton.initialize(buttonOptions) binding.googlePayButton.setOnClickListener { - googlePayComponent?.startGooglePayScreen() + googlePayComponent?.submit() } } @@ -162,7 +162,6 @@ class GooglePayFragment : BottomSheetDialogFragment() { internal val TAG = getLogTag() internal const val RETURN_URL_EXTRA = "RETURN_URL_EXTRA" - internal const val ACTIVITY_RESULT_CODE = 1 fun show(fragmentManager: FragmentManager) { GooglePayFragment().show(fragmentManager, TAG) diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt index 9f1b59d8ae..51dc35c4db 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt @@ -110,7 +110,7 @@ private fun SessionsGooglePayContent( AdyenComponent(googlePayComponent) PayButton( - onClick = { googlePayComponent.startGooglePayScreen() }, + onClick = { googlePayComponent.submit() }, allowedPaymentMethods = googlePayComponent.getGooglePayButtonParameters().allowedPaymentMethods, theme = if (useDarkTheme) ButtonTheme.Light else ButtonTheme.Dark, type = ButtonType.Pay, diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt index f984bae3e9..810dd88e0e 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt @@ -17,6 +17,7 @@ import com.adyen.checkout.action.core.internal.DefaultActionHandlingComponent import com.adyen.checkout.action.core.internal.ui.GenericActionDelegate import com.adyen.checkout.components.core.PaymentMethodTypes import com.adyen.checkout.components.core.internal.ActivityResultHandlingComponent +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 @@ -26,6 +27,7 @@ import com.adyen.checkout.core.AdyenLogLevel import com.adyen.checkout.core.internal.util.adyenLog import com.adyen.checkout.googlepay.internal.provider.GooglePayComponentProvider import com.adyen.checkout.googlepay.internal.ui.GooglePayDelegate +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 @@ -43,6 +45,7 @@ class GooglePayComponent internal constructor( ) : ViewModel(), PaymentComponent, ActivityResultHandlingComponent, + ButtonComponent, ViewableComponent, ActionHandlingComponent by actionHandlingComponent { @@ -79,7 +82,7 @@ class GooglePayComponent internal constructor( * @param activity The activity to start the screen and later receive the result. * @param requestCode The code that will be returned on the [Activity.onActivityResult] */ - @Deprecated("Deprecated in favor of startGooglePayScreen()", ReplaceWith("startGooglePayScreen()")) + @Deprecated("Deprecated in favor of startGooglePayScreen()", ReplaceWith("submit()")) fun startGooglePayScreen(activity: Activity, requestCode: Int) { @Suppress("DEPRECATION") googlePayDelegate.startGooglePayScreen(activity, requestCode) @@ -88,10 +91,13 @@ class GooglePayComponent internal constructor( /** * Start the GooglePay screen. */ - fun startGooglePayScreen() { - googlePayDelegate.startGooglePayScreen() + override fun submit() { + (delegate as? ButtonDelegate)?.onSubmit() + ?: adyenLog(AdyenLogLevel.ERROR) { "Component is currently not submittable, ignoring." } } + override fun isConfirmationRequired(): Boolean = (delegate as? ButtonDelegate)?.isConfirmationRequired() ?: false + /** * Returns some of the parameters required to initialize the [Google Pay button](https://docs.adyen.com/payment-methods/google-pay/android-component/#2-show-the-google-pay-button). */ @@ -106,7 +112,7 @@ class GooglePayComponent internal constructor( * @param resultCode The result code from the [Activity.onActivityResult] * @param data The data intent from the [Activity.onActivityResult] */ - @Deprecated("When using startGooglePayScreen() this method is no longer needed.", ReplaceWith("")) + @Deprecated("When using submit() this method is no longer needed.", ReplaceWith("")) override fun handleActivityResult(resultCode: Int, data: Intent?) { googlePayDelegate.handleActivityResult(resultCode, data) } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index 0ab012cecd..0c007f3619 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -68,7 +68,8 @@ internal class DefaultGooglePayDelegate( private val submitChannel: Channel = bufferedChannel() override val submitFlow: Flow = submitChannel.receiveAsFlow() - override val viewFlow: Flow = MutableStateFlow(GooglePayComponentViewType) + private val _viewFlow = MutableStateFlow(GooglePayComponentViewType) + override val viewFlow: Flow = _viewFlow private var _coroutineScope: CoroutineScope? = null private val coroutineScope: CoroutineScope get() = requireNotNull(_coroutineScope) @@ -162,7 +163,7 @@ internal class DefaultGooglePayDelegate( AutoResolveHelper.resolveTask(paymentsClient.loadPaymentData(paymentDataRequest), activity, requestCode) } - override fun startGooglePayScreen() { + override fun onSubmit() { adyenLog(AdyenLogLevel.DEBUG) { "startGooglePayScreen" } val paymentsClient = Wallet.getPaymentsClient(application, GooglePayUtils.createWalletOptions(componentParams)) val paymentDataRequest = GooglePayUtils.createPaymentDataRequest(componentParams) @@ -243,6 +244,13 @@ internal class DefaultGooglePayDelegate( return GooglePayButtonParameters(allowedPaymentMethods) } + // Currently, we don't show any button, but we could potentially show the Google Pay button. + override fun isConfirmationRequired(): Boolean = false + + override fun shouldShowSubmitButton(): Boolean = isConfirmationRequired() + + override fun shouldEnableSubmitButton(): Boolean = shouldShowSubmitButton() + override fun getPaymentMethodType(): String { return paymentMethod.type ?: PaymentMethodTypes.UNKNOWN } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt index 52f72d5053..77ee7a403e 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt @@ -10,11 +10,11 @@ package com.adyen.checkout.googlepay.internal.ui import android.app.Activity import android.content.Intent -import androidx.activity.result.ActivityResultLauncher import com.adyen.checkout.components.core.internal.ui.PaymentComponentDelegate import com.adyen.checkout.core.exception.CheckoutException import com.adyen.checkout.googlepay.GooglePayButtonParameters import com.adyen.checkout.googlepay.GooglePayComponentState +import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate import com.adyen.checkout.ui.core.internal.ui.ViewProvidingDelegate import com.google.android.gms.tasks.Task import com.google.android.gms.wallet.PaymentData @@ -23,7 +23,8 @@ import kotlinx.coroutines.flow.Flow internal interface GooglePayDelegate : PaymentComponentDelegate, - ViewProvidingDelegate { + ViewProvidingDelegate, + ButtonDelegate { val componentStateFlow: Flow @@ -31,11 +32,9 @@ internal interface GooglePayDelegate : val payEventFlow: Flow> - @Deprecated("Deprecated in favor of startGooglePayScreen()", ReplaceWith("startGooglePayScreen()")) + @Deprecated("Deprecated in favor of startGooglePayScreen()", ReplaceWith("onSubmit()")) fun startGooglePayScreen(activity: Activity, requestCode: Int) - fun startGooglePayScreen() - fun handleActivityResult(resultCode: Int, data: Intent?) fun getGooglePayButtonParameters(): GooglePayButtonParameters diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/GooglePayComponentTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/GooglePayComponentTest.kt index c4cf36944b..6f59f59e13 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/GooglePayComponentTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/GooglePayComponentTest.kt @@ -12,20 +12,20 @@ import android.app.Activity import android.content.Intent import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.viewModelScope -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.components.core.internal.ComponentEventHandler import com.adyen.checkout.components.core.internal.PaymentComponentEvent +import com.adyen.checkout.googlepay.internal.ui.GooglePayComponentViewType import com.adyen.checkout.googlepay.internal.ui.GooglePayDelegate import com.adyen.checkout.test.LoggingExtension import com.adyen.checkout.test.TestDispatcherExtension import com.adyen.checkout.test.extensions.invokeOnCleared +import com.adyen.checkout.test.extensions.test import com.adyen.checkout.ui.core.internal.ui.TestComponentViewType import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -35,6 +35,7 @@ import org.mockito.kotlin.any import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.mock +import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -50,6 +51,7 @@ internal class GooglePayComponentTest( @BeforeEach fun before() { + whenever(googlePayDelegate.viewFlow) doReturn MutableStateFlow(GooglePayComponentViewType) whenever(genericActionDelegate.viewFlow) doReturn MutableStateFlow(null) component = GooglePayComponent( @@ -96,13 +98,33 @@ internal class GooglePayComponentTest( } @Test - fun `when component is initialized then view flow should bu null`() = runTest { - component.viewFlow.test { - assertNull(awaitItem()) - expectNoEvents() - } + fun `when component is initialized, then view flow should match google pay delegate view flow`() = runTest { + val viewFlow = component.viewFlow.test(testScheduler) + + assertEquals(GooglePayComponentViewType, viewFlow.latestValue) } + @Test + fun `when cash app pay delegate view flow emits a value then component view flow should match that value`() = + runTest { + val delegateViewFlow = MutableStateFlow(TestComponentViewType.VIEW_TYPE_1) + whenever(googlePayDelegate.viewFlow) doReturn delegateViewFlow + component = GooglePayComponent( + googlePayDelegate, + genericActionDelegate, + actionHandlingComponent, + componentEventHandler, + ) + + val testViewFlow = component.viewFlow.test(testScheduler) + + assertEquals(TestComponentViewType.VIEW_TYPE_1, testViewFlow.latestValue) + + delegateViewFlow.emit(TestComponentViewType.VIEW_TYPE_2) + + assertEquals(TestComponentViewType.VIEW_TYPE_2, testViewFlow.latestValue) + } + @Test 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) @@ -114,16 +136,18 @@ internal class GooglePayComponentTest( componentEventHandler = componentEventHandler, ) - component.viewFlow.test { - assertEquals(TestComponentViewType.VIEW_TYPE_1, awaitItem()) + val testViewFlow = component.viewFlow.test(testScheduler) - actionDelegateViewFlow.emit(TestComponentViewType.VIEW_TYPE_2) - assertEquals(TestComponentViewType.VIEW_TYPE_2, awaitItem()) + // this value should match the value of the main delegate and not the action delegate + // and in practice the initial value of the action delegate view flow is always null so it should be ignored + assertEquals(GooglePayComponentViewType, testViewFlow.latestValue) - expectNoEvents() - } + actionDelegateViewFlow.emit(TestComponentViewType.VIEW_TYPE_2) + + assertEquals(TestComponentViewType.VIEW_TYPE_2, testViewFlow.latestValue) } + @Suppress("DEPRECATION") @Test fun `when startGooglePayScreen is called then delegate startGooglePayScreen is called`() { val activity = Activity() @@ -132,6 +156,7 @@ internal class GooglePayComponentTest( verify(googlePayDelegate).startGooglePayScreen(activity, requestCode) } + @Suppress("DEPRECATION") @Test fun `when handleActivityResult is called then delegate handleActivityResult is called`() { val requestCode = 1 @@ -139,4 +164,49 @@ internal class GooglePayComponentTest( component.handleActivityResult(requestCode, intent) verify(googlePayDelegate).handleActivityResult(requestCode, intent) } + + @Test + fun `when submit is called and active delegate is the payment delegate, then delegate onSubmit is called`() { + component = GooglePayComponent( + googlePayDelegate, + genericActionDelegate, + actionHandlingComponent, + componentEventHandler, + ) + whenever(component.delegate).thenReturn(googlePayDelegate) + + component.submit() + + verify(googlePayDelegate).onSubmit() + } + + @Test + fun `when submit is called and active delegate is the action delegate, then delegate onSubmit is not called`() { + component = GooglePayComponent( + googlePayDelegate, + genericActionDelegate, + actionHandlingComponent, + componentEventHandler, + ) + whenever(component.delegate).thenReturn(genericActionDelegate) + + component.submit() + + verify(googlePayDelegate, never()).onSubmit() + } + + @Test + fun `when isConfirmationRequired and delegate is default, then delegate is called`() { + component = GooglePayComponent( + googlePayDelegate, + genericActionDelegate, + actionHandlingComponent, + componentEventHandler, + ) + whenever(component.delegate).thenReturn(googlePayDelegate) + + component.isConfirmationRequired() + + verify(googlePayDelegate).isConfirmationRequired() + } } diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt index b44f4b74be..2e1a0234fa 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt @@ -8,6 +8,7 @@ package com.adyen.checkout.googlepay.internal.ui +import android.app.Application import app.cash.turbine.test import com.adyen.checkout.components.core.Amount import com.adyen.checkout.components.core.CheckoutConfiguration @@ -212,6 +213,7 @@ internal class DefaultGooglePayDelegateTest { componentParams = GooglePayComponentParamsMapper(CommonComponentParamsMapper()) .mapToParams(configuration, Locale.US, null, null, paymentMethod), analyticsManager = analyticsManager, + application = Application(), ) } From ab81227bdf1868f66a70b17a9c48d211af7eb52f Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Thu, 13 Jun 2024 15:38:26 +0200 Subject: [PATCH 10/44] Move PaymentsClient to constructor This makes the code interacting with it testable. COAND-855 --- .../provider/GooglePayComponentProvider.kt | 10 ++- .../internal/ui/DefaultGooglePayDelegate.kt | 5 +- .../googlepay/internal/util/TaskExtensions.kt | 4 +- .../ui/DefaultGooglePayDelegateTest.kt | 75 ++++++++++++++++--- 4 files changed, 76 insertions(+), 18 deletions(-) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt index e6e081c479..b845ba3971 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt @@ -108,13 +108,16 @@ constructor( sessionId = null, ) + val paymentsClient = + Wallet.getPaymentsClient(application, GooglePayUtils.createWalletOptions(componentParams)) + val googlePayDelegate = DefaultGooglePayDelegate( observerRepository = PaymentObserverRepository(), paymentMethod = paymentMethod, order = order, componentParams = componentParams, analyticsManager = analyticsManager, - application = application, + paymentsClient = paymentsClient, ) val genericActionDelegate = @@ -196,13 +199,16 @@ constructor( sessionId = checkoutSession.sessionSetupResponse.id, ) + val paymentsClient = + Wallet.getPaymentsClient(application, GooglePayUtils.createWalletOptions(componentParams)) + val googlePayDelegate = DefaultGooglePayDelegate( observerRepository = PaymentObserverRepository(), paymentMethod = paymentMethod, order = checkoutSession.order, componentParams = componentParams, analyticsManager = analyticsManager, - application = application, + paymentsClient = paymentsClient, ) val genericActionDelegate = diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index 0c007f3619..a4c3cada93 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -9,7 +9,6 @@ package com.adyen.checkout.googlepay.internal.ui import android.app.Activity -import android.app.Application import android.content.Intent import androidx.annotation.VisibleForTesting import androidx.lifecycle.LifecycleOwner @@ -38,6 +37,7 @@ import com.google.android.gms.common.api.CommonStatusCodes import com.google.android.gms.tasks.Task import com.google.android.gms.wallet.AutoResolveHelper import com.google.android.gms.wallet.PaymentData +import com.google.android.gms.wallet.PaymentsClient import com.google.android.gms.wallet.Wallet import com.google.android.gms.wallet.contract.ApiTaskResult import kotlinx.coroutines.CoroutineScope @@ -56,7 +56,7 @@ internal class DefaultGooglePayDelegate( private val order: OrderRequest?, override val componentParams: GooglePayComponentParams, private val analyticsManager: AnalyticsManager, - private val application: Application, + private val paymentsClient: PaymentsClient, ) : GooglePayDelegate { private val _componentStateFlow = MutableStateFlow(createComponentState()) @@ -165,7 +165,6 @@ internal class DefaultGooglePayDelegate( override fun onSubmit() { adyenLog(AdyenLogLevel.DEBUG) { "startGooglePayScreen" } - val paymentsClient = Wallet.getPaymentsClient(application, GooglePayUtils.createWalletOptions(componentParams)) val paymentDataRequest = GooglePayUtils.createPaymentDataRequest(componentParams) val paymentDataTask = paymentsClient.loadPaymentData(paymentDataRequest) coroutineScope.launch { diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/util/TaskExtensions.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/util/TaskExtensions.kt index d2a843f58d..8087675b87 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/util/TaskExtensions.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/util/TaskExtensions.kt @@ -20,7 +20,7 @@ internal suspend fun Task.awaitTask(cancellationTokenSource: Cancellation } else { suspendCancellableCoroutine { cont -> // Run the callback directly to avoid unnecessarily scheduling on the main thread. - addOnCompleteListener(DirectExecutor, cont::resume) + addOnCompleteListener(DirectExecutor(), cont::resume) cancellationTokenSource?.let { cancellationSource -> cont.invokeOnCancellation { cancellationSource.cancel() } @@ -32,7 +32,7 @@ internal suspend fun Task.awaitTask(cancellationTokenSource: Cancellation /** * An [Executor] that just directly executes the [Runnable]. */ -private object DirectExecutor : Executor { +private class DirectExecutor : Executor { override fun execute(runnable: Runnable) { runnable.run() } diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt index 2e1a0234fa..9801cd01b6 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt @@ -8,7 +8,6 @@ package com.adyen.checkout.googlepay.internal.ui -import android.app.Application import app.cash.turbine.test import com.adyen.checkout.components.core.Amount import com.adyen.checkout.components.core.CheckoutConfiguration @@ -20,17 +19,26 @@ import com.adyen.checkout.components.core.internal.analytics.GenericEvents import com.adyen.checkout.components.core.internal.analytics.TestAnalyticsManager import com.adyen.checkout.components.core.internal.ui.model.CommonComponentParamsMapper import com.adyen.checkout.core.Environment +import com.adyen.checkout.core.exception.ComponentException import com.adyen.checkout.googlepay.GooglePayConfiguration import com.adyen.checkout.googlepay.googlePay import com.adyen.checkout.googlepay.internal.ui.model.GooglePayComponentParamsMapper import com.adyen.checkout.googlepay.internal.util.GooglePayUtils +import com.adyen.checkout.test.LoggingExtension +import com.adyen.checkout.test.extensions.test +import com.google.android.gms.common.api.Status +import com.google.android.gms.tasks.Tasks +import com.google.android.gms.wallet.AutoResolveHelper import com.google.android.gms.wallet.PaymentData +import com.google.android.gms.wallet.PaymentsClient +import com.google.android.gms.wallet.contract.ApiTaskResult 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.assertInstanceOf import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach @@ -40,19 +48,22 @@ 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.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.whenever import java.util.Locale @OptIn(ExperimentalCoroutinesApi::class) -@ExtendWith(MockitoExtension::class) -internal class DefaultGooglePayDelegateTest { +@ExtendWith(MockitoExtension::class, LoggingExtension::class) +internal class DefaultGooglePayDelegateTest( + @Mock private val paymentsClient: PaymentsClient, +) { private lateinit var analyticsManager: TestAnalyticsManager private lateinit var delegate: DefaultGooglePayDelegate - private val paymentData: PaymentData - get() = PaymentData.fromJson("{\"paymentMethodData\": {\"tokenizationData\": {\"token\": \"test_token\"}}}") - @BeforeEach fun beforeEach() { analyticsManager = TestAnalyticsManager() @@ -94,7 +105,7 @@ internal class DefaultGooglePayDelegateTest { delegate.componentStateFlow.test { skipItems(1) - val paymentData = paymentData + val paymentData = TEST_PAYMENT_DATA delegate.updateComponentState(paymentData) @@ -122,6 +133,18 @@ internal class DefaultGooglePayDelegateTest { } } + @Test + fun `when onSubmit is called, then event is emitted to start Google Pay`() = runTest { + val task = Tasks.forResult(TEST_PAYMENT_DATA) + whenever(paymentsClient.loadPaymentData(any())) doReturn task + val payEventFlow = delegate.payEventFlow.test(testScheduler) + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + delegate.onSubmit() + + assertEquals(task, payEventFlow.latestValue) + } + @ParameterizedTest @MethodSource("amountSource") fun `when input data is valid then amount is propagated in component state if set`( @@ -134,7 +157,7 @@ internal class DefaultGooglePayDelegateTest { } delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) delegate.componentStateFlow.test { - delegate.updateComponentState(paymentData) + delegate.updateComponentState(TEST_PAYMENT_DATA) assertEquals(expectedComponentStateValue, expectMostRecentItem().data.amount) } } @@ -161,7 +184,7 @@ internal class DefaultGooglePayDelegateTest { fun `when component state updates amd the data is valid, then submit event is tracked`() { delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - delegate.updateComponentState(paymentData) + delegate.updateComponentState(TEST_PAYMENT_DATA) val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) analyticsManager.assertLastEventEquals(expectedEvent) @@ -174,7 +197,7 @@ internal class DefaultGooglePayDelegateTest { delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) delegate.componentStateFlow.test { - delegate.updateComponentState(paymentData) + delegate.updateComponentState(TEST_PAYMENT_DATA) assertEquals(TEST_CHECKOUT_ATTEMPT_ID, expectMostRecentItem().data.paymentMethod?.checkoutAttemptId) } @@ -188,6 +211,24 @@ internal class DefaultGooglePayDelegateTest { } } + @ParameterizedTest + @MethodSource("paymentResultSource") + fun `when handling payment result, then success or error is emitted`( + result: ApiTaskResult, + isSuccess: Boolean, + ) = runTest { + val componentStateFlow = delegate.componentStateFlow.test(testScheduler) + val exceptionFlow = delegate.exceptionFlow.test(testScheduler) + + delegate.handlePaymentResult(result) + + if (isSuccess) { + assertEquals(result.result, componentStateFlow.latestValue.paymentData) + } else { + assertInstanceOf(ComponentException::class.java, exceptionFlow.latestValue) + } + } + private fun createCheckoutConfiguration( amount: Amount? = null, configuration: GooglePayConfiguration.Builder.() -> Unit = {} @@ -213,7 +254,7 @@ internal class DefaultGooglePayDelegateTest { componentParams = GooglePayComponentParamsMapper(CommonComponentParamsMapper()) .mapToParams(configuration, Locale.US, null, null, paymentMethod), analyticsManager = analyticsManager, - application = Application(), + paymentsClient = paymentsClient, ) } @@ -221,6 +262,8 @@ internal class DefaultGooglePayDelegateTest { private val TEST_ORDER = OrderRequest("PSP", "ORDER_DATA") private const val TEST_CHECKOUT_ATTEMPT_ID = "TEST_CHECKOUT_ATTEMPT_ID" private const val TEST_PAYMENT_METHOD_TYPE = "TEST_PAYMENT_METHOD_TYPE" + private val TEST_PAYMENT_DATA: PaymentData = + PaymentData.fromJson("{\"paymentMethodData\": {\"tokenizationData\": {\"token\": \"test_token\"}}}") @JvmStatic fun amountSource() = listOf( @@ -230,5 +273,15 @@ internal class DefaultGooglePayDelegateTest { arguments(null, Amount("USD", 0)), arguments(null, Amount("USD", 0)), ) + + @JvmStatic + fun paymentResultSource() = listOf( + arguments(ApiTaskResult(TEST_PAYMENT_DATA, Status.RESULT_SUCCESS), true), + arguments(ApiTaskResult(null, Status.RESULT_SUCCESS), false), + arguments(ApiTaskResult(null, Status.RESULT_CANCELED), false), + arguments(ApiTaskResult(null, Status.RESULT_INTERNAL_ERROR), false), + arguments(ApiTaskResult(null, Status.RESULT_INTERRUPTED), false), + arguments(ApiTaskResult(null, Status(AutoResolveHelper.RESULT_ERROR)), false), + ) } } From 63ec39b4a3a52300ed089c6db8ab49035968752c Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Thu, 13 Jun 2024 15:47:21 +0200 Subject: [PATCH 11/44] Add release note COAND-855 --- RELEASE_NOTES.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index efe3a28d75..f6d200830b 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -8,7 +8,17 @@ [//]: # (## Deprecated) [//]: # ( - Configurations public constructor are deprecated, please use each Configuration's builder to make a Configuration object) +## New +- Launch Google Pay with `submit()` to get rid of the deprecated activity result handling. + ## Fixed -- For the Address Lookup functionality: - - Address data is now correctly saved to `PaymentComponentData`. - - Address fields that were edited manually no longer lose their state when starting Lookup mode. + +## Improved + +## Changed +- Dependency versions: + | Name | Version | + |--------------------------------------------------------------------------------------------------------|-------------------------------| + | | | + +## Deprecated From 5e131bfa712b65a2a3b64ce42d9c04c1eae553fb Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Mon, 24 Jun 2024 15:59:36 +0200 Subject: [PATCH 12/44] Run apiDump COAND-855 --- googlepay/api/googlepay.api | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/googlepay/api/googlepay.api b/googlepay/api/googlepay.api index dc8b4cabca..6175187b12 100644 --- a/googlepay/api/googlepay.api +++ b/googlepay/api/googlepay.api @@ -65,7 +65,7 @@ public final class com/adyen/checkout/googlepay/GooglePayButtonParameters { public fun toString ()Ljava/lang/String; } -public final class com/adyen/checkout/googlepay/GooglePayComponent : androidx/lifecycle/ViewModel, com/adyen/checkout/action/core/internal/ActionHandlingComponent, com/adyen/checkout/components/core/internal/ActivityResultHandlingComponent, com/adyen/checkout/components/core/internal/PaymentComponent, com/adyen/checkout/ui/core/internal/ui/ViewableComponent { +public final class com/adyen/checkout/googlepay/GooglePayComponent : androidx/lifecycle/ViewModel, com/adyen/checkout/action/core/internal/ActionHandlingComponent, com/adyen/checkout/components/core/internal/ActivityResultHandlingComponent, com/adyen/checkout/components/core/internal/ButtonComponent, com/adyen/checkout/components/core/internal/PaymentComponent, com/adyen/checkout/ui/core/internal/ui/ViewableComponent { public static final field Companion Lcom/adyen/checkout/googlepay/GooglePayComponent$Companion; public static final field PAYMENT_METHOD_TYPES Ljava/util/List; public static final field PROVIDER Lcom/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider; @@ -76,9 +76,11 @@ public final class com/adyen/checkout/googlepay/GooglePayComponent : androidx/li public fun handleAction (Lcom/adyen/checkout/components/core/action/Action;Landroid/app/Activity;)V public fun handleActivityResult (ILandroid/content/Intent;)V public fun handleIntent (Landroid/content/Intent;)V + public fun isConfirmationRequired ()Z public fun setInteractionBlocked (Z)V public fun setOnRedirectListener (Lkotlin/jvm/functions/Function0;)V public final fun startGooglePayScreen (Landroid/app/Activity;I)V + public fun submit ()V } public final class com/adyen/checkout/googlepay/GooglePayComponent$Companion { From ea3eed67a5754279f1f084e73ea5a74894868afc Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Fri, 20 Sep 2024 16:23:13 +0200 Subject: [PATCH 13/44] Fix build after rebase COAND-855 --- .../checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index a4c3cada93..3ec285aaea 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -248,8 +248,6 @@ internal class DefaultGooglePayDelegate( override fun shouldShowSubmitButton(): Boolean = isConfirmationRequired() - override fun shouldEnableSubmitButton(): Boolean = shouldShowSubmitButton() - override fun getPaymentMethodType(): String { return paymentMethod.type ?: PaymentMethodTypes.UNKNOWN } From bf76474ba6219540316579e4e5dad1c4a0b42d9a Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 26 Jun 2024 13:45:19 +0200 Subject: [PATCH 14/44] Add configuration for submit button visibility in Google Pay COAND-942 --- .../googlepay/GooglePayConfiguration.kt | 26 ++++++++++++++++--- .../ui/model/GooglePayComponentParams.kt | 4 ++- .../model/GooglePayComponentParamsMapper.kt | 1 + .../googlepay/GooglePayConfigurationTest.kt | 4 +++ .../GooglePayComponentParamsMapperTest.kt | 11 +++++--- .../internal/util/GooglePayUtilsTest.kt | 2 ++ 6 files changed, 40 insertions(+), 8 deletions(-) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayConfiguration.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayConfiguration.kt index 70a2bb0b4f..5593701902 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayConfiguration.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayConfiguration.kt @@ -16,6 +16,8 @@ import com.adyen.checkout.components.core.Amount import com.adyen.checkout.components.core.AnalyticsConfiguration import com.adyen.checkout.components.core.CheckoutConfiguration import com.adyen.checkout.components.core.PaymentMethod +import com.adyen.checkout.components.core.internal.ButtonConfiguration +import com.adyen.checkout.components.core.internal.ButtonConfigurationBuilder import com.adyen.checkout.components.core.internal.Configuration import com.adyen.checkout.components.core.internal.util.CheckoutConfigurationMarker import com.adyen.checkout.core.Environment @@ -34,9 +36,10 @@ class GooglePayConfiguration private constructor( override val environment: Environment, override val clientKey: String, override val analyticsConfiguration: AnalyticsConfiguration?, + override val amount: Amount?, + override val isSubmitButtonVisible: Boolean?, val merchantAccount: String?, val googlePayEnvironment: Int?, - override val amount: Amount?, val totalPriceStatus: String?, val countryCode: String?, val merchantInfo: MerchantInfo?, @@ -52,14 +55,15 @@ class GooglePayConfiguration private constructor( val isBillingAddressRequired: Boolean?, val billingAddressParameters: BillingAddressParameters?, internal val genericActionConfiguration: GenericActionConfiguration, -) : Configuration { +) : Configuration, ButtonConfiguration { /** * Builder to create a [GooglePayConfiguration]. */ @Suppress("TooManyFunctions") class Builder : - ActionHandlingPaymentMethodConfigurationBuilder { + ActionHandlingPaymentMethodConfigurationBuilder, + ButtonConfigurationBuilder { private var merchantAccount: String? = null private var googlePayEnvironment: Int? = null private var merchantInfo: MerchantInfo? = null @@ -76,6 +80,7 @@ class GooglePayConfiguration private constructor( private var isBillingAddressRequired: Boolean? = null private var billingAddressParameters: BillingAddressParameters? = null private var totalPriceStatus: String? = null + private var isSubmitButtonVisible: Boolean? = null /** * Initialize a configuration builder with the required fields. @@ -378,15 +383,28 @@ class GooglePayConfiguration private constructor( return super.setAmount(amount) } + /** + * Sets if submit button will be visible or not. + * + * Default is false. + * + * @param isSubmitButtonVisible If submit button should be visible or not. + */ + override fun setSubmitButtonVisible(isSubmitButtonVisible: Boolean): Builder { + this.isSubmitButtonVisible = isSubmitButtonVisible + return this + } + override fun buildInternal(): GooglePayConfiguration { return GooglePayConfiguration( shopperLocale = shopperLocale, environment = environment, clientKey = clientKey, analyticsConfiguration = analyticsConfiguration, + amount = amount, + isSubmitButtonVisible = isSubmitButtonVisible, merchantAccount = merchantAccount, googlePayEnvironment = googlePayEnvironment, - amount = amount, totalPriceStatus = totalPriceStatus, countryCode = countryCode, merchantInfo = merchantInfo, diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParams.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParams.kt index e6c7526487..b09994cc08 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParams.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParams.kt @@ -9,6 +9,7 @@ package com.adyen.checkout.googlepay.internal.ui.model import com.adyen.checkout.components.core.Amount +import com.adyen.checkout.components.core.internal.ui.model.ButtonParams import com.adyen.checkout.components.core.internal.ui.model.CommonComponentParams import com.adyen.checkout.components.core.internal.ui.model.ComponentParams import com.adyen.checkout.googlepay.BillingAddressParameters @@ -18,6 +19,7 @@ import com.adyen.checkout.googlepay.ShippingAddressParameters internal data class GooglePayComponentParams( private val commonComponentParams: CommonComponentParams, override val amount: Amount, + override val isSubmitButtonVisible: Boolean, val gatewayMerchantId: String, val googlePayEnvironment: Int, val totalPriceStatus: String, @@ -34,4 +36,4 @@ internal data class GooglePayComponentParams( val shippingAddressParameters: ShippingAddressParameters?, val isBillingAddressRequired: Boolean, val billingAddressParameters: BillingAddressParameters?, -) : ComponentParams by commonComponentParams +) : ComponentParams by commonComponentParams, ButtonParams diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt index ee737a5192..ded32f9d7c 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt @@ -60,6 +60,7 @@ internal class GooglePayComponentParamsMapper( return GooglePayComponentParams( commonComponentParams = commonComponentParams, amount = commonComponentParams.amount ?: DEFAULT_AMOUNT, + isSubmitButtonVisible = googlePayConfiguration?.isSubmitButtonVisible ?: false, gatewayMerchantId = googlePayConfiguration.getPreferredGatewayMerchantId(paymentMethod), allowedAuthMethods = googlePayConfiguration.getAvailableAuthMethods(), allowedCardNetworks = googlePayConfiguration.getAvailableCardNetworks(paymentMethod), diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/GooglePayConfigurationTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/GooglePayConfigurationTest.kt index e75b35bf10..3160310d55 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/GooglePayConfigurationTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/GooglePayConfigurationTest.kt @@ -22,6 +22,7 @@ internal class GooglePayConfigurationTest { analyticsConfiguration = AnalyticsConfiguration(AnalyticsLevel.ALL), ) { googlePay { + setSubmitButtonVisible(true) setMerchantAccount("merchantAccount") setGooglePayEnvironment(WalletConstants.ENVIRONMENT_PRODUCTION) setMerchantInfo(MerchantInfo(merchantId = "id")) @@ -48,6 +49,7 @@ internal class GooglePayConfigurationTest { environment = Environment.TEST, clientKey = TEST_CLIENT_KEY, ) + .setSubmitButtonVisible(true) .setAmount(Amount("EUR", 123L)) .setAnalyticsConfiguration(AnalyticsConfiguration(AnalyticsLevel.ALL)) .setMerchantAccount("merchantAccount") @@ -99,6 +101,7 @@ internal class GooglePayConfigurationTest { clientKey = TEST_CLIENT_KEY, ) .setAmount(Amount("EUR", 123L)) + .setSubmitButtonVisible(true) .setAnalyticsConfiguration(AnalyticsConfiguration(AnalyticsLevel.ALL)) .setMerchantAccount("merchantAccount") .setGooglePayEnvironment(WalletConstants.ENVIRONMENT_PRODUCTION) @@ -139,6 +142,7 @@ internal class GooglePayConfigurationTest { assertEquals(config.environment, actualGooglePayCardConfig?.environment) assertEquals(config.clientKey, actualGooglePayCardConfig?.clientKey) assertEquals(config.amount, actualGooglePayCardConfig?.amount) + assertEquals(config.isSubmitButtonVisible, actualGooglePayCardConfig?.isSubmitButtonVisible) assertEquals(config.analyticsConfiguration, actualGooglePayCardConfig?.analyticsConfiguration) assertEquals(config.merchantAccount, actualGooglePayCardConfig?.merchantAccount) assertEquals(config.googlePayEnvironment, actualGooglePayCardConfig?.googlePayEnvironment) diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt index 244d2b9558..c223edb5a7 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt @@ -111,9 +111,10 @@ internal class GooglePayComponentParamsMapperTest { environment = Environment.APSE, clientKey = TEST_CLIENT_KEY_2, analyticsParams = AnalyticsParams(AnalyticsParamsLevel.ALL, TEST_CLIENT_KEY_2), + amount = amount, + isSubmitButtonVisible = false, gatewayMerchantId = "MERCHANT_ACCOUNT", googlePayEnvironment = WalletConstants.ENVIRONMENT_PRODUCTION, - amount = amount, totalPriceStatus = "STATUS", countryCode = "ZZ", merchantInfo = merchantInfo, @@ -148,6 +149,7 @@ internal class GooglePayComponentParamsMapperTest { googlePay { setAmount(Amount("USD", 1L)) setAnalyticsConfiguration(AnalyticsConfiguration(AnalyticsLevel.ALL)) + setSubmitButtonVisible(true) setMerchantAccount(TEST_GATEWAY_MERCHANT_ID) } } @@ -172,6 +174,7 @@ internal class GooglePayComponentParamsMapperTest { currency = "CAD", value = 123L, ), + isSubmitButtonVisible = true, ) assertEquals(expected, params) @@ -524,9 +527,10 @@ internal class GooglePayComponentParamsMapperTest { clientKey: String = TEST_CLIENT_KEY_1, analyticsParams: AnalyticsParams = AnalyticsParams(AnalyticsParamsLevel.ALL, TEST_CLIENT_KEY_1), isCreatedByDropIn: Boolean = false, + amount: Amount? = null, + isSubmitButtonVisible: Boolean = false, gatewayMerchantId: String = TEST_GATEWAY_MERCHANT_ID, googlePayEnvironment: Int = WalletConstants.ENVIRONMENT_TEST, - amount: Amount? = null, totalPriceStatus: String = "FINAL", countryCode: String? = null, merchantInfo: MerchantInfo? = null, @@ -550,9 +554,10 @@ internal class GooglePayComponentParamsMapperTest { isCreatedByDropIn = isCreatedByDropIn, amount = amount, ), + amount = amount ?: Amount("USD", 0), + isSubmitButtonVisible = isSubmitButtonVisible, gatewayMerchantId = gatewayMerchantId, googlePayEnvironment = googlePayEnvironment, - amount = amount ?: Amount("USD", 0), totalPriceStatus = totalPriceStatus, countryCode = countryCode, merchantInfo = merchantInfo, diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/util/GooglePayUtilsTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/util/GooglePayUtilsTest.kt index a5f45707d4..d953fb7de3 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/util/GooglePayUtilsTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/util/GooglePayUtilsTest.kt @@ -248,6 +248,7 @@ internal class GooglePayUtilsTest { amount = null, ), amount = Amount("USD", 0), + isSubmitButtonVisible = false, gatewayMerchantId = "", googlePayEnvironment = WalletConstants.ENVIRONMENT_TEST, totalPriceStatus = "NOT_CURRENTLY_KNOWN", @@ -278,6 +279,7 @@ internal class GooglePayUtilsTest { amount = Amount("EUR", 13_37), ), amount = Amount("EUR", 13_37), + isSubmitButtonVisible = true, gatewayMerchantId = "GATEWAY_MERCHANT_ID", googlePayEnvironment = WalletConstants.ENVIRONMENT_PRODUCTION, totalPriceStatus = "TOTAL_PRICE_STATUS", From 44b5bc17d64b046c92b5ffd37efa4c62cc1cb7e5 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 26 Jun 2024 15:01:13 +0200 Subject: [PATCH 15/44] Show pay button within component COAND-942 --- .../internal/ui/view/CashAppPayButtonView.kt | 3 + .../CheckoutConfigurationProvider.kt | 1 + .../example/ui/googlepay/GooglePayFragment.kt | 28 --------- .../ui/googlepay/GooglePayViewModel.kt | 2 +- .../ui/googlepay/GooglePayViewState.kt | 2 - .../compose/SessionsGooglePayActivity.kt | 1 - .../compose/SessionsGooglePayScreen.kt | 13 ---- .../main/res/layout/fragment_google_pay.xml | 13 ---- googlepay/api/googlepay.api | 9 ++- .../checkout/googlepay/GooglePayComponent.kt | 8 +-- .../provider/GooglePayComponentProvider.kt | 3 + .../internal/ui/DefaultGooglePayDelegate.kt | 60 +++++++++---------- .../internal/ui/GooglePayButtonView.kt | 48 +++++++++++++++ .../internal/ui/GooglePayViewProvider.kt | 13 +++- .../model/GooglePayComponentParamsMapper.kt | 13 +++- .../res/layout/view_google_pay_button.xml | 19 ++++++ .../ui/DefaultGooglePayDelegateTest.kt | 27 +++++---- .../GooglePayComponentParamsMapperTest.kt | 2 +- .../checkout/ui/core/AdyenComponentView.kt | 1 + .../core/internal/ui/view/DefaultPayButton.kt | 3 + .../ui/core/internal/ui/view/PayButton.kt | 3 + 21 files changed, 161 insertions(+), 111 deletions(-) create mode 100644 googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt create mode 100644 googlepay/src/main/res/layout/view_google_pay_button.xml diff --git a/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/view/CashAppPayButtonView.kt b/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/view/CashAppPayButtonView.kt index 61c0bedb14..723facce10 100644 --- a/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/view/CashAppPayButtonView.kt +++ b/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/view/CashAppPayButtonView.kt @@ -12,6 +12,7 @@ import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater import com.adyen.checkout.cashapppay.databinding.CashAppPayButtonViewBinding +import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate import com.adyen.checkout.ui.core.internal.ui.view.PayButton internal class CashAppPayButtonView @JvmOverloads constructor( @@ -22,6 +23,8 @@ internal class CashAppPayButtonView @JvmOverloads constructor( private val binding = CashAppPayButtonViewBinding.inflate(LayoutInflater.from(context), this) + override fun initialize(delegate: ButtonDelegate) = Unit + override fun setEnabled(enabled: Boolean) { binding.payButton.isEnabled = enabled } 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 4d35bcc87b..811d2b9489 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 @@ -89,6 +89,7 @@ internal class CheckoutConfigurationProvider @Inject constructor( } googlePay { + setSubmitButtonVisible(true) setCountryCode(keyValueStorage.getCountry()) } diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayFragment.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayFragment.kt index b5a84815b6..0af398bdf5 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayFragment.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayFragment.kt @@ -24,8 +24,6 @@ import com.adyen.checkout.example.extensions.getLogTag import com.adyen.checkout.example.ui.configuration.CheckoutConfigurationProvider import com.adyen.checkout.googlepay.GooglePayComponent import com.adyen.checkout.redirect.RedirectComponent -import com.google.android.gms.wallet.button.ButtonConstants.ButtonType -import com.google.android.gms.wallet.button.ButtonOptions import com.google.android.material.bottomsheet.BottomSheetDialogFragment import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.launchIn @@ -88,8 +86,6 @@ class GooglePayFragment : BottomSheetDialogFragment() { this.googlePayComponent = googlePayComponent binding.componentView.attach(googlePayComponent, viewLifecycleOwner) - - loadGooglePayButton() } private fun onViewState(state: GooglePayViewState) { @@ -99,28 +95,18 @@ class GooglePayFragment : BottomSheetDialogFragment() { binding.errorView.text = getString(state.message) binding.componentView.isVisible = false binding.progressIndicator.isVisible = false - binding.googlePayButton.isVisible = false } GooglePayViewState.Loading -> { binding.errorView.isVisible = false binding.componentView.isVisible = false binding.progressIndicator.isVisible = true - binding.googlePayButton.isVisible = false - } - - GooglePayViewState.ShowButton -> { - binding.errorView.isVisible = false - binding.componentView.isVisible = false - binding.progressIndicator.isVisible = false - binding.googlePayButton.isVisible = true } GooglePayViewState.ShowComponent -> { binding.errorView.isVisible = false binding.componentView.isVisible = true binding.progressIndicator.isVisible = false - binding.googlePayButton.isVisible = false } } } @@ -137,20 +123,6 @@ class GooglePayFragment : BottomSheetDialogFragment() { dismiss() } - private fun loadGooglePayButton() { - val allowedPaymentMethods = googlePayComponent?.getGooglePayButtonParameters()?.allowedPaymentMethods.orEmpty() - val buttonOptions = ButtonOptions - .newBuilder() - .setButtonType(ButtonType.PAY) - .setAllowedPaymentMethods(allowedPaymentMethods) - .build() - binding.googlePayButton.initialize(buttonOptions) - - binding.googlePayButton.setOnClickListener { - googlePayComponent?.submit() - } - } - override fun onDestroyView() { super.onDestroyView() _binding = null diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewModel.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewModel.kt index c4d3be339c..d19ba0dd1c 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewModel.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewModel.kt @@ -114,7 +114,7 @@ internal class GooglePayViewModel @Inject constructor( override fun onAvailabilityResult(isAvailable: Boolean, paymentMethod: PaymentMethod) { viewModelScope.launch { if (isAvailable) { - _viewState.emit(GooglePayViewState.ShowButton) + _viewState.emit(GooglePayViewState.ShowComponent) } else { _viewState.emit(GooglePayViewState.Error(R.string.google_pay_unavailable_error)) } diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewState.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewState.kt index d9bceb0940..d66232b067 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewState.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewState.kt @@ -14,8 +14,6 @@ internal sealed class GooglePayViewState { data object Loading : GooglePayViewState() - data object ShowButton : GooglePayViewState() - data object ShowComponent : GooglePayViewState() data class Error(@StringRes val message: Int) : GooglePayViewState() diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayActivity.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayActivity.kt index 83f46fcd62..0d66f76d3b 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayActivity.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayActivity.kt @@ -46,7 +46,6 @@ class SessionsGooglePayActivity : AppCompatActivity() { val isDarkTheme = uiThemeRepository.isDarkTheme() ExampleTheme(isDarkTheme) { SessionsGooglePayScreen( - useDarkTheme = isDarkTheme, onBackPressed = { onBackPressedDispatcher.onBackPressed() }, googlePayState = googlePayState, eventsState = eventsState, diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt index 51dc35c4db..701fe4ec4f 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayScreen.kt @@ -35,13 +35,9 @@ import com.adyen.checkout.components.compose.AdyenComponent import com.adyen.checkout.components.compose.get import com.adyen.checkout.example.ui.compose.ResultContent import com.adyen.checkout.googlepay.GooglePayComponent -import com.google.pay.button.ButtonTheme -import com.google.pay.button.ButtonType -import com.google.pay.button.PayButton @Composable internal fun SessionsGooglePayScreen( - useDarkTheme: Boolean, googlePayState: SessionsGooglePayState, eventsState: SessionsGooglePayEvents, onBackPressed: () -> Unit, @@ -62,7 +58,6 @@ internal fun SessionsGooglePayScreen( SessionsGooglePayContent( googlePayState = googlePayState, googlePayEvents = eventsState, - useDarkTheme = useDarkTheme, modifier = Modifier.padding(innerPadding), ) } @@ -73,7 +68,6 @@ internal fun SessionsGooglePayScreen( private fun SessionsGooglePayContent( googlePayState: SessionsGooglePayState, googlePayEvents: SessionsGooglePayEvents, - useDarkTheme: Boolean, modifier: Modifier = Modifier, ) { val activity = LocalContext.current as Activity @@ -108,13 +102,6 @@ private fun SessionsGooglePayContent( is SessionsGooglePayState.ShowButton -> { AdyenComponent(googlePayComponent) - - PayButton( - onClick = { googlePayComponent.submit() }, - allowedPaymentMethods = googlePayComponent.getGooglePayButtonParameters().allowedPaymentMethods, - theme = if (useDarkTheme) ButtonTheme.Light else ButtonTheme.Dark, - type = ButtonType.Pay, - ) } is SessionsGooglePayState.FinalResult -> { diff --git a/example-app/src/main/res/layout/fragment_google_pay.xml b/example-app/src/main/res/layout/fragment_google_pay.xml index fa75802986..2dce7d3b29 100644 --- a/example-app/src/main/res/layout/fragment_google_pay.xml +++ b/example-app/src/main/res/layout/fragment_google_pay.xml @@ -8,7 +8,6 @@ @@ -16,18 +15,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - - (Ljava/util/Locale;Lcom/adyen/checkout/core/Environment;Ljava/lang/String;Lcom/adyen/checkout/components/core/AnalyticsConfiguration;Ljava/lang/String;Ljava/lang/Integer;Lcom/adyen/checkout/components/core/Amount;Ljava/lang/String;Ljava/lang/String;Lcom/adyen/checkout/googlepay/MerchantInfo;Ljava/util/List;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lcom/adyen/checkout/googlepay/ShippingAddressParameters;Ljava/lang/Boolean;Lcom/adyen/checkout/googlepay/BillingAddressParameters;Lcom/adyen/checkout/action/core/GenericActionConfiguration;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/util/Locale;Lcom/adyen/checkout/core/Environment;Ljava/lang/String;Lcom/adyen/checkout/components/core/AnalyticsConfiguration;Lcom/adyen/checkout/components/core/Amount;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Lcom/adyen/checkout/googlepay/MerchantInfo;Ljava/util/List;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lcom/adyen/checkout/googlepay/ShippingAddressParameters;Ljava/lang/Boolean;Lcom/adyen/checkout/googlepay/BillingAddressParameters;Lcom/adyen/checkout/action/core/GenericActionConfiguration;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public fun describeContents ()I public final fun getAllowedAuthMethods ()Ljava/util/List; public final fun getAllowedCardNetworks ()Ljava/util/List; @@ -129,10 +129,11 @@ public final class com/adyen/checkout/googlepay/GooglePayConfiguration : com/ady public final fun isEmailRequired ()Ljava/lang/Boolean; public final fun isExistingPaymentMethodRequired ()Ljava/lang/Boolean; public final fun isShippingAddressRequired ()Ljava/lang/Boolean; + public fun isSubmitButtonVisible ()Ljava/lang/Boolean; public fun writeToParcel (Landroid/os/Parcel;I)V } -public final class com/adyen/checkout/googlepay/GooglePayConfiguration$Builder : com/adyen/checkout/action/core/internal/ActionHandlingPaymentMethodConfigurationBuilder { +public final class com/adyen/checkout/googlepay/GooglePayConfiguration$Builder : com/adyen/checkout/action/core/internal/ActionHandlingPaymentMethodConfigurationBuilder, com/adyen/checkout/components/core/internal/ButtonConfigurationBuilder { public fun (Landroid/content/Context;Lcom/adyen/checkout/core/Environment;Ljava/lang/String;)V public fun (Lcom/adyen/checkout/core/Environment;Ljava/lang/String;)V public fun (Ljava/util/Locale;Lcom/adyen/checkout/core/Environment;Ljava/lang/String;)V @@ -154,6 +155,8 @@ public final class com/adyen/checkout/googlepay/GooglePayConfiguration$Builder : public final fun setMerchantInfo (Lcom/adyen/checkout/googlepay/MerchantInfo;)Lcom/adyen/checkout/googlepay/GooglePayConfiguration$Builder; public final fun setShippingAddressParameters (Lcom/adyen/checkout/googlepay/ShippingAddressParameters;)Lcom/adyen/checkout/googlepay/GooglePayConfiguration$Builder; public final fun setShippingAddressRequired (Z)Lcom/adyen/checkout/googlepay/GooglePayConfiguration$Builder; + public synthetic fun setSubmitButtonVisible (Z)Lcom/adyen/checkout/components/core/internal/ButtonConfigurationBuilder; + public fun setSubmitButtonVisible (Z)Lcom/adyen/checkout/googlepay/GooglePayConfiguration$Builder; public final fun setTotalPriceStatus (Ljava/lang/String;)Lcom/adyen/checkout/googlepay/GooglePayConfiguration$Builder; } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt index 810dd88e0e..739664c573 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt @@ -96,6 +96,10 @@ class GooglePayComponent internal constructor( ?: adyenLog(AdyenLogLevel.ERROR) { "Component is currently not submittable, ignoring." } } + override fun setInteractionBlocked(isInteractionBlocked: Boolean) { + adyenLog(AdyenLogLevel.WARN) { "Interaction with GooglePayComponent can't be blocked" } + } + override fun isConfirmationRequired(): Boolean = (delegate as? ButtonDelegate)?.isConfirmationRequired() ?: false /** @@ -117,10 +121,6 @@ class GooglePayComponent internal constructor( googlePayDelegate.handleActivityResult(resultCode, data) } - override fun setInteractionBlocked(isInteractionBlocked: Boolean) { - adyenLog(AdyenLogLevel.WARN) { "Interaction with GooglePayComponent can't be blocked" } - } - override fun onCleared() { super.onCleared() adyenLog(AdyenLogLevel.DEBUG) { "onCleared" } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt index b845ba3971..e6e094a0b0 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt @@ -53,6 +53,7 @@ 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.ui.SubmitHandler import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability import com.google.android.gms.wallet.Wallet @@ -112,6 +113,7 @@ constructor( Wallet.getPaymentsClient(application, GooglePayUtils.createWalletOptions(componentParams)) val googlePayDelegate = DefaultGooglePayDelegate( + submitHandler = SubmitHandler(savedStateHandle), observerRepository = PaymentObserverRepository(), paymentMethod = paymentMethod, order = order, @@ -203,6 +205,7 @@ constructor( Wallet.getPaymentsClient(application, GooglePayUtils.createWalletOptions(componentParams)) val googlePayDelegate = DefaultGooglePayDelegate( + submitHandler = SubmitHandler(savedStateHandle), observerRepository = PaymentObserverRepository(), paymentMethod = paymentMethod, order = checkoutSession.order, diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index 3ec285aaea..17ae5f285a 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -32,7 +32,9 @@ import com.adyen.checkout.googlepay.internal.data.model.GooglePayPaymentMethodMo import com.adyen.checkout.googlepay.internal.ui.model.GooglePayComponentParams import com.adyen.checkout.googlepay.internal.util.GooglePayUtils import com.adyen.checkout.googlepay.internal.util.awaitTask +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.SubmitHandler import com.google.android.gms.common.api.CommonStatusCodes import com.google.android.gms.tasks.Task import com.google.android.gms.wallet.AutoResolveHelper @@ -44,13 +46,12 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch -@Suppress("TooManyFunctions") +@Suppress("TooManyFunctions", "LongParameterList") internal class DefaultGooglePayDelegate( + private val submitHandler: SubmitHandler, private val observerRepository: PaymentObserverRepository, private val paymentMethod: PaymentMethod, private val order: OrderRequest?, @@ -65,8 +66,7 @@ internal class DefaultGooglePayDelegate( private val exceptionChannel: Channel = bufferedChannel() override val exceptionFlow: Flow = exceptionChannel.receiveAsFlow() - private val submitChannel: Channel = bufferedChannel() - override val submitFlow: Flow = submitChannel.receiveAsFlow() + override val submitFlow: Flow = submitHandler.submitFlow private val _viewFlow = MutableStateFlow(GooglePayComponentViewType) override val viewFlow: Flow = _viewFlow @@ -79,12 +79,9 @@ internal class DefaultGooglePayDelegate( override fun initialize(coroutineScope: CoroutineScope) { _coroutineScope = coroutineScope + submitHandler.initialize(coroutineScope, componentStateFlow) initializeAnalytics(coroutineScope) - - componentStateFlow.onEach { - onState(it) - }.launchIn(coroutineScope) } private fun initializeAnalytics(coroutineScope: CoroutineScope) { @@ -95,15 +92,6 @@ internal class DefaultGooglePayDelegate( analyticsManager.trackEvent(event) } - private fun onState(state: GooglePayComponentState) { - if (state.isValid) { - val event = GenericEvents.submit(paymentMethod.type.orEmpty()) - analyticsManager.trackEvent(event) - - submitChannel.trySend(state) - } - } - override fun observe( lifecycleOwner: LifecycleOwner, coroutineScope: CoroutineScope, @@ -154,7 +142,7 @@ internal class DefaultGooglePayDelegate( ) } - @Deprecated("Deprecated in favor of startGooglePayScreen()", replaceWith = ReplaceWith("startGooglePayScreen()")) + @Deprecated("Deprecated in favor of onSubmit()", replaceWith = ReplaceWith("onSubmit()")) override fun startGooglePayScreen(activity: Activity, requestCode: Int) { adyenLog(AdyenLogLevel.DEBUG) { "startGooglePayScreen" } val paymentsClient = Wallet.getPaymentsClient(activity, GooglePayUtils.createWalletOptions(componentParams)) @@ -164,7 +152,7 @@ internal class DefaultGooglePayDelegate( } override fun onSubmit() { - adyenLog(AdyenLogLevel.DEBUG) { "startGooglePayScreen" } + adyenLog(AdyenLogLevel.DEBUG) { "onSubmit" } val paymentDataRequest = GooglePayUtils.createPaymentDataRequest(componentParams) val paymentDataTask = paymentsClient.loadPaymentData(paymentDataRequest) coroutineScope.launch { @@ -175,14 +163,8 @@ internal class DefaultGooglePayDelegate( override fun handlePaymentResult(paymentDataTaskResult: ApiTaskResult) { when (val statusCode = paymentDataTaskResult.status.statusCode) { CommonStatusCodes.SUCCESS -> { - val paymentData = paymentDataTaskResult.result - if (paymentData == null) { - adyenLog(AdyenLogLevel.ERROR) { "Result data is null" } - exceptionChannel.trySend(ComponentException("GooglePay encountered an unexpected error")) - return - } adyenLog(AdyenLogLevel.INFO) { "GooglePay payment result successful" } - updateComponentState(paymentData) + initiatePayment(paymentDataTaskResult.result) } CommonStatusCodes.CANCELED -> { @@ -216,8 +198,7 @@ internal class DefaultGooglePayDelegate( exceptionChannel.trySend(ComponentException("Result data is null")) return } - val paymentData = PaymentData.getFromIntent(data) - updateComponentState(paymentData) + initiatePayment(PaymentData.getFromIntent(data)) } Activity.RESULT_CANCELED -> { @@ -229,9 +210,22 @@ internal class DefaultGooglePayDelegate( val statusMessage: String = status?.let { ": ${it.statusMessage}" }.orEmpty() exceptionChannel.trySend(ComponentException("GooglePay returned an error$statusMessage")) } + } + } - else -> Unit + private fun initiatePayment(paymentData: PaymentData?) { + if (paymentData == null) { + adyenLog(AdyenLogLevel.ERROR) { "Payment data is null" } + exceptionChannel.trySend(ComponentException("GooglePay encountered an unexpected error")) + return } + adyenLog(AdyenLogLevel.INFO) { "GooglePay payment result successful" } + + val event = GenericEvents.submit(paymentMethod.type.orEmpty()) + analyticsManager.trackEvent(event) + + updateComponentState(paymentData) + submitHandler.onSubmit(_componentStateFlow.value) } override fun getGooglePayButtonParameters(): GooglePayButtonParameters { @@ -243,10 +237,10 @@ internal class DefaultGooglePayDelegate( return GooglePayButtonParameters(allowedPaymentMethods) } - // Currently, we don't show any button, but we could potentially show the Google Pay button. - override fun isConfirmationRequired(): Boolean = false + @Suppress("USELESS_IS_CHECK") + override fun isConfirmationRequired(): Boolean = _viewFlow.value is ButtonComponentViewType - override fun shouldShowSubmitButton(): Boolean = isConfirmationRequired() + override fun shouldShowSubmitButton(): Boolean = isConfirmationRequired() && componentParams.isSubmitButtonVisible override fun getPaymentMethodType(): String { return paymentMethod.type ?: PaymentMethodTypes.UNKNOWN diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt new file mode 100644 index 0000000000..4e3b1136bd --- /dev/null +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 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 26/6/2024. + */ + +package com.adyen.checkout.googlepay.internal.ui + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import com.adyen.checkout.googlepay.databinding.ViewGooglePayButtonBinding +import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate +import com.adyen.checkout.ui.core.internal.ui.view.PayButton +import com.google.android.gms.wallet.button.ButtonConstants.ButtonType +import com.google.android.gms.wallet.button.ButtonOptions + +internal class GooglePayButtonView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : PayButton(context, attrs, defStyleAttr) { + + private val binding = ViewGooglePayButtonBinding.inflate(LayoutInflater.from(context), this) + + override fun initialize(delegate: ButtonDelegate) { + check(delegate is GooglePayDelegate) + + binding.payButton.initialize( + ButtonOptions.newBuilder() + .setButtonType(ButtonType.PAY) + .setAllowedPaymentMethods(delegate.getGooglePayButtonParameters().allowedPaymentMethods) + .build(), + ) + } + + override fun setEnabled(enabled: Boolean) { + binding.payButton.isEnabled = enabled + } + + override fun setOnClickListener(listener: OnClickListener?) { + binding.payButton.setOnClickListener(listener) + } + + override fun setText(text: String?) = Unit +} diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt index 1ca8a11d61..f6255bdb3d 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt @@ -10,9 +10,12 @@ package com.adyen.checkout.googlepay.internal.ui import android.content.Context import android.view.LayoutInflater +import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType +import com.adyen.checkout.ui.core.internal.ui.ButtonViewProvider 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 +import com.adyen.checkout.ui.core.internal.ui.view.PayButton internal object GooglePayViewProvider : ViewProvider { @@ -33,6 +36,14 @@ internal object GooglePayViewProvider : ViewProvider { } } -internal object GooglePayComponentViewType : ComponentViewType { +internal class GooglePayButtonViewProvider : ButtonViewProvider { + override fun getButton(context: Context): PayButton = GooglePayButtonView(context) +} + +internal object GooglePayComponentViewType : ButtonComponentViewType { + override val buttonViewProvider: ButtonViewProvider get() = GooglePayButtonViewProvider() + override val viewProvider: ViewProvider = GooglePayViewProvider + + override val buttonTextResId: Int = ButtonComponentViewType.DEFAULT_BUTTON_TEXT_RES_ID } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt index ded32f9d7c..cbecb9fabf 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt @@ -60,7 +60,7 @@ internal class GooglePayComponentParamsMapper( return GooglePayComponentParams( commonComponentParams = commonComponentParams, amount = commonComponentParams.amount ?: DEFAULT_AMOUNT, - isSubmitButtonVisible = googlePayConfiguration?.isSubmitButtonVisible ?: false, + isSubmitButtonVisible = shouldShowSubmitButton(commonComponentParams, googlePayConfiguration), gatewayMerchantId = googlePayConfiguration.getPreferredGatewayMerchantId(paymentMethod), allowedAuthMethods = googlePayConfiguration.getAvailableAuthMethods(), allowedCardNetworks = googlePayConfiguration.getAvailableCardNetworks(paymentMethod), @@ -80,6 +80,17 @@ internal class GooglePayComponentParamsMapper( ) } + private fun shouldShowSubmitButton( + commonComponentParams: CommonComponentParams, + googlePayConfiguration: GooglePayConfiguration?, + ): Boolean { + return if (commonComponentParams.isCreatedByDropIn) { + false + } else { + googlePayConfiguration?.isSubmitButtonVisible ?: false + } + } + /** * Returns the [GooglePayConfiguration.merchantAccount] if set, or falls back to the * paymentMethod.configuration.gatewayMerchantId field returned by the API. diff --git a/googlepay/src/main/res/layout/view_google_pay_button.xml b/googlepay/src/main/res/layout/view_google_pay_button.xml new file mode 100644 index 0000000000..28bb00cc96 --- /dev/null +++ b/googlepay/src/main/res/layout/view_google_pay_button.xml @@ -0,0 +1,19 @@ + + + + + + + diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt index 9801cd01b6..a3ee34d28e 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt @@ -20,12 +20,14 @@ import com.adyen.checkout.components.core.internal.analytics.TestAnalyticsManage import com.adyen.checkout.components.core.internal.ui.model.CommonComponentParamsMapper import com.adyen.checkout.core.Environment import com.adyen.checkout.core.exception.ComponentException +import com.adyen.checkout.googlepay.GooglePayComponentState import com.adyen.checkout.googlepay.GooglePayConfiguration import com.adyen.checkout.googlepay.googlePay import com.adyen.checkout.googlepay.internal.ui.model.GooglePayComponentParamsMapper import com.adyen.checkout.googlepay.internal.util.GooglePayUtils import com.adyen.checkout.test.LoggingExtension import com.adyen.checkout.test.extensions.test +import com.adyen.checkout.ui.core.internal.ui.SubmitHandler import com.google.android.gms.common.api.Status import com.google.android.gms.tasks.Tasks import com.google.android.gms.wallet.AutoResolveHelper @@ -58,6 +60,7 @@ import java.util.Locale @OptIn(ExperimentalCoroutinesApi::class) @ExtendWith(MockitoExtension::class, LoggingExtension::class) internal class DefaultGooglePayDelegateTest( + @Mock private val submitHandler: SubmitHandler, @Mock private val paymentsClient: PaymentsClient, ) { @@ -66,6 +69,7 @@ internal class DefaultGooglePayDelegateTest( @BeforeEach fun beforeEach() { + whenever(paymentsClient.loadPaymentData(any())) doReturn Tasks.forResult(TEST_PAYMENT_DATA) analyticsManager = TestAnalyticsManager() delegate = createGooglePayDelegate() } @@ -180,16 +184,6 @@ internal class DefaultGooglePayDelegateTest( analyticsManager.assertLastEventEquals(expectedEvent) } - @Test - fun `when component state updates amd the data is valid, then submit event is tracked`() { - delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - - delegate.updateComponentState(TEST_PAYMENT_DATA) - - val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) - analyticsManager.assertLastEventEquals(expectedEvent) - } - @Test fun `when component state is valid then PaymentMethodDetails should contain checkoutAttemptId`() = runTest { analyticsManager.setCheckoutAttemptId(TEST_CHECKOUT_ATTEMPT_ID) @@ -203,6 +197,18 @@ internal class DefaultGooglePayDelegateTest( } } + @Test + fun `when payment is successful and the data is valid, then submit event is tracked`() { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + delegate.updateComponentState(TEST_PAYMENT_DATA) + + val result = ApiTaskResult(TEST_PAYMENT_DATA, Status.RESULT_SUCCESS) + delegate.handlePaymentResult(result) + + val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE) + analyticsManager.assertLastEventEquals(expectedEvent) + } + @Test fun `when delegate is cleared then analytics manager is cleared`() { delegate.onCleared() @@ -248,6 +254,7 @@ internal class DefaultGooglePayDelegateTest( ), ): DefaultGooglePayDelegate { return DefaultGooglePayDelegate( + submitHandler = submitHandler, observerRepository = PaymentObserverRepository(), paymentMethod = PaymentMethod(type = TEST_PAYMENT_METHOD_TYPE), order = TEST_ORDER, diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt index c223edb5a7..783651f107 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt @@ -174,7 +174,7 @@ internal class GooglePayComponentParamsMapperTest { currency = "CAD", value = 123L, ), - isSubmitButtonVisible = true, + isSubmitButtonVisible = false, ) assertEquals(expected, params) 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 8988f179d0..ac5a442fbd 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 @@ -147,6 +147,7 @@ class AdyenComponentView @JvmOverloads constructor( binding.frameLayoutButtonContainer.isVisible = buttonDelegate.shouldShowSubmitButton() val buttonView = (viewType as ButtonComponentViewType) .buttonViewProvider.getButton(context) + buttonView.initialize(buttonDelegate) buttonView.setText(viewType, componentParams, localizedContext) buttonView.setOnClickListener { buttonDelegate.onSubmit() diff --git a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/DefaultPayButton.kt b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/DefaultPayButton.kt index 96c12a09b2..a062e2567e 100644 --- a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/DefaultPayButton.kt +++ b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/DefaultPayButton.kt @@ -12,6 +12,7 @@ import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater import com.adyen.checkout.ui.core.databinding.DefaultPayButtonViewBinding +import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate internal class DefaultPayButton @JvmOverloads constructor( context: Context, @@ -21,6 +22,8 @@ internal class DefaultPayButton @JvmOverloads constructor( private val binding = DefaultPayButtonViewBinding.inflate(LayoutInflater.from(context), this) + override fun initialize(delegate: ButtonDelegate) = Unit + override fun setEnabled(enabled: Boolean) { binding.payButton.isEnabled = enabled } diff --git a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/PayButton.kt b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/PayButton.kt index f818ba1070..452c93659c 100644 --- a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/PayButton.kt +++ b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/PayButton.kt @@ -12,6 +12,7 @@ import android.content.Context import android.util.AttributeSet import android.widget.FrameLayout import androidx.annotation.RestrictTo +import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) abstract class PayButton( @@ -20,6 +21,8 @@ abstract class PayButton( defStyleAttr: Int, ) : FrameLayout(context, attrs, defStyleAttr) { + abstract fun initialize(delegate: ButtonDelegate) + abstract override fun setEnabled(enabled: Boolean) abstract override fun setOnClickListener(listener: OnClickListener?) From 1f1db293db57236ecc38a42708397302658248ba Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Thu, 10 Oct 2024 10:01:06 +0200 Subject: [PATCH 16/44] Support blocking interaction for GooglePayComponent COAND-942 --- .../checkout/googlepay/GooglePayComponent.kt | 4 ++- .../internal/ui/DefaultGooglePayDelegate.kt | 4 +++ .../googlepay/GooglePayComponentTest.kt | 28 +++++++++++++++++-- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt index 739664c573..450e0bb7f2 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt @@ -26,6 +26,7 @@ import com.adyen.checkout.components.core.internal.ui.ComponentDelegate import com.adyen.checkout.core.AdyenLogLevel import com.adyen.checkout.core.internal.util.adyenLog import com.adyen.checkout.googlepay.internal.provider.GooglePayComponentProvider +import com.adyen.checkout.googlepay.internal.ui.DefaultGooglePayDelegate import com.adyen.checkout.googlepay.internal.ui.GooglePayDelegate import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate import com.adyen.checkout.ui.core.internal.ui.ComponentViewType @@ -97,7 +98,8 @@ class GooglePayComponent internal constructor( } override fun setInteractionBlocked(isInteractionBlocked: Boolean) { - adyenLog(AdyenLogLevel.WARN) { "Interaction with GooglePayComponent can't be blocked" } + (delegate as? DefaultGooglePayDelegate)?.setInteractionBlocked(isInteractionBlocked) + ?: adyenLog(AdyenLogLevel.ERROR) { "Payment component is not interactable, ignoring." } } override fun isConfirmationRequired(): Boolean = (delegate as? ButtonDelegate)?.isConfirmationRequired() ?: false diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index 17ae5f285a..ae5f5807ae 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -242,6 +242,10 @@ internal class DefaultGooglePayDelegate( override fun shouldShowSubmitButton(): Boolean = isConfirmationRequired() && componentParams.isSubmitButtonVisible + internal fun setInteractionBlocked(isInteractionBlocked: Boolean) { + submitHandler.setInteractionBlocked(isInteractionBlocked) + } + override fun getPaymentMethodType(): String { return paymentMethod.type ?: PaymentMethodTypes.UNKNOWN } diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/GooglePayComponentTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/GooglePayComponentTest.kt index 6f59f59e13..f303433530 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/GooglePayComponentTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/GooglePayComponentTest.kt @@ -16,8 +16,8 @@ import com.adyen.checkout.action.core.internal.DefaultActionHandlingComponent import com.adyen.checkout.action.core.internal.ui.GenericActionDelegate import com.adyen.checkout.components.core.internal.ComponentEventHandler import com.adyen.checkout.components.core.internal.PaymentComponentEvent +import com.adyen.checkout.googlepay.internal.ui.DefaultGooglePayDelegate import com.adyen.checkout.googlepay.internal.ui.GooglePayComponentViewType -import com.adyen.checkout.googlepay.internal.ui.GooglePayDelegate import com.adyen.checkout.test.LoggingExtension import com.adyen.checkout.test.TestDispatcherExtension import com.adyen.checkout.test.extensions.invokeOnCleared @@ -41,7 +41,7 @@ import org.mockito.kotlin.whenever @ExtendWith(MockitoExtension::class, TestDispatcherExtension::class, LoggingExtension::class) internal class GooglePayComponentTest( - @Mock private val googlePayDelegate: GooglePayDelegate, + @Mock private val googlePayDelegate: DefaultGooglePayDelegate, @Mock private val genericActionDelegate: GenericActionDelegate, @Mock private val actionHandlingComponent: DefaultActionHandlingComponent, @Mock private val componentEventHandler: ComponentEventHandler, @@ -105,7 +105,7 @@ internal class GooglePayComponentTest( } @Test - fun `when cash app pay delegate view flow emits a value then component view flow should match that value`() = + fun `when google pay delegate view flow emits a value then component view flow should match that value`() = runTest { val delegateViewFlow = MutableStateFlow(TestComponentViewType.VIEW_TYPE_1) whenever(googlePayDelegate.viewFlow) doReturn delegateViewFlow @@ -209,4 +209,26 @@ internal class GooglePayComponentTest( verify(googlePayDelegate).isConfirmationRequired() } + + @Test + fun `when setInteractionBlocked is called, then delegate is called`() { + component = GooglePayComponent( + googlePayDelegate, + genericActionDelegate, + actionHandlingComponent, + componentEventHandler, + ) + whenever(component.delegate).thenReturn(googlePayDelegate) + + component.setInteractionBlocked(true) + + verify(googlePayDelegate).setInteractionBlocked(true) + } + + @Test + fun `when getGooglePayButtonParameters is called, then delegate is called`() { + component.getGooglePayButtonParameters() + + verify(googlePayDelegate).getGooglePayButtonParameters() + } } From 095b74d7b7575357bf045a48067ca71b0b02df9e Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Thu, 10 Oct 2024 10:12:10 +0200 Subject: [PATCH 17/44] Add tests for GooglePayComponentParamsMapper COAND-942 --- .../model/GooglePayComponentParamsMapper.kt | 1 + .../GooglePayComponentParamsMapperTest.kt | 54 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt index cbecb9fabf..1b500e0475 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt @@ -87,6 +87,7 @@ internal class GooglePayComponentParamsMapper( return if (commonComponentParams.isCreatedByDropIn) { false } else { + // TODO return true by default in v6 googlePayConfiguration?.isSubmitButtonVisible ?: false } } diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt index 783651f107..ce3cf2c15d 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt @@ -33,6 +33,9 @@ import com.adyen.checkout.googlepay.googlePay import com.adyen.checkout.test.LoggingExtension import com.google.android.gms.wallet.WalletConstants 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.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.ExtendWith @@ -483,6 +486,57 @@ internal class GooglePayComponentParamsMapperTest { assertEquals(expected, params) } + @Nested + inner class SubmitButtonVisibilityTest { + + @Test + fun `when created by drop-in, then submit button should not be visible`() { + val configuration = createCheckoutConfiguration() + + val params = googlePayComponentParamsMapper.mapToParams( + checkoutConfiguration = configuration, + deviceLocale = DEVICE_LOCALE, + dropInOverrideParams = DropInOverrideParams(null, null, true), + componentSessionParams = null, + paymentMethod = PaymentMethod(), + ) + + assertFalse(params.isSubmitButtonVisible) + } + + @Test + fun `when not created by drop-in and set in configuration, then submit button should be visible`() { + val configuration = createCheckoutConfiguration { + setSubmitButtonVisible(true) + } + + val params = googlePayComponentParamsMapper.mapToParams( + checkoutConfiguration = configuration, + deviceLocale = DEVICE_LOCALE, + dropInOverrideParams = null, + componentSessionParams = null, + paymentMethod = PaymentMethod(), + ) + + assertTrue(params.isSubmitButtonVisible) + } + + @Test + fun `when not created by drop-in and not configured, then submit button should not be visible`() { + val configuration = createCheckoutConfiguration() + + val params = googlePayComponentParamsMapper.mapToParams( + checkoutConfiguration = configuration, + deviceLocale = DEVICE_LOCALE, + dropInOverrideParams = null, + componentSessionParams = null, + paymentMethod = PaymentMethod(), + ) + + assertFalse(params.isSubmitButtonVisible) + } + } + private fun createCheckoutConfiguration( amount: Amount? = null, shopperLocale: Locale? = null, From 865cf62893b5c5126877d683a3af99532ed43500 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Thu, 10 Oct 2024 10:46:02 +0200 Subject: [PATCH 18/44] Add more tests for DefaultGooglePayDelegate COAND-942 --- .../ui/DefaultGooglePayDelegateTest.kt | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt index a3ee34d28e..8859ccab67 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt @@ -44,6 +44,7 @@ import org.junit.jupiter.api.Assertions.assertInstanceOf import org.junit.jupiter.api.Assertions.assertNull 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 @@ -54,6 +55,7 @@ import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.kotlin.any import org.mockito.kotlin.doReturn +import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import java.util.Locale @@ -166,6 +168,51 @@ internal class DefaultGooglePayDelegateTest( } } + @Nested + @DisplayName("when submit button is configured to be") + inner class SubmitButtonVisibilityTest { + + @Test + fun `hidden, then it should not show`() { + delegate = createGooglePayDelegate( + configuration = createCheckoutConfiguration { + setSubmitButtonVisible(false) + }, + ) + + assertFalse(delegate.shouldShowSubmitButton()) + } + + @Test + fun `visible, then it should show`() { + delegate = createGooglePayDelegate( + configuration = createCheckoutConfiguration { + setSubmitButtonVisible(true) + }, + ) + + 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) + } + } + @Nested inner class AnalyticsTest { From 66aaf27cc8399055c80f696531951bc09e9d3a51 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Fri, 11 Oct 2024 12:42:02 +0200 Subject: [PATCH 19/44] Allow to programmatically style the Google Pay button COAND-942 --- .../googlepay/GooglePayButtonStyling.kt | 41 +++++++++++++++++++ .../googlepay/GooglePayConfiguration.kt | 8 ++++ .../internal/ui/GooglePayButtonView.kt | 28 ++++++++++--- .../internal/ui/GooglePayDelegate.kt | 3 ++ .../ui/model/GooglePayComponentParams.kt | 2 + .../model/GooglePayComponentParamsMapper.kt | 1 + .../GooglePayComponentParamsMapperTest.kt | 12 ++++++ .../internal/util/GooglePayUtilsTest.kt | 2 + 8 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt new file mode 100644 index 0000000000..b136440742 --- /dev/null +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 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 11/10/2024. + */ + +package com.adyen.checkout.googlepay + +import android.os.Parcelable +import androidx.annotation.Dimension +import com.google.android.gms.wallet.button.ButtonConstants +import kotlinx.parcelize.Parcelize + +@Parcelize +data class GooglePayButtonStyling( + val buttonTheme: GooglePayButtonTheme?, + val buttonType: GooglePayButtonType?, + @Dimension(Dimension.DP) val cornerRadius: Int?, +) : Parcelable + +enum class GooglePayButtonTheme( + val value: Int, +) { + LIGHT(ButtonConstants.ButtonTheme.LIGHT), + DARK(ButtonConstants.ButtonTheme.DARK), +} + +enum class GooglePayButtonType( + val value: Int, +) { + BUY(ButtonConstants.ButtonType.BUY), + BOOK(ButtonConstants.ButtonType.BOOK), + CHECKOUT(ButtonConstants.ButtonType.CHECKOUT), + DONATE(ButtonConstants.ButtonType.DONATE), + ORDER(ButtonConstants.ButtonType.ORDER), + PAY(ButtonConstants.ButtonType.PAY), + SUBSCRIBE(ButtonConstants.ButtonType.SUBSCRIBE), + PLAIN(ButtonConstants.ButtonType.PLAIN), +} diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayConfiguration.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayConfiguration.kt index 5593701902..e0bbef6a2a 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayConfiguration.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayConfiguration.kt @@ -54,6 +54,7 @@ class GooglePayConfiguration private constructor( val shippingAddressParameters: ShippingAddressParameters?, val isBillingAddressRequired: Boolean?, val billingAddressParameters: BillingAddressParameters?, + val googlePayButtonStyling: GooglePayButtonStyling?, internal val genericActionConfiguration: GenericActionConfiguration, ) : Configuration, ButtonConfiguration { @@ -80,6 +81,7 @@ class GooglePayConfiguration private constructor( private var isBillingAddressRequired: Boolean? = null private var billingAddressParameters: BillingAddressParameters? = null private var totalPriceStatus: String? = null + private var googlePayButtonStyling: GooglePayButtonStyling? = null private var isSubmitButtonVisible: Boolean? = null /** @@ -383,6 +385,11 @@ class GooglePayConfiguration private constructor( return super.setAmount(amount) } + fun setGooglePayButtonStyling(googlePayButtonStyling: GooglePayButtonStyling): Builder { + this.googlePayButtonStyling = googlePayButtonStyling + return this + } + /** * Sets if submit button will be visible or not. * @@ -419,6 +426,7 @@ class GooglePayConfiguration private constructor( shippingAddressParameters = shippingAddressParameters, isBillingAddressRequired = isBillingAddressRequired, billingAddressParameters = billingAddressParameters, + googlePayButtonStyling = googlePayButtonStyling, genericActionConfiguration = genericActionConfigurationBuilder.build(), ) } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt index 4e3b1136bd..20b05c3fca 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt @@ -9,12 +9,12 @@ package com.adyen.checkout.googlepay.internal.ui import android.content.Context +import android.content.res.Resources import android.util.AttributeSet import android.view.LayoutInflater import com.adyen.checkout.googlepay.databinding.ViewGooglePayButtonBinding import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate import com.adyen.checkout.ui.core.internal.ui.view.PayButton -import com.google.android.gms.wallet.button.ButtonConstants.ButtonType import com.google.android.gms.wallet.button.ButtonOptions internal class GooglePayButtonView @JvmOverloads constructor( @@ -28,11 +28,29 @@ internal class GooglePayButtonView @JvmOverloads constructor( override fun initialize(delegate: ButtonDelegate) { check(delegate is GooglePayDelegate) + val buttonStyle = delegate.componentParams.googlePayButtonStyling + + val buttonType = buttonStyle?.buttonType + val buttonTheme = buttonStyle?.buttonTheme + val cornerRadius = + buttonStyle?.cornerRadius?.let { (it * Resources.getSystem().displayMetrics.density).toInt() } + binding.payButton.initialize( - ButtonOptions.newBuilder() - .setButtonType(ButtonType.PAY) - .setAllowedPaymentMethods(delegate.getGooglePayButtonParameters().allowedPaymentMethods) - .build(), + ButtonOptions.newBuilder().apply { + if (buttonType != null) { + setButtonType(buttonType.value) + } + + if (buttonTheme != null) { + setButtonTheme(buttonTheme.value) + } + + if (cornerRadius != null) { + setCornerRadius(cornerRadius) + } + + setAllowedPaymentMethods(delegate.getGooglePayButtonParameters().allowedPaymentMethods) + }.build(), ) } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt index 77ee7a403e..309a96d08d 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt @@ -14,6 +14,7 @@ import com.adyen.checkout.components.core.internal.ui.PaymentComponentDelegate import com.adyen.checkout.core.exception.CheckoutException import com.adyen.checkout.googlepay.GooglePayButtonParameters import com.adyen.checkout.googlepay.GooglePayComponentState +import com.adyen.checkout.googlepay.internal.ui.model.GooglePayComponentParams import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate import com.adyen.checkout.ui.core.internal.ui.ViewProvidingDelegate import com.google.android.gms.tasks.Task @@ -26,6 +27,8 @@ internal interface GooglePayDelegate : ViewProvidingDelegate, ButtonDelegate { + override val componentParams: GooglePayComponentParams + val componentStateFlow: Flow val exceptionFlow: Flow diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParams.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParams.kt index b09994cc08..f6d8bed96a 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParams.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParams.kt @@ -13,6 +13,7 @@ import com.adyen.checkout.components.core.internal.ui.model.ButtonParams import com.adyen.checkout.components.core.internal.ui.model.CommonComponentParams import com.adyen.checkout.components.core.internal.ui.model.ComponentParams import com.adyen.checkout.googlepay.BillingAddressParameters +import com.adyen.checkout.googlepay.GooglePayButtonStyling import com.adyen.checkout.googlepay.MerchantInfo import com.adyen.checkout.googlepay.ShippingAddressParameters @@ -36,4 +37,5 @@ internal data class GooglePayComponentParams( val shippingAddressParameters: ShippingAddressParameters?, val isBillingAddressRequired: Boolean, val billingAddressParameters: BillingAddressParameters?, + val googlePayButtonStyling: GooglePayButtonStyling?, ) : ComponentParams by commonComponentParams, ButtonParams diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt index 1b500e0475..02e974e9dd 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapper.kt @@ -77,6 +77,7 @@ internal class GooglePayComponentParamsMapper( shippingAddressParameters = googlePayConfiguration?.shippingAddressParameters, isBillingAddressRequired = googlePayConfiguration?.isBillingAddressRequired ?: false, billingAddressParameters = googlePayConfiguration?.billingAddressParameters, + googlePayButtonStyling = googlePayConfiguration?.googlePayButtonStyling, ) } diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt index ce3cf2c15d..3ec2d394f7 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayComponentParamsMapperTest.kt @@ -26,6 +26,9 @@ import com.adyen.checkout.core.exception.ComponentException import com.adyen.checkout.googlepay.AllowedAuthMethods import com.adyen.checkout.googlepay.AllowedCardNetworks import com.adyen.checkout.googlepay.BillingAddressParameters +import com.adyen.checkout.googlepay.GooglePayButtonStyling +import com.adyen.checkout.googlepay.GooglePayButtonTheme +import com.adyen.checkout.googlepay.GooglePayButtonType import com.adyen.checkout.googlepay.GooglePayConfiguration import com.adyen.checkout.googlepay.MerchantInfo import com.adyen.checkout.googlepay.ShippingAddressParameters @@ -74,6 +77,11 @@ internal class GooglePayComponentParamsMapperTest { val allowedCardNetworks = listOf("CARD1", "CARD2") val shippingAddressParameters = ShippingAddressParameters(listOf("ZZ", "AA"), true) val billingAddressParameters = BillingAddressParameters("FORMAT", true) + val googlePayButtonStyling = GooglePayButtonStyling( + buttonTheme = GooglePayButtonTheme.LIGHT, + buttonType = GooglePayButtonType.BOOK, + cornerRadius = 16, + ) val configuration = CheckoutConfiguration( shopperLocale = Locale.FRANCE, @@ -98,6 +106,7 @@ internal class GooglePayComponentParamsMapperTest { setShippingAddressParameters(shippingAddressParameters) setShippingAddressRequired(true) setTotalPriceStatus("STATUS") + setGooglePayButtonStyling(googlePayButtonStyling) } } @@ -132,6 +141,7 @@ internal class GooglePayComponentParamsMapperTest { shippingAddressParameters = shippingAddressParameters, isBillingAddressRequired = true, billingAddressParameters = billingAddressParameters, + googlePayButtonStyling = googlePayButtonStyling, ) assertEquals(expected, params) @@ -599,6 +609,7 @@ internal class GooglePayComponentParamsMapperTest { shippingAddressParameters: ShippingAddressParameters? = null, isBillingAddressRequired: Boolean = false, billingAddressParameters: BillingAddressParameters? = null, + googlePayButtonStyling: GooglePayButtonStyling? = null, ) = GooglePayComponentParams( commonComponentParams = CommonComponentParams( shopperLocale = shopperLocale, @@ -626,6 +637,7 @@ internal class GooglePayComponentParamsMapperTest { shippingAddressParameters = shippingAddressParameters, isBillingAddressRequired = isBillingAddressRequired, billingAddressParameters = billingAddressParameters, + googlePayButtonStyling = googlePayButtonStyling, ) companion object { diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/util/GooglePayUtilsTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/util/GooglePayUtilsTest.kt index d953fb7de3..807a2f42c4 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/util/GooglePayUtilsTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/util/GooglePayUtilsTest.kt @@ -265,6 +265,7 @@ internal class GooglePayUtilsTest { shippingAddressParameters = null, isBillingAddressRequired = false, billingAddressParameters = null, + googlePayButtonStyling = null, ) } @@ -305,6 +306,7 @@ internal class GooglePayUtilsTest { format = "FORMAT", isPhoneNumberRequired = true, ), + googlePayButtonStyling = null, ) } } From 191eca8cf36ce8eaf882a6d78446c23bb9c12b39 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Fri, 11 Oct 2024 14:17:20 +0200 Subject: [PATCH 20/44] Allow styling of Google Pay button through XML COAND-942 --- .../src/main/res/values-night/styles.xml | 4 +- example-app/src/main/res/values/styles.xml | 4 +- .../googlepay/GooglePayButtonStyling.kt | 6 +-- .../internal/ui/GooglePayButtonView.kt | 53 ++++++++++++++++++- .../res/layout/view_google_pay_button.xml | 1 + googlepay/src/main/res/values/attrs.xml | 30 +++++++++++ googlepay/src/main/res/values/styles.xml | 15 ++++++ 7 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 googlepay/src/main/res/values/attrs.xml create mode 100644 googlepay/src/main/res/values/styles.xml diff --git a/example-app/src/main/res/values-night/styles.xml b/example-app/src/main/res/values-night/styles.xml index b8704d3158..f347af9167 100644 --- a/example-app/src/main/res/values-night/styles.xml +++ b/example-app/src/main/res/values-night/styles.xml @@ -8,7 +8,7 @@ - diff --git a/example-app/src/main/res/values/styles.xml b/example-app/src/main/res/values/styles.xml index ef0d1e9681..41c372fcf1 100644 --- a/example-app/src/main/res/values/styles.xml +++ b/example-app/src/main/res/values/styles.xml @@ -38,8 +38,8 @@ diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt index b136440742..79c5bdd5a4 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt @@ -15,9 +15,9 @@ import kotlinx.parcelize.Parcelize @Parcelize data class GooglePayButtonStyling( - val buttonTheme: GooglePayButtonTheme?, - val buttonType: GooglePayButtonType?, - @Dimension(Dimension.DP) val cornerRadius: Int?, + val buttonTheme: GooglePayButtonTheme? = null, + val buttonType: GooglePayButtonType? = null, + @Dimension(Dimension.DP) val cornerRadius: Int? = null, ) : Parcelable enum class GooglePayButtonTheme( diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt index 20b05c3fca..ae5208a483 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt @@ -12,6 +12,9 @@ import android.content.Context import android.content.res.Resources import android.util.AttributeSet import android.view.LayoutInflater +import com.adyen.checkout.googlepay.GooglePayButtonTheme +import com.adyen.checkout.googlepay.GooglePayButtonType +import com.adyen.checkout.googlepay.R import com.adyen.checkout.googlepay.databinding.ViewGooglePayButtonBinding import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate import com.adyen.checkout.ui.core.internal.ui.view.PayButton @@ -25,15 +28,37 @@ internal class GooglePayButtonView @JvmOverloads constructor( private val binding = ViewGooglePayButtonBinding.inflate(LayoutInflater.from(context), this) + private val styledButtonType: GooglePayButtonType? + private val styledButtonTheme: GooglePayButtonTheme? + private val styledCornerRadius: Int? + + init { + val typedArray = context.theme.obtainStyledAttributes( + attrs, + R.styleable.GooglePayButtonView, + defStyleAttr, + R.style.AdyenCheckout_GooglePay_Button, + ) + styledButtonType = + typedArray.getInt(R.styleable.GooglePayButtonView_adyenGooglePayButtonType, -1).mapStyledButtonType() + styledButtonTheme = + typedArray.getInt(R.styleable.GooglePayButtonView_adyenGooglePayButtonTheme, -1).mapStyledButtonTheme() + styledCornerRadius = + typedArray.getDimensionPixelSize(R.styleable.GooglePayButtonView_adyenGooglePayButtonCornerRadius, -1) + .mapStyledCornerRadius() + typedArray.recycle() + } + override fun initialize(delegate: ButtonDelegate) { check(delegate is GooglePayDelegate) val buttonStyle = delegate.componentParams.googlePayButtonStyling - val buttonType = buttonStyle?.buttonType - val buttonTheme = buttonStyle?.buttonTheme + val buttonType = buttonStyle?.buttonType ?: styledButtonType + val buttonTheme = buttonStyle?.buttonTheme ?: styledButtonTheme val cornerRadius = buttonStyle?.cornerRadius?.let { (it * Resources.getSystem().displayMetrics.density).toInt() } + ?: styledCornerRadius binding.payButton.initialize( ButtonOptions.newBuilder().apply { @@ -63,4 +88,28 @@ internal class GooglePayButtonView @JvmOverloads constructor( } override fun setText(text: String?) = Unit + + private fun Int.mapStyledButtonType(): GooglePayButtonType? = when (this) { + 0 -> GooglePayButtonType.BUY + 1 -> GooglePayButtonType.BOOK + 2 -> GooglePayButtonType.CHECKOUT + 3 -> GooglePayButtonType.DONATE + 4 -> GooglePayButtonType.ORDER + 5 -> GooglePayButtonType.PAY + 6 -> GooglePayButtonType.SUBSCRIBE + 7 -> GooglePayButtonType.PLAIN + else -> null + } + + private fun Int.mapStyledButtonTheme(): GooglePayButtonTheme? = when (this) { + 0 -> GooglePayButtonTheme.LIGHT + 1 -> GooglePayButtonTheme.DARK + else -> null + } + + private fun Int.mapStyledCornerRadius(): Int? = if (this == -1) { + null + } else { + this + } } diff --git a/googlepay/src/main/res/layout/view_google_pay_button.xml b/googlepay/src/main/res/layout/view_google_pay_button.xml index 28bb00cc96..5adc62558e 100644 --- a/googlepay/src/main/res/layout/view_google_pay_button.xml +++ b/googlepay/src/main/res/layout/view_google_pay_button.xml @@ -12,6 +12,7 @@ diff --git a/googlepay/src/main/res/values/attrs.xml b/googlepay/src/main/res/values/attrs.xml new file mode 100644 index 0000000000..4239a03f37 --- /dev/null +++ b/googlepay/src/main/res/values/attrs.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/googlepay/src/main/res/values/styles.xml b/googlepay/src/main/res/values/styles.xml new file mode 100644 index 0000000000..61e560dbb9 --- /dev/null +++ b/googlepay/src/main/res/values/styles.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/googlepay/api/googlepay.api b/googlepay/api/googlepay.api index 440414a733..a56954af9c 100644 --- a/googlepay/api/googlepay.api +++ b/googlepay/api/googlepay.api @@ -65,6 +65,58 @@ public final class com/adyen/checkout/googlepay/GooglePayButtonParameters { public fun toString ()Ljava/lang/String; } +public final class com/adyen/checkout/googlepay/GooglePayButtonStyling : android/os/Parcelable { + public static final field CREATOR Landroid/os/Parcelable$Creator; + public fun ()V + public fun (Lcom/adyen/checkout/googlepay/GooglePayButtonTheme;Lcom/adyen/checkout/googlepay/GooglePayButtonType;Ljava/lang/Integer;)V + public synthetic fun (Lcom/adyen/checkout/googlepay/GooglePayButtonTheme;Lcom/adyen/checkout/googlepay/GooglePayButtonType;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/adyen/checkout/googlepay/GooglePayButtonTheme; + public final fun component2 ()Lcom/adyen/checkout/googlepay/GooglePayButtonType; + public final fun component3 ()Ljava/lang/Integer; + public final fun copy (Lcom/adyen/checkout/googlepay/GooglePayButtonTheme;Lcom/adyen/checkout/googlepay/GooglePayButtonType;Ljava/lang/Integer;)Lcom/adyen/checkout/googlepay/GooglePayButtonStyling; + public static synthetic fun copy$default (Lcom/adyen/checkout/googlepay/GooglePayButtonStyling;Lcom/adyen/checkout/googlepay/GooglePayButtonTheme;Lcom/adyen/checkout/googlepay/GooglePayButtonType;Ljava/lang/Integer;ILjava/lang/Object;)Lcom/adyen/checkout/googlepay/GooglePayButtonStyling; + public fun describeContents ()I + public fun equals (Ljava/lang/Object;)Z + public final fun getButtonTheme ()Lcom/adyen/checkout/googlepay/GooglePayButtonTheme; + public final fun getButtonType ()Lcom/adyen/checkout/googlepay/GooglePayButtonType; + public final fun getCornerRadius ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public fun writeToParcel (Landroid/os/Parcel;I)V +} + +public final class com/adyen/checkout/googlepay/GooglePayButtonStyling$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lcom/adyen/checkout/googlepay/GooglePayButtonStyling; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lcom/adyen/checkout/googlepay/GooglePayButtonStyling; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + +public final class com/adyen/checkout/googlepay/GooglePayButtonTheme : java/lang/Enum { + public static final field DARK Lcom/adyen/checkout/googlepay/GooglePayButtonTheme; + public static final field LIGHT Lcom/adyen/checkout/googlepay/GooglePayButtonTheme; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public final fun getValue ()I + public static fun valueOf (Ljava/lang/String;)Lcom/adyen/checkout/googlepay/GooglePayButtonTheme; + public static fun values ()[Lcom/adyen/checkout/googlepay/GooglePayButtonTheme; +} + +public final class com/adyen/checkout/googlepay/GooglePayButtonType : java/lang/Enum { + public static final field BOOK Lcom/adyen/checkout/googlepay/GooglePayButtonType; + public static final field BUY Lcom/adyen/checkout/googlepay/GooglePayButtonType; + public static final field CHECKOUT Lcom/adyen/checkout/googlepay/GooglePayButtonType; + public static final field DONATE Lcom/adyen/checkout/googlepay/GooglePayButtonType; + public static final field ORDER Lcom/adyen/checkout/googlepay/GooglePayButtonType; + public static final field PAY Lcom/adyen/checkout/googlepay/GooglePayButtonType; + public static final field PLAIN Lcom/adyen/checkout/googlepay/GooglePayButtonType; + public static final field SUBSCRIBE Lcom/adyen/checkout/googlepay/GooglePayButtonType; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public final fun getValue ()I + public static fun valueOf (Ljava/lang/String;)Lcom/adyen/checkout/googlepay/GooglePayButtonType; + public static fun values ()[Lcom/adyen/checkout/googlepay/GooglePayButtonType; +} + public final class com/adyen/checkout/googlepay/GooglePayComponent : androidx/lifecycle/ViewModel, com/adyen/checkout/action/core/internal/ActionHandlingComponent, com/adyen/checkout/components/core/internal/ActivityResultHandlingComponent, com/adyen/checkout/components/core/internal/ButtonComponent, com/adyen/checkout/components/core/internal/PaymentComponent, com/adyen/checkout/ui/core/internal/ui/ViewableComponent { public static final field Companion Lcom/adyen/checkout/googlepay/GooglePayComponent$Companion; public static final field PAYMENT_METHOD_TYPES Ljava/util/List; @@ -106,7 +158,7 @@ public final class com/adyen/checkout/googlepay/GooglePayComponentState : com/ad public final class com/adyen/checkout/googlepay/GooglePayConfiguration : com/adyen/checkout/components/core/internal/ButtonConfiguration, com/adyen/checkout/components/core/internal/Configuration { public static final field CREATOR Landroid/os/Parcelable$Creator; - public synthetic fun (Ljava/util/Locale;Lcom/adyen/checkout/core/Environment;Ljava/lang/String;Lcom/adyen/checkout/components/core/AnalyticsConfiguration;Lcom/adyen/checkout/components/core/Amount;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Lcom/adyen/checkout/googlepay/MerchantInfo;Ljava/util/List;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lcom/adyen/checkout/googlepay/ShippingAddressParameters;Ljava/lang/Boolean;Lcom/adyen/checkout/googlepay/BillingAddressParameters;Lcom/adyen/checkout/action/core/GenericActionConfiguration;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/util/Locale;Lcom/adyen/checkout/core/Environment;Ljava/lang/String;Lcom/adyen/checkout/components/core/AnalyticsConfiguration;Lcom/adyen/checkout/components/core/Amount;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Lcom/adyen/checkout/googlepay/MerchantInfo;Ljava/util/List;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lcom/adyen/checkout/googlepay/ShippingAddressParameters;Ljava/lang/Boolean;Lcom/adyen/checkout/googlepay/BillingAddressParameters;Lcom/adyen/checkout/googlepay/GooglePayButtonStyling;Lcom/adyen/checkout/action/core/GenericActionConfiguration;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public fun describeContents ()I public final fun getAllowedAuthMethods ()Ljava/util/List; public final fun getAllowedCardNetworks ()Ljava/util/List; @@ -116,6 +168,7 @@ public final class com/adyen/checkout/googlepay/GooglePayConfiguration : com/ady public fun getClientKey ()Ljava/lang/String; public final fun getCountryCode ()Ljava/lang/String; public fun getEnvironment ()Lcom/adyen/checkout/core/Environment; + public final fun getGooglePayButtonStyling ()Lcom/adyen/checkout/googlepay/GooglePayButtonStyling; public final fun getGooglePayEnvironment ()Ljava/lang/Integer; public final fun getMerchantAccount ()Ljava/lang/String; public final fun getMerchantInfo ()Lcom/adyen/checkout/googlepay/MerchantInfo; @@ -150,6 +203,7 @@ public final class com/adyen/checkout/googlepay/GooglePayConfiguration$Builder : public final fun setCountryCode (Ljava/lang/String;)Lcom/adyen/checkout/googlepay/GooglePayConfiguration$Builder; public final fun setEmailRequired (Z)Lcom/adyen/checkout/googlepay/GooglePayConfiguration$Builder; public final fun setExistingPaymentMethodRequired (Z)Lcom/adyen/checkout/googlepay/GooglePayConfiguration$Builder; + public final fun setGooglePayButtonStyling (Lcom/adyen/checkout/googlepay/GooglePayButtonStyling;)Lcom/adyen/checkout/googlepay/GooglePayConfiguration$Builder; public final fun setGooglePayEnvironment (I)Lcom/adyen/checkout/googlepay/GooglePayConfiguration$Builder; public final fun setMerchantAccount (Ljava/lang/String;)Lcom/adyen/checkout/googlepay/GooglePayConfiguration$Builder; public final fun setMerchantInfo (Lcom/adyen/checkout/googlepay/MerchantInfo;)Lcom/adyen/checkout/googlepay/GooglePayConfiguration$Builder; diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt index fe2ac968b3..4d48323a70 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt @@ -20,6 +20,7 @@ import kotlinx.parcelize.Parcelize * @param buttonType Changes the text displayed inside of the button. * @param cornerRadius Sets the corner radius of the button. For example, passing 16 means the radius will be 16 dp. */ +@Suppress("MaxLineLength") @Parcelize data class GooglePayButtonStyling( val buttonTheme: GooglePayButtonTheme? = null, diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt index ae5208a483..6b99b440b6 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt @@ -89,6 +89,7 @@ internal class GooglePayButtonView @JvmOverloads constructor( override fun setText(text: String?) = Unit + @Suppress("MagicNumber") private fun Int.mapStyledButtonType(): GooglePayButtonType? = when (this) { 0 -> GooglePayButtonType.BUY 1 -> GooglePayButtonType.BOOK From 415953ac3d21cdbabf841df4c05bfed49dd7198e Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Tue, 15 Oct 2024 15:58:54 +0200 Subject: [PATCH 23/44] Automatically use dark/light button theme based on app theme This is the same as we do with other payment method buttons and makes it easier for merchants to supports dark-mode. It is still be possible for merchants to change this behaviour if they want. COAND-942 --- example-app/src/main/res/values/styles.xml | 4 ---- .../src/main/res/values-night/styles.xml | 5 +++-- googlepay/src/main/res/values/styles.xml | 4 +++- 3 files changed, 6 insertions(+), 7 deletions(-) rename {example-app => googlepay}/src/main/res/values-night/styles.xml (81%) diff --git a/example-app/src/main/res/values/styles.xml b/example-app/src/main/res/values/styles.xml index acbb8ba49a..ec5f9b5a42 100644 --- a/example-app/src/main/res/values/styles.xml +++ b/example-app/src/main/res/values/styles.xml @@ -38,8 +38,4 @@ - diff --git a/example-app/src/main/res/values-night/styles.xml b/googlepay/src/main/res/values-night/styles.xml similarity index 81% rename from example-app/src/main/res/values-night/styles.xml rename to googlepay/src/main/res/values-night/styles.xml index f347af9167..ad24e59f4c 100644 --- a/example-app/src/main/res/values-night/styles.xml +++ b/googlepay/src/main/res/values-night/styles.xml @@ -1,9 +1,9 @@ @@ -11,4 +11,5 @@ + diff --git a/googlepay/src/main/res/values/styles.xml b/googlepay/src/main/res/values/styles.xml index 61e560dbb9..afba028b53 100644 --- a/googlepay/src/main/res/values/styles.xml +++ b/googlepay/src/main/res/values/styles.xml @@ -10,6 +10,8 @@ From 7094661a362bc55c068cc772e89c464f4bbae176 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Tue, 15 Oct 2024 13:38:39 +0200 Subject: [PATCH 24/44] Display toolbar in drop-in Google Pay fragment COAND-1012 --- .../ui/GooglePayComponentDialogFragment.kt | 16 ++++++++++++++++ .../res/layout/fragment_google_pay_component.xml | 5 +++++ 2 files changed, 21 insertions(+) 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 8d0abe3465..b4b0fdfa33 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 @@ -42,6 +42,12 @@ internal class GooglePayComponentDialogFragment : private lateinit var paymentMethod: PaymentMethod private lateinit var component: GooglePayComponent + private val toolbarMode: DropInBottomSheetToolbarMode + get() = when { + dropInViewModel.shouldSkipToSinglePaymentMethod() -> DropInBottomSheetToolbarMode.CLOSE_BUTTON + else -> DropInBottomSheetToolbarMode.BACK_BUTTON + } + override fun onCreate(savedInstanceState: Bundle?) { adyenLog(AdyenLogLevel.DEBUG) { "onCreate" } super.onCreate(savedInstanceState) @@ -60,6 +66,8 @@ internal class GooglePayComponentDialogFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { adyenLog(AdyenLogLevel.DEBUG) { "onViewCreated" } + initToolbar() + loadComponent() binding.componentView.attach(component, viewLifecycleOwner) @@ -72,6 +80,14 @@ internal class GooglePayComponentDialogFragment : .launchIn(viewLifecycleOwner.lifecycleScope) } + private fun initToolbar() = with(binding.bottomSheetToolbar) { + setTitle(paymentMethod.name) + setOnButtonClickListener { + onBackPressed() + } + setMode(toolbarMode) + } + private fun loadComponent() { @Suppress("SwallowedException") try { diff --git a/drop-in/src/main/res/layout/fragment_google_pay_component.xml b/drop-in/src/main/res/layout/fragment_google_pay_component.xml index 32cee25328..59b9c0ff67 100644 --- a/drop-in/src/main/res/layout/fragment_google_pay_component.xml +++ b/drop-in/src/main/res/layout/fragment_google_pay_component.xml @@ -11,6 +11,11 @@ android:layout_height="wrap_content" android:orientation="vertical"> + + Date: Tue, 15 Oct 2024 15:49:25 +0200 Subject: [PATCH 25/44] Add release note COAND-1012 --- RELEASE_NOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index f6d200830b..fdcfd2a392 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -10,6 +10,7 @@ ## New - Launch Google Pay with `submit()` to get rid of the deprecated activity result handling. +- For drop-in, show a toolbar on every intermediary screen, so shoppers can always easily navigate back. ## Fixed From 095cc7f5117912c97950a69a4c5dd648d8eff5dc Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 16 Oct 2024 15:37:13 +0200 Subject: [PATCH 26/44] Move Cash apps loading indicator to ui-core COAND-1012 --- .../internal/ui/CashAppPayViewProvider.kt | 4 +-- .../main/res/template/values/strings.xml.tt | 1 - cashapppay/src/main/res/values/styles.xml | 13 --------- .../internal/ui/view/ProcessingPaymentView.kt | 27 ++++++++++++------- .../res/layout/processing_payment_view.xml | 4 +-- .../main/res/template/values/strings.xml.tt | 2 ++ ui-core/src/main/res/values/strings.xml | 2 ++ ui-core/src/main/res/values/styles.xml | 15 +++++++++++ 8 files changed, 41 insertions(+), 27 deletions(-) rename cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/view/CashAppPayWaitingView.kt => ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/ProcessingPaymentView.kt (61%) rename cashapppay/src/main/res/layout/cash_app_pay_waiting_view.xml => ui-core/src/main/res/layout/processing_payment_view.xml (80%) 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 de259f016b..a8f667decc 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 @@ -11,13 +11,13 @@ package com.adyen.checkout.cashapppay.internal.ui import android.content.Context 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 import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType import com.adyen.checkout.ui.core.internal.ui.ButtonViewProvider 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 import com.adyen.checkout.ui.core.internal.ui.view.PayButton +import com.adyen.checkout.ui.core.internal.ui.view.ProcessingPaymentView internal object CashAppPayViewProvider : ViewProvider { @@ -26,7 +26,7 @@ internal object CashAppPayViewProvider : ViewProvider { context: Context, ): ComponentView = when (viewType) { CashAppPayComponentViewType -> CashAppPayView(context) - PaymentInProgressViewType -> CashAppPayWaitingView(context) + PaymentInProgressViewType -> ProcessingPaymentView(context) else -> throw IllegalArgumentException("Unsupported view type") } } diff --git a/cashapppay/src/main/res/template/values/strings.xml.tt b/cashapppay/src/main/res/template/values/strings.xml.tt index 3ce489f054..b936f73e1d 100644 --- a/cashapppay/src/main/res/template/values/strings.xml.tt +++ b/cashapppay/src/main/res/template/values/strings.xml.tt @@ -8,5 +8,4 @@ %%storeDetails%% - %%paypal.processingPayment%% diff --git a/cashapppay/src/main/res/values/styles.xml b/cashapppay/src/main/res/values/styles.xml index 643a771043..52a4547201 100644 --- a/cashapppay/src/main/res/values/styles.xml +++ b/cashapppay/src/main/res/values/styles.xml @@ -15,18 +15,5 @@ 18sp - - - - + + + + From 3cc83a517775d355755c95c72ce2affbf395ab93 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 16 Oct 2024 16:30:07 +0200 Subject: [PATCH 27/44] Add translations COAND-1012 --- cashapppay/src/main/res/values-ar/strings.xml | 1 - cashapppay/src/main/res/values-bg-rBG/strings.xml | 1 - cashapppay/src/main/res/values-ca-rES/strings.xml | 1 - cashapppay/src/main/res/values-cs-rCZ/strings.xml | 1 - cashapppay/src/main/res/values-da-rDK/strings.xml | 1 - cashapppay/src/main/res/values-de-rDE/strings.xml | 1 - cashapppay/src/main/res/values-el-rGR/strings.xml | 1 - cashapppay/src/main/res/values-es-rES/strings.xml | 1 - cashapppay/src/main/res/values-et-rEE/strings.xml | 1 - cashapppay/src/main/res/values-fi-rFI/strings.xml | 1 - cashapppay/src/main/res/values-fr-rFR/strings.xml | 1 - cashapppay/src/main/res/values-hr-rHR/strings.xml | 1 - cashapppay/src/main/res/values-hu-rHU/strings.xml | 1 - cashapppay/src/main/res/values-is-rIS/strings.xml | 1 - cashapppay/src/main/res/values-it-rIT/strings.xml | 1 - cashapppay/src/main/res/values-ja-rJP/strings.xml | 1 - cashapppay/src/main/res/values-ko-rKR/strings.xml | 1 - cashapppay/src/main/res/values-lt-rLT/strings.xml | 1 - cashapppay/src/main/res/values-lv-rLV/strings.xml | 1 - cashapppay/src/main/res/values-nb-rNO/strings.xml | 1 - cashapppay/src/main/res/values-nl-rNL/strings.xml | 1 - cashapppay/src/main/res/values-pl-rPL/strings.xml | 1 - cashapppay/src/main/res/values-pt-rBR/strings.xml | 1 - cashapppay/src/main/res/values-pt-rPT/strings.xml | 1 - cashapppay/src/main/res/values-ro-rRO/strings.xml | 1 - cashapppay/src/main/res/values-ru-rRU/strings.xml | 1 - cashapppay/src/main/res/values-sk-rSK/strings.xml | 1 - cashapppay/src/main/res/values-sl-rSI/strings.xml | 1 - cashapppay/src/main/res/values-sv-rSE/strings.xml | 1 - cashapppay/src/main/res/values-zh-rCN/strings.xml | 1 - cashapppay/src/main/res/values-zh-rTW/strings.xml | 1 - cashapppay/src/main/res/values/strings.xml | 1 - twint/src/main/res/values-ar/strings.xml | 2 +- twint/src/main/res/values-bg-rBG/strings.xml | 2 +- twint/src/main/res/values-ca-rES/strings.xml | 2 +- twint/src/main/res/values-cs-rCZ/strings.xml | 2 +- twint/src/main/res/values-da-rDK/strings.xml | 2 +- twint/src/main/res/values-de-rDE/strings.xml | 2 +- twint/src/main/res/values-el-rGR/strings.xml | 2 +- twint/src/main/res/values-es-rES/strings.xml | 2 +- twint/src/main/res/values-et-rEE/strings.xml | 2 +- twint/src/main/res/values-fi-rFI/strings.xml | 2 +- twint/src/main/res/values-fr-rFR/strings.xml | 2 +- twint/src/main/res/values-hr-rHR/strings.xml | 2 +- twint/src/main/res/values-hu-rHU/strings.xml | 2 +- twint/src/main/res/values-is-rIS/strings.xml | 2 +- twint/src/main/res/values-it-rIT/strings.xml | 2 +- twint/src/main/res/values-ja-rJP/strings.xml | 2 +- twint/src/main/res/values-ko-rKR/strings.xml | 2 +- twint/src/main/res/values-lt-rLT/strings.xml | 2 +- twint/src/main/res/values-lv-rLV/strings.xml | 2 +- twint/src/main/res/values-nb-rNO/strings.xml | 2 +- twint/src/main/res/values-nl-rNL/strings.xml | 2 +- twint/src/main/res/values-pl-rPL/strings.xml | 2 +- twint/src/main/res/values-pt-rBR/strings.xml | 2 +- twint/src/main/res/values-pt-rPT/strings.xml | 2 +- twint/src/main/res/values-ro-rRO/strings.xml | 2 +- twint/src/main/res/values-ru-rRU/strings.xml | 2 +- twint/src/main/res/values-sk-rSK/strings.xml | 2 +- twint/src/main/res/values-sl-rSI/strings.xml | 2 +- twint/src/main/res/values-sv-rSE/strings.xml | 2 +- twint/src/main/res/values-zh-rCN/strings.xml | 2 +- twint/src/main/res/values-zh-rTW/strings.xml | 2 +- twint/src/main/res/values/strings.xml | 2 +- ui-core/src/main/res/values-ar/strings.xml | 2 ++ ui-core/src/main/res/values-bg-rBG/strings.xml | 2 ++ ui-core/src/main/res/values-ca-rES/strings.xml | 2 ++ ui-core/src/main/res/values-cs-rCZ/strings.xml | 2 ++ ui-core/src/main/res/values-da-rDK/strings.xml | 2 ++ ui-core/src/main/res/values-de-rDE/strings.xml | 2 ++ ui-core/src/main/res/values-el-rGR/strings.xml | 2 ++ ui-core/src/main/res/values-es-rES/strings.xml | 2 ++ ui-core/src/main/res/values-et-rEE/strings.xml | 2 ++ ui-core/src/main/res/values-fi-rFI/strings.xml | 2 ++ ui-core/src/main/res/values-fr-rFR/strings.xml | 2 ++ ui-core/src/main/res/values-hr-rHR/strings.xml | 2 ++ ui-core/src/main/res/values-hu-rHU/strings.xml | 2 ++ ui-core/src/main/res/values-is-rIS/strings.xml | 2 ++ ui-core/src/main/res/values-it-rIT/strings.xml | 2 ++ ui-core/src/main/res/values-ja-rJP/strings.xml | 2 ++ ui-core/src/main/res/values-ko-rKR/strings.xml | 2 ++ ui-core/src/main/res/values-lt-rLT/strings.xml | 2 ++ ui-core/src/main/res/values-lv-rLV/strings.xml | 2 ++ ui-core/src/main/res/values-nb-rNO/strings.xml | 2 ++ ui-core/src/main/res/values-nl-rNL/strings.xml | 2 ++ ui-core/src/main/res/values-pl-rPL/strings.xml | 2 ++ ui-core/src/main/res/values-pt-rBR/strings.xml | 2 ++ ui-core/src/main/res/values-pt-rPT/strings.xml | 2 ++ ui-core/src/main/res/values-ro-rRO/strings.xml | 2 ++ ui-core/src/main/res/values-ru-rRU/strings.xml | 2 ++ ui-core/src/main/res/values-sk-rSK/strings.xml | 2 ++ ui-core/src/main/res/values-sl-rSI/strings.xml | 2 ++ ui-core/src/main/res/values-sv-rSE/strings.xml | 2 ++ ui-core/src/main/res/values-zh-rCN/strings.xml | 2 ++ ui-core/src/main/res/values-zh-rTW/strings.xml | 2 ++ 95 files changed, 94 insertions(+), 64 deletions(-) diff --git a/cashapppay/src/main/res/values-ar/strings.xml b/cashapppay/src/main/res/values-ar/strings.xml index 2b812ea78f..b0878541df 100644 --- a/cashapppay/src/main/res/values-ar/strings.xml +++ b/cashapppay/src/main/res/values-ar/strings.xml @@ -8,5 +8,4 @@ حفظ لمدفوعاتي القادمة - جارِ معالجة المدفوعات… diff --git a/cashapppay/src/main/res/values-bg-rBG/strings.xml b/cashapppay/src/main/res/values-bg-rBG/strings.xml index 8f84adb4cf..49f695af31 100644 --- a/cashapppay/src/main/res/values-bg-rBG/strings.xml +++ b/cashapppay/src/main/res/values-bg-rBG/strings.xml @@ -8,5 +8,4 @@ Запазване за следващото ми плащане - Обработка на плащането… diff --git a/cashapppay/src/main/res/values-ca-rES/strings.xml b/cashapppay/src/main/res/values-ca-rES/strings.xml index 0634aeb780..b638f6b993 100644 --- a/cashapppay/src/main/res/values-ca-rES/strings.xml +++ b/cashapppay/src/main/res/values-ca-rES/strings.xml @@ -8,5 +8,4 @@ Desa\'l per al meu proper pagament - S\'esta processant el pagament… diff --git a/cashapppay/src/main/res/values-cs-rCZ/strings.xml b/cashapppay/src/main/res/values-cs-rCZ/strings.xml index 1801d976d9..4308eac985 100644 --- a/cashapppay/src/main/res/values-cs-rCZ/strings.xml +++ b/cashapppay/src/main/res/values-cs-rCZ/strings.xml @@ -8,5 +8,4 @@ Uložit pro příští platby - Zpracování platby… diff --git a/cashapppay/src/main/res/values-da-rDK/strings.xml b/cashapppay/src/main/res/values-da-rDK/strings.xml index b5e13d4b70..7e5cefdd1b 100644 --- a/cashapppay/src/main/res/values-da-rDK/strings.xml +++ b/cashapppay/src/main/res/values-da-rDK/strings.xml @@ -8,5 +8,4 @@ Gem til min næste betaling - Behandler betaling… diff --git a/cashapppay/src/main/res/values-de-rDE/strings.xml b/cashapppay/src/main/res/values-de-rDE/strings.xml index 1a33495c92..1ff4ddc1b4 100644 --- a/cashapppay/src/main/res/values-de-rDE/strings.xml +++ b/cashapppay/src/main/res/values-de-rDE/strings.xml @@ -8,5 +8,4 @@ Für zukünftige Zahlvorgänge speichern - Zahlung wird verarbeitet… diff --git a/cashapppay/src/main/res/values-el-rGR/strings.xml b/cashapppay/src/main/res/values-el-rGR/strings.xml index c3da340648..2b59f67998 100644 --- a/cashapppay/src/main/res/values-el-rGR/strings.xml +++ b/cashapppay/src/main/res/values-el-rGR/strings.xml @@ -8,5 +8,4 @@ Αποθήκευση για την επόμενη πληρωμή μου - Επεξεργασία πληρωμής… diff --git a/cashapppay/src/main/res/values-es-rES/strings.xml b/cashapppay/src/main/res/values-es-rES/strings.xml index a282fe5af7..79fef68395 100644 --- a/cashapppay/src/main/res/values-es-rES/strings.xml +++ b/cashapppay/src/main/res/values-es-rES/strings.xml @@ -8,5 +8,4 @@ Recordar para mi próximo pago - Procesando pago… diff --git a/cashapppay/src/main/res/values-et-rEE/strings.xml b/cashapppay/src/main/res/values-et-rEE/strings.xml index b37ef99f09..52ae2947aa 100644 --- a/cashapppay/src/main/res/values-et-rEE/strings.xml +++ b/cashapppay/src/main/res/values-et-rEE/strings.xml @@ -8,5 +8,4 @@ Salvesta mu järgmise makse jaoks - Makse töötlemine … diff --git a/cashapppay/src/main/res/values-fi-rFI/strings.xml b/cashapppay/src/main/res/values-fi-rFI/strings.xml index 47a7aebdc4..27ad6df965 100644 --- a/cashapppay/src/main/res/values-fi-rFI/strings.xml +++ b/cashapppay/src/main/res/values-fi-rFI/strings.xml @@ -8,5 +8,4 @@ Tallenna seuraavaa maksuani varten - Maksua käsitellään… diff --git a/cashapppay/src/main/res/values-fr-rFR/strings.xml b/cashapppay/src/main/res/values-fr-rFR/strings.xml index 6615d2c753..aae6b252fc 100644 --- a/cashapppay/src/main/res/values-fr-rFR/strings.xml +++ b/cashapppay/src/main/res/values-fr-rFR/strings.xml @@ -8,5 +8,4 @@ Sauvegarder pour mon prochain paiement - Traitement du paiement en cours… diff --git a/cashapppay/src/main/res/values-hr-rHR/strings.xml b/cashapppay/src/main/res/values-hr-rHR/strings.xml index 0c5a8a7e7b..f1a49b69e7 100644 --- a/cashapppay/src/main/res/values-hr-rHR/strings.xml +++ b/cashapppay/src/main/res/values-hr-rHR/strings.xml @@ -8,5 +8,4 @@ Pohrani za moje sljedeće plaćanje - Obrada plaćanja u tijeku… diff --git a/cashapppay/src/main/res/values-hu-rHU/strings.xml b/cashapppay/src/main/res/values-hu-rHU/strings.xml index 37218519e2..db5ad56615 100644 --- a/cashapppay/src/main/res/values-hu-rHU/strings.xml +++ b/cashapppay/src/main/res/values-hu-rHU/strings.xml @@ -8,5 +8,4 @@ Mentés a következő fizetéshez - Fizetés feldolgozása… diff --git a/cashapppay/src/main/res/values-is-rIS/strings.xml b/cashapppay/src/main/res/values-is-rIS/strings.xml index 8c84a651ed..f9beb5794b 100644 --- a/cashapppay/src/main/res/values-is-rIS/strings.xml +++ b/cashapppay/src/main/res/values-is-rIS/strings.xml @@ -8,5 +8,4 @@ Spara fyrir næstu greiðslu - Unnið úr greiðslu… diff --git a/cashapppay/src/main/res/values-it-rIT/strings.xml b/cashapppay/src/main/res/values-it-rIT/strings.xml index cf28df04c9..9563785e4f 100644 --- a/cashapppay/src/main/res/values-it-rIT/strings.xml +++ b/cashapppay/src/main/res/values-it-rIT/strings.xml @@ -8,5 +8,4 @@ Salva per il prossimo pagamento - Elaborazione del pagamento in corso… diff --git a/cashapppay/src/main/res/values-ja-rJP/strings.xml b/cashapppay/src/main/res/values-ja-rJP/strings.xml index 9cdc8febb8..8944ed199d 100644 --- a/cashapppay/src/main/res/values-ja-rJP/strings.xml +++ b/cashapppay/src/main/res/values-ja-rJP/strings.xml @@ -8,5 +8,4 @@ 次回のお支払いのため詳細を保存 - 支払いを処理しています… diff --git a/cashapppay/src/main/res/values-ko-rKR/strings.xml b/cashapppay/src/main/res/values-ko-rKR/strings.xml index 7e21b66846..32aaeb06b9 100644 --- a/cashapppay/src/main/res/values-ko-rKR/strings.xml +++ b/cashapppay/src/main/res/values-ko-rKR/strings.xml @@ -8,5 +8,4 @@ 다음 결제를 위해 이 수단 저장 - 결제 처리 중… diff --git a/cashapppay/src/main/res/values-lt-rLT/strings.xml b/cashapppay/src/main/res/values-lt-rLT/strings.xml index 8ff48b9685..431023b205 100644 --- a/cashapppay/src/main/res/values-lt-rLT/strings.xml +++ b/cashapppay/src/main/res/values-lt-rLT/strings.xml @@ -8,5 +8,4 @@ Išsaugoti kitam mokėjimui - Mokėjimas apdorojamas… diff --git a/cashapppay/src/main/res/values-lv-rLV/strings.xml b/cashapppay/src/main/res/values-lv-rLV/strings.xml index b87ba14304..a78ccce57c 100644 --- a/cashapppay/src/main/res/values-lv-rLV/strings.xml +++ b/cashapppay/src/main/res/values-lv-rLV/strings.xml @@ -8,5 +8,4 @@ Saglabāt manam nākamajam maksājumam - Notiek maksājuma apstrāde… diff --git a/cashapppay/src/main/res/values-nb-rNO/strings.xml b/cashapppay/src/main/res/values-nb-rNO/strings.xml index 0c036a6d1d..c863f1d07e 100644 --- a/cashapppay/src/main/res/values-nb-rNO/strings.xml +++ b/cashapppay/src/main/res/values-nb-rNO/strings.xml @@ -8,5 +8,4 @@ Lagre til min neste betaling - Behandler betaling… diff --git a/cashapppay/src/main/res/values-nl-rNL/strings.xml b/cashapppay/src/main/res/values-nl-rNL/strings.xml index 9371e67494..9cd55a0534 100644 --- a/cashapppay/src/main/res/values-nl-rNL/strings.xml +++ b/cashapppay/src/main/res/values-nl-rNL/strings.xml @@ -8,5 +8,4 @@ Bewaar voor mijn volgende betaling - Betaling wordt verwerkt… diff --git a/cashapppay/src/main/res/values-pl-rPL/strings.xml b/cashapppay/src/main/res/values-pl-rPL/strings.xml index 10614e4c13..a18f52a602 100644 --- a/cashapppay/src/main/res/values-pl-rPL/strings.xml +++ b/cashapppay/src/main/res/values-pl-rPL/strings.xml @@ -8,5 +8,4 @@ Zapisz na potrzeby następnej płatności - Przetwarzanie płatności… diff --git a/cashapppay/src/main/res/values-pt-rBR/strings.xml b/cashapppay/src/main/res/values-pt-rBR/strings.xml index 89cc49afb1..e96e04a449 100644 --- a/cashapppay/src/main/res/values-pt-rBR/strings.xml +++ b/cashapppay/src/main/res/values-pt-rBR/strings.xml @@ -8,5 +8,4 @@ Salvar para meu próximo pagamento - Processando pagamento… diff --git a/cashapppay/src/main/res/values-pt-rPT/strings.xml b/cashapppay/src/main/res/values-pt-rPT/strings.xml index 83e20e6bc5..225f8b4529 100644 --- a/cashapppay/src/main/res/values-pt-rPT/strings.xml +++ b/cashapppay/src/main/res/values-pt-rPT/strings.xml @@ -8,5 +8,4 @@ Guardar para o meu próximo pagamento - A processar pagamento… diff --git a/cashapppay/src/main/res/values-ro-rRO/strings.xml b/cashapppay/src/main/res/values-ro-rRO/strings.xml index e90e4c3008..fb8655eb49 100644 --- a/cashapppay/src/main/res/values-ro-rRO/strings.xml +++ b/cashapppay/src/main/res/values-ro-rRO/strings.xml @@ -8,5 +8,4 @@ Salvează pentru următoarea mea plată - Se prelucrează plata… diff --git a/cashapppay/src/main/res/values-ru-rRU/strings.xml b/cashapppay/src/main/res/values-ru-rRU/strings.xml index d01ef30545..c14a1aeedf 100644 --- a/cashapppay/src/main/res/values-ru-rRU/strings.xml +++ b/cashapppay/src/main/res/values-ru-rRU/strings.xml @@ -8,5 +8,4 @@ Сохранить для следующего платежа - Платеж обрабатывается… diff --git a/cashapppay/src/main/res/values-sk-rSK/strings.xml b/cashapppay/src/main/res/values-sk-rSK/strings.xml index ff3c302d5a..8198dbd57e 100644 --- a/cashapppay/src/main/res/values-sk-rSK/strings.xml +++ b/cashapppay/src/main/res/values-sk-rSK/strings.xml @@ -8,5 +8,4 @@ Uložiť pre moju ďalšiu platbu - Platba sa spracúva. diff --git a/cashapppay/src/main/res/values-sl-rSI/strings.xml b/cashapppay/src/main/res/values-sl-rSI/strings.xml index e799889891..09bde2f79c 100644 --- a/cashapppay/src/main/res/values-sl-rSI/strings.xml +++ b/cashapppay/src/main/res/values-sl-rSI/strings.xml @@ -8,5 +8,4 @@ Shrani za moje naslednje plačilo - Obdelava plačila… diff --git a/cashapppay/src/main/res/values-sv-rSE/strings.xml b/cashapppay/src/main/res/values-sv-rSE/strings.xml index 085bc46c5c..3bf819354a 100644 --- a/cashapppay/src/main/res/values-sv-rSE/strings.xml +++ b/cashapppay/src/main/res/values-sv-rSE/strings.xml @@ -8,5 +8,4 @@ Spara till min nästa betalning - Behandlar betalning… diff --git a/cashapppay/src/main/res/values-zh-rCN/strings.xml b/cashapppay/src/main/res/values-zh-rCN/strings.xml index 012a1a675a..27a63acc09 100644 --- a/cashapppay/src/main/res/values-zh-rCN/strings.xml +++ b/cashapppay/src/main/res/values-zh-rCN/strings.xml @@ -8,5 +8,4 @@ 保存以便下次支付使用 - 正在处理付款… diff --git a/cashapppay/src/main/res/values-zh-rTW/strings.xml b/cashapppay/src/main/res/values-zh-rTW/strings.xml index 62265679dc..438cc5afb8 100644 --- a/cashapppay/src/main/res/values-zh-rTW/strings.xml +++ b/cashapppay/src/main/res/values-zh-rTW/strings.xml @@ -8,5 +8,4 @@ 儲存以供下次付款使用 - 正在處理付款…… diff --git a/cashapppay/src/main/res/values/strings.xml b/cashapppay/src/main/res/values/strings.xml index c3b37e4f2d..1243ed256a 100644 --- a/cashapppay/src/main/res/values/strings.xml +++ b/cashapppay/src/main/res/values/strings.xml @@ -8,5 +8,4 @@ Save for my next payment - Processing payment… diff --git a/twint/src/main/res/values-ar/strings.xml b/twint/src/main/res/values-ar/strings.xml index 3c676a010f..1b041cc74f 100644 --- a/twint/src/main/res/values-ar/strings.xml +++ b/twint/src/main/res/values-ar/strings.xml @@ -8,4 +8,4 @@ حفظ لمدفوعاتي القادمة - \ No newline at end of file + diff --git a/twint/src/main/res/values-bg-rBG/strings.xml b/twint/src/main/res/values-bg-rBG/strings.xml index b33c81e03f..962a51e8fa 100644 --- a/twint/src/main/res/values-bg-rBG/strings.xml +++ b/twint/src/main/res/values-bg-rBG/strings.xml @@ -8,4 +8,4 @@ Запазване за следващото ми плащане - \ No newline at end of file + diff --git a/twint/src/main/res/values-ca-rES/strings.xml b/twint/src/main/res/values-ca-rES/strings.xml index 2eb0bf4599..e3789995b0 100644 --- a/twint/src/main/res/values-ca-rES/strings.xml +++ b/twint/src/main/res/values-ca-rES/strings.xml @@ -8,4 +8,4 @@ Desa\'l per al meu proper pagament - \ No newline at end of file + diff --git a/twint/src/main/res/values-cs-rCZ/strings.xml b/twint/src/main/res/values-cs-rCZ/strings.xml index ea36c1927e..d842afd337 100644 --- a/twint/src/main/res/values-cs-rCZ/strings.xml +++ b/twint/src/main/res/values-cs-rCZ/strings.xml @@ -8,4 +8,4 @@ Uložit pro příští platby - \ No newline at end of file + diff --git a/twint/src/main/res/values-da-rDK/strings.xml b/twint/src/main/res/values-da-rDK/strings.xml index 83eaefc275..6ead94aac2 100644 --- a/twint/src/main/res/values-da-rDK/strings.xml +++ b/twint/src/main/res/values-da-rDK/strings.xml @@ -8,4 +8,4 @@ Gem til min næste betaling - \ No newline at end of file + diff --git a/twint/src/main/res/values-de-rDE/strings.xml b/twint/src/main/res/values-de-rDE/strings.xml index d957332637..a1c7f4ddfa 100644 --- a/twint/src/main/res/values-de-rDE/strings.xml +++ b/twint/src/main/res/values-de-rDE/strings.xml @@ -8,4 +8,4 @@ Für zukünftige Zahlvorgänge speichern - \ No newline at end of file + diff --git a/twint/src/main/res/values-el-rGR/strings.xml b/twint/src/main/res/values-el-rGR/strings.xml index f2b61e492a..6631def477 100644 --- a/twint/src/main/res/values-el-rGR/strings.xml +++ b/twint/src/main/res/values-el-rGR/strings.xml @@ -8,4 +8,4 @@ Αποθήκευση για την επόμενη πληρωμή μου - \ No newline at end of file + diff --git a/twint/src/main/res/values-es-rES/strings.xml b/twint/src/main/res/values-es-rES/strings.xml index 480120a6c0..a78f6b4999 100644 --- a/twint/src/main/res/values-es-rES/strings.xml +++ b/twint/src/main/res/values-es-rES/strings.xml @@ -8,4 +8,4 @@ Recordar para mi próximo pago - \ No newline at end of file + diff --git a/twint/src/main/res/values-et-rEE/strings.xml b/twint/src/main/res/values-et-rEE/strings.xml index 219eb4ee28..c3dc72ac75 100644 --- a/twint/src/main/res/values-et-rEE/strings.xml +++ b/twint/src/main/res/values-et-rEE/strings.xml @@ -8,4 +8,4 @@ Salvesta mu järgmise makse jaoks - \ No newline at end of file + diff --git a/twint/src/main/res/values-fi-rFI/strings.xml b/twint/src/main/res/values-fi-rFI/strings.xml index beb439e1af..97c6ca4835 100644 --- a/twint/src/main/res/values-fi-rFI/strings.xml +++ b/twint/src/main/res/values-fi-rFI/strings.xml @@ -8,4 +8,4 @@ Tallenna seuraavaa maksuani varten - \ No newline at end of file + diff --git a/twint/src/main/res/values-fr-rFR/strings.xml b/twint/src/main/res/values-fr-rFR/strings.xml index c636922f89..eeaf6ecee0 100644 --- a/twint/src/main/res/values-fr-rFR/strings.xml +++ b/twint/src/main/res/values-fr-rFR/strings.xml @@ -8,4 +8,4 @@ Sauvegarder pour mon prochain paiement - \ No newline at end of file + diff --git a/twint/src/main/res/values-hr-rHR/strings.xml b/twint/src/main/res/values-hr-rHR/strings.xml index e0a3103fc5..0c667e965b 100644 --- a/twint/src/main/res/values-hr-rHR/strings.xml +++ b/twint/src/main/res/values-hr-rHR/strings.xml @@ -8,4 +8,4 @@ Pohrani za moje sljedeće plaćanje - \ No newline at end of file + diff --git a/twint/src/main/res/values-hu-rHU/strings.xml b/twint/src/main/res/values-hu-rHU/strings.xml index 71aaa91f6e..4ce8ed203e 100644 --- a/twint/src/main/res/values-hu-rHU/strings.xml +++ b/twint/src/main/res/values-hu-rHU/strings.xml @@ -8,4 +8,4 @@ Mentés a következő fizetéshez - \ No newline at end of file + diff --git a/twint/src/main/res/values-is-rIS/strings.xml b/twint/src/main/res/values-is-rIS/strings.xml index 49a032ebef..bb8b6de721 100644 --- a/twint/src/main/res/values-is-rIS/strings.xml +++ b/twint/src/main/res/values-is-rIS/strings.xml @@ -8,4 +8,4 @@ Spara fyrir næstu greiðslu - \ No newline at end of file + diff --git a/twint/src/main/res/values-it-rIT/strings.xml b/twint/src/main/res/values-it-rIT/strings.xml index 1f450a5576..0547e381fd 100644 --- a/twint/src/main/res/values-it-rIT/strings.xml +++ b/twint/src/main/res/values-it-rIT/strings.xml @@ -8,4 +8,4 @@ Salva per il prossimo pagamento - \ No newline at end of file + diff --git a/twint/src/main/res/values-ja-rJP/strings.xml b/twint/src/main/res/values-ja-rJP/strings.xml index f3d599c38d..4661bf0e39 100644 --- a/twint/src/main/res/values-ja-rJP/strings.xml +++ b/twint/src/main/res/values-ja-rJP/strings.xml @@ -8,4 +8,4 @@ 次回のお支払いのため詳細を保存 - \ No newline at end of file + diff --git a/twint/src/main/res/values-ko-rKR/strings.xml b/twint/src/main/res/values-ko-rKR/strings.xml index ef67d17d60..11d6a13add 100644 --- a/twint/src/main/res/values-ko-rKR/strings.xml +++ b/twint/src/main/res/values-ko-rKR/strings.xml @@ -8,4 +8,4 @@ 다음 결제를 위해 이 수단 저장 - \ No newline at end of file + diff --git a/twint/src/main/res/values-lt-rLT/strings.xml b/twint/src/main/res/values-lt-rLT/strings.xml index 5584f3d058..735847f4d8 100644 --- a/twint/src/main/res/values-lt-rLT/strings.xml +++ b/twint/src/main/res/values-lt-rLT/strings.xml @@ -8,4 +8,4 @@ Išsaugoti kitam mokėjimui - \ No newline at end of file + diff --git a/twint/src/main/res/values-lv-rLV/strings.xml b/twint/src/main/res/values-lv-rLV/strings.xml index 02801275ac..e0175f1d77 100644 --- a/twint/src/main/res/values-lv-rLV/strings.xml +++ b/twint/src/main/res/values-lv-rLV/strings.xml @@ -8,4 +8,4 @@ Saglabāt manam nākamajam maksājumam - \ No newline at end of file + diff --git a/twint/src/main/res/values-nb-rNO/strings.xml b/twint/src/main/res/values-nb-rNO/strings.xml index 3be320881d..766f804875 100644 --- a/twint/src/main/res/values-nb-rNO/strings.xml +++ b/twint/src/main/res/values-nb-rNO/strings.xml @@ -8,4 +8,4 @@ Lagre til min neste betaling - \ No newline at end of file + diff --git a/twint/src/main/res/values-nl-rNL/strings.xml b/twint/src/main/res/values-nl-rNL/strings.xml index 47497632a2..ff4c9a96f8 100644 --- a/twint/src/main/res/values-nl-rNL/strings.xml +++ b/twint/src/main/res/values-nl-rNL/strings.xml @@ -8,4 +8,4 @@ Bewaar voor mijn volgende betaling - \ No newline at end of file + diff --git a/twint/src/main/res/values-pl-rPL/strings.xml b/twint/src/main/res/values-pl-rPL/strings.xml index c04ebef994..0572c31a14 100644 --- a/twint/src/main/res/values-pl-rPL/strings.xml +++ b/twint/src/main/res/values-pl-rPL/strings.xml @@ -8,4 +8,4 @@ Zapisz na potrzeby następnej płatności - \ No newline at end of file + diff --git a/twint/src/main/res/values-pt-rBR/strings.xml b/twint/src/main/res/values-pt-rBR/strings.xml index ed2bbccfe6..a6572eb2cf 100644 --- a/twint/src/main/res/values-pt-rBR/strings.xml +++ b/twint/src/main/res/values-pt-rBR/strings.xml @@ -8,4 +8,4 @@ Salvar para meu próximo pagamento - \ No newline at end of file + diff --git a/twint/src/main/res/values-pt-rPT/strings.xml b/twint/src/main/res/values-pt-rPT/strings.xml index b61951548e..2c50ac483e 100644 --- a/twint/src/main/res/values-pt-rPT/strings.xml +++ b/twint/src/main/res/values-pt-rPT/strings.xml @@ -8,4 +8,4 @@ Guardar para o meu próximo pagamento - \ No newline at end of file + diff --git a/twint/src/main/res/values-ro-rRO/strings.xml b/twint/src/main/res/values-ro-rRO/strings.xml index 612d0dbc24..102b51e538 100644 --- a/twint/src/main/res/values-ro-rRO/strings.xml +++ b/twint/src/main/res/values-ro-rRO/strings.xml @@ -8,4 +8,4 @@ Salvează pentru următoarea mea plată - \ No newline at end of file + diff --git a/twint/src/main/res/values-ru-rRU/strings.xml b/twint/src/main/res/values-ru-rRU/strings.xml index a0d065257e..cb4cefbc3d 100644 --- a/twint/src/main/res/values-ru-rRU/strings.xml +++ b/twint/src/main/res/values-ru-rRU/strings.xml @@ -8,4 +8,4 @@ Сохранить для следующего платежа - \ No newline at end of file + diff --git a/twint/src/main/res/values-sk-rSK/strings.xml b/twint/src/main/res/values-sk-rSK/strings.xml index 99b5522b90..41bfac319d 100644 --- a/twint/src/main/res/values-sk-rSK/strings.xml +++ b/twint/src/main/res/values-sk-rSK/strings.xml @@ -8,4 +8,4 @@ Uložiť pre moju ďalšiu platbu - \ No newline at end of file + diff --git a/twint/src/main/res/values-sl-rSI/strings.xml b/twint/src/main/res/values-sl-rSI/strings.xml index 36550aca6f..28c2174f42 100644 --- a/twint/src/main/res/values-sl-rSI/strings.xml +++ b/twint/src/main/res/values-sl-rSI/strings.xml @@ -8,4 +8,4 @@ Shrani za moje naslednje plačilo - \ No newline at end of file + diff --git a/twint/src/main/res/values-sv-rSE/strings.xml b/twint/src/main/res/values-sv-rSE/strings.xml index efbef0ed6c..6dd5e8ce64 100644 --- a/twint/src/main/res/values-sv-rSE/strings.xml +++ b/twint/src/main/res/values-sv-rSE/strings.xml @@ -8,4 +8,4 @@ Spara till min nästa betalning - \ No newline at end of file + diff --git a/twint/src/main/res/values-zh-rCN/strings.xml b/twint/src/main/res/values-zh-rCN/strings.xml index 2dc4996f5d..b7d8c86746 100644 --- a/twint/src/main/res/values-zh-rCN/strings.xml +++ b/twint/src/main/res/values-zh-rCN/strings.xml @@ -8,4 +8,4 @@ 保存以便下次支付使用 - \ No newline at end of file + diff --git a/twint/src/main/res/values-zh-rTW/strings.xml b/twint/src/main/res/values-zh-rTW/strings.xml index a84f33995d..2961a62b97 100644 --- a/twint/src/main/res/values-zh-rTW/strings.xml +++ b/twint/src/main/res/values-zh-rTW/strings.xml @@ -8,4 +8,4 @@ 儲存以供下次付款使用 - \ No newline at end of file + diff --git a/twint/src/main/res/values/strings.xml b/twint/src/main/res/values/strings.xml index e6745cc0fc..358a131969 100644 --- a/twint/src/main/res/values/strings.xml +++ b/twint/src/main/res/values/strings.xml @@ -8,4 +8,4 @@ Save for my next payment - \ No newline at end of file + diff --git a/ui-core/src/main/res/values-ar/strings.xml b/ui-core/src/main/res/values-ar/strings.xml index 0c600a31fe..27e07bc79c 100644 --- a/ui-core/src/main/res/values-ar/strings.xml +++ b/ui-core/src/main/res/values-ar/strings.xml @@ -55,4 +55,6 @@ أدخل العنوان يدويًا استخدم هذا العنوان العنوان مطلوب + + جارِ معالجة المدفوعات… diff --git a/ui-core/src/main/res/values-bg-rBG/strings.xml b/ui-core/src/main/res/values-bg-rBG/strings.xml index 60b9caff52..cbfe431112 100644 --- a/ui-core/src/main/res/values-bg-rBG/strings.xml +++ b/ui-core/src/main/res/values-bg-rBG/strings.xml @@ -55,4 +55,6 @@ Въведете адреса ръчно Използвайте този адрес Изисква се адрес + + Обработка на плащането… diff --git a/ui-core/src/main/res/values-ca-rES/strings.xml b/ui-core/src/main/res/values-ca-rES/strings.xml index 0dcd892455..bb82408927 100644 --- a/ui-core/src/main/res/values-ca-rES/strings.xml +++ b/ui-core/src/main/res/values-ca-rES/strings.xml @@ -55,4 +55,6 @@ Introduïu l\'adreça manualment Utilitza aquesta adreça Adreça obligatòria + + S\'esta processant el pagament… diff --git a/ui-core/src/main/res/values-cs-rCZ/strings.xml b/ui-core/src/main/res/values-cs-rCZ/strings.xml index 08a6d31a05..bf5cdd623f 100644 --- a/ui-core/src/main/res/values-cs-rCZ/strings.xml +++ b/ui-core/src/main/res/values-cs-rCZ/strings.xml @@ -55,4 +55,6 @@ Zadejte adresu ručně Použijte tuto adresu Požadovaná adresa + + Zpracování platby… diff --git a/ui-core/src/main/res/values-da-rDK/strings.xml b/ui-core/src/main/res/values-da-rDK/strings.xml index 16a357ebe7..6d7800ca30 100644 --- a/ui-core/src/main/res/values-da-rDK/strings.xml +++ b/ui-core/src/main/res/values-da-rDK/strings.xml @@ -55,4 +55,6 @@ Indtast adresse manuelt Brug denne adresse Adresse er påkrævet + + Behandler betaling… diff --git a/ui-core/src/main/res/values-de-rDE/strings.xml b/ui-core/src/main/res/values-de-rDE/strings.xml index c0f7f3fb4a..9375a2d105 100644 --- a/ui-core/src/main/res/values-de-rDE/strings.xml +++ b/ui-core/src/main/res/values-de-rDE/strings.xml @@ -55,4 +55,6 @@ Geben Sie die Adresse manuell ein Diese Adresse verwenden Adresse erforderlich + + Zahlung wird verarbeitet… diff --git a/ui-core/src/main/res/values-el-rGR/strings.xml b/ui-core/src/main/res/values-el-rGR/strings.xml index 9df7e3b258..6b72311c35 100644 --- a/ui-core/src/main/res/values-el-rGR/strings.xml +++ b/ui-core/src/main/res/values-el-rGR/strings.xml @@ -55,4 +55,6 @@ Εισαγάγετε τη διεύθυνση μη αυτόματα Χρησιμοποιήστε αυτήν τη διεύθυνση Απαιτείται διεύθυνση + + Επεξεργασία πληρωμής… diff --git a/ui-core/src/main/res/values-es-rES/strings.xml b/ui-core/src/main/res/values-es-rES/strings.xml index 56db8cd164..f04df842c8 100644 --- a/ui-core/src/main/res/values-es-rES/strings.xml +++ b/ui-core/src/main/res/values-es-rES/strings.xml @@ -55,4 +55,6 @@ Introduzca la dirección manualmente Usar esta dirección Se necesita la dirección + + Procesando pago… diff --git a/ui-core/src/main/res/values-et-rEE/strings.xml b/ui-core/src/main/res/values-et-rEE/strings.xml index be7feabe52..5102dad98c 100644 --- a/ui-core/src/main/res/values-et-rEE/strings.xml +++ b/ui-core/src/main/res/values-et-rEE/strings.xml @@ -55,4 +55,6 @@ Sisestage aadress käsitsi Kasuta seda aadressi Aadress on kohustuslik + + Makse töötlemine … diff --git a/ui-core/src/main/res/values-fi-rFI/strings.xml b/ui-core/src/main/res/values-fi-rFI/strings.xml index 40768d332f..2bed3074a4 100644 --- a/ui-core/src/main/res/values-fi-rFI/strings.xml +++ b/ui-core/src/main/res/values-fi-rFI/strings.xml @@ -55,4 +55,6 @@ Syötä osoite manuaalisesti Käytä tätä osoitetta Osoite vaaditaan + + Maksua käsitellään… diff --git a/ui-core/src/main/res/values-fr-rFR/strings.xml b/ui-core/src/main/res/values-fr-rFR/strings.xml index 7bfef50a70..0e190448d8 100644 --- a/ui-core/src/main/res/values-fr-rFR/strings.xml +++ b/ui-core/src/main/res/values-fr-rFR/strings.xml @@ -55,4 +55,6 @@ Saisissez l\'adresse manuellement Utiliser cette adresse Adresse requise + + Traitement du paiement en cours… diff --git a/ui-core/src/main/res/values-hr-rHR/strings.xml b/ui-core/src/main/res/values-hr-rHR/strings.xml index 8756b3c7f0..d78008307e 100644 --- a/ui-core/src/main/res/values-hr-rHR/strings.xml +++ b/ui-core/src/main/res/values-hr-rHR/strings.xml @@ -55,4 +55,6 @@ Ručno unesite adresu Koristi ovu adresu Potrebna je adresa + + Obrada plaćanja u tijeku… diff --git a/ui-core/src/main/res/values-hu-rHU/strings.xml b/ui-core/src/main/res/values-hu-rHU/strings.xml index 3fe5f079e7..b148728aca 100644 --- a/ui-core/src/main/res/values-hu-rHU/strings.xml +++ b/ui-core/src/main/res/values-hu-rHU/strings.xml @@ -55,4 +55,6 @@ Manuálisan írjon be egy címet Használja ezt a címet A cím megadása kötelező + + Fizetés feldolgozása… diff --git a/ui-core/src/main/res/values-is-rIS/strings.xml b/ui-core/src/main/res/values-is-rIS/strings.xml index 5357859c35..b41822122d 100644 --- a/ui-core/src/main/res/values-is-rIS/strings.xml +++ b/ui-core/src/main/res/values-is-rIS/strings.xml @@ -55,4 +55,6 @@ Slá inn heimilisfang handvirkt Nota þetta heimilisfang Heimilisfang er áskilið + + Unnið úr greiðslu… diff --git a/ui-core/src/main/res/values-it-rIT/strings.xml b/ui-core/src/main/res/values-it-rIT/strings.xml index 362587fe11..bf4ec27201 100644 --- a/ui-core/src/main/res/values-it-rIT/strings.xml +++ b/ui-core/src/main/res/values-it-rIT/strings.xml @@ -55,4 +55,6 @@ Inserisci l\'indirizzo manualmente Usa questo indirizzo Indirizzo richiesto + + Elaborazione del pagamento in corso… diff --git a/ui-core/src/main/res/values-ja-rJP/strings.xml b/ui-core/src/main/res/values-ja-rJP/strings.xml index f4779958bd..ac9b224306 100644 --- a/ui-core/src/main/res/values-ja-rJP/strings.xml +++ b/ui-core/src/main/res/values-ja-rJP/strings.xml @@ -55,4 +55,6 @@ 住所を手動で入力してください この住所を使用する 住所が必要です + + 支払いを処理しています… diff --git a/ui-core/src/main/res/values-ko-rKR/strings.xml b/ui-core/src/main/res/values-ko-rKR/strings.xml index 0ceaad2bd9..41a35e7022 100644 --- a/ui-core/src/main/res/values-ko-rKR/strings.xml +++ b/ui-core/src/main/res/values-ko-rKR/strings.xml @@ -55,4 +55,6 @@ 수동으로 주소 입력 이 주소 사용 주소 필수 + + 결제 처리 중… diff --git a/ui-core/src/main/res/values-lt-rLT/strings.xml b/ui-core/src/main/res/values-lt-rLT/strings.xml index 63643d7a4a..0d61c38d3f 100644 --- a/ui-core/src/main/res/values-lt-rLT/strings.xml +++ b/ui-core/src/main/res/values-lt-rLT/strings.xml @@ -55,4 +55,6 @@ Įveskite adresą rankiniu būdu Naudokite šį adresą Adresas privalomas + + Mokėjimas apdorojamas… diff --git a/ui-core/src/main/res/values-lv-rLV/strings.xml b/ui-core/src/main/res/values-lv-rLV/strings.xml index 75ad230807..de3ae5ef24 100644 --- a/ui-core/src/main/res/values-lv-rLV/strings.xml +++ b/ui-core/src/main/res/values-lv-rLV/strings.xml @@ -55,4 +55,6 @@ Ievadiet adresi manuāli Izmantot šo adresi Nepieciešama adrese + + Notiek maksājuma apstrāde… diff --git a/ui-core/src/main/res/values-nb-rNO/strings.xml b/ui-core/src/main/res/values-nb-rNO/strings.xml index 60b237c954..43b34dd4a1 100644 --- a/ui-core/src/main/res/values-nb-rNO/strings.xml +++ b/ui-core/src/main/res/values-nb-rNO/strings.xml @@ -55,4 +55,6 @@ Skriv inn adressen manuelt Bruk denne adressen Adresse er nødvendig + + Behandler betaling… diff --git a/ui-core/src/main/res/values-nl-rNL/strings.xml b/ui-core/src/main/res/values-nl-rNL/strings.xml index ddab9d7a2a..49d65bba1f 100644 --- a/ui-core/src/main/res/values-nl-rNL/strings.xml +++ b/ui-core/src/main/res/values-nl-rNL/strings.xml @@ -55,4 +55,6 @@ Voer het adres handmatig in Dit adres gebruiken Adres verplicht + + Betaling wordt verwerkt… diff --git a/ui-core/src/main/res/values-pl-rPL/strings.xml b/ui-core/src/main/res/values-pl-rPL/strings.xml index 0b1549741a..f7ba1e3212 100644 --- a/ui-core/src/main/res/values-pl-rPL/strings.xml +++ b/ui-core/src/main/res/values-pl-rPL/strings.xml @@ -55,4 +55,6 @@ Wprowadź adres ręcznie Użyj tego adresu Wymagane jest podanie adresu + + Przetwarzanie płatności… diff --git a/ui-core/src/main/res/values-pt-rBR/strings.xml b/ui-core/src/main/res/values-pt-rBR/strings.xml index f9c6125cd8..7fb23e3234 100644 --- a/ui-core/src/main/res/values-pt-rBR/strings.xml +++ b/ui-core/src/main/res/values-pt-rBR/strings.xml @@ -55,4 +55,6 @@ Inserir endereço manualmente Usar este endereço O endereço é obrigatório + + Processando pagamento… diff --git a/ui-core/src/main/res/values-pt-rPT/strings.xml b/ui-core/src/main/res/values-pt-rPT/strings.xml index 85748bf8ab..e53023132a 100644 --- a/ui-core/src/main/res/values-pt-rPT/strings.xml +++ b/ui-core/src/main/res/values-pt-rPT/strings.xml @@ -55,4 +55,6 @@ Introduza o endereço manualmente Utilize este endereço Endereço necessário + + A processar pagamento… diff --git a/ui-core/src/main/res/values-ro-rRO/strings.xml b/ui-core/src/main/res/values-ro-rRO/strings.xml index d4e2fba271..5622e9aeb0 100644 --- a/ui-core/src/main/res/values-ro-rRO/strings.xml +++ b/ui-core/src/main/res/values-ro-rRO/strings.xml @@ -55,4 +55,6 @@ Introduceți adresa manual Folosiți această adresă Adresa este necesară + + Se prelucrează plata… diff --git a/ui-core/src/main/res/values-ru-rRU/strings.xml b/ui-core/src/main/res/values-ru-rRU/strings.xml index 403d293a36..ddaf975f5b 100644 --- a/ui-core/src/main/res/values-ru-rRU/strings.xml +++ b/ui-core/src/main/res/values-ru-rRU/strings.xml @@ -55,4 +55,6 @@ Ввести адрес вручную Используйте этот адрес Требуется адрес + + Платеж обрабатывается… diff --git a/ui-core/src/main/res/values-sk-rSK/strings.xml b/ui-core/src/main/res/values-sk-rSK/strings.xml index e70c9c52cf..e1907ba92d 100644 --- a/ui-core/src/main/res/values-sk-rSK/strings.xml +++ b/ui-core/src/main/res/values-sk-rSK/strings.xml @@ -55,4 +55,6 @@ Manuálne zadajte adresu Použite túto adresu Adresa sa požaduje + + Platba sa spracúva. diff --git a/ui-core/src/main/res/values-sl-rSI/strings.xml b/ui-core/src/main/res/values-sl-rSI/strings.xml index e9ab25d9f0..313d5c7001 100644 --- a/ui-core/src/main/res/values-sl-rSI/strings.xml +++ b/ui-core/src/main/res/values-sl-rSI/strings.xml @@ -55,4 +55,6 @@ Naslov vnesite ročno Uporabite ta naslov Naslov je obvezen + + Obdelava plačila… diff --git a/ui-core/src/main/res/values-sv-rSE/strings.xml b/ui-core/src/main/res/values-sv-rSE/strings.xml index 549b056e98..a8853b8a33 100644 --- a/ui-core/src/main/res/values-sv-rSE/strings.xml +++ b/ui-core/src/main/res/values-sv-rSE/strings.xml @@ -55,4 +55,6 @@ Ange adress manuellt Använd denna adress Adress krävs + + Behandlar betalning… diff --git a/ui-core/src/main/res/values-zh-rCN/strings.xml b/ui-core/src/main/res/values-zh-rCN/strings.xml index d0aecfc55d..c2dbe72405 100644 --- a/ui-core/src/main/res/values-zh-rCN/strings.xml +++ b/ui-core/src/main/res/values-zh-rCN/strings.xml @@ -55,4 +55,6 @@ 手动输入地址 使用此地址 地址为必填项 + + 正在处理付款… diff --git a/ui-core/src/main/res/values-zh-rTW/strings.xml b/ui-core/src/main/res/values-zh-rTW/strings.xml index ac39b50ea0..02e9e7ab85 100644 --- a/ui-core/src/main/res/values-zh-rTW/strings.xml +++ b/ui-core/src/main/res/values-zh-rTW/strings.xml @@ -55,4 +55,6 @@ 手動輸入地址 使用此地址 必須填寫地址 + + 正在處理付款…… From db1e93571fc832750b198305aedde849b0a89d32 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Fri, 18 Oct 2024 14:19:54 +0200 Subject: [PATCH 28/44] Show loading state in Google Pay COAND-1012 --- .../internal/ui/DefaultGooglePayDelegate.kt | 6 ++++-- .../googlepay/internal/ui/GooglePayViewProvider.kt | 12 ++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index ae5f5807ae..201c37f58f 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -68,7 +68,7 @@ internal class DefaultGooglePayDelegate( override val submitFlow: Flow = submitHandler.submitFlow - private val _viewFlow = MutableStateFlow(GooglePayComponentViewType) + private val _viewFlow = MutableStateFlow(GooglePayComponentViewType) override val viewFlow: Flow = _viewFlow private var _coroutineScope: CoroutineScope? = null @@ -153,6 +153,9 @@ internal class DefaultGooglePayDelegate( override fun onSubmit() { adyenLog(AdyenLogLevel.DEBUG) { "onSubmit" } + + _viewFlow.tryEmit(PaymentInProgressViewType) + val paymentDataRequest = GooglePayUtils.createPaymentDataRequest(componentParams) val paymentDataTask = paymentsClient.loadPaymentData(paymentDataRequest) coroutineScope.launch { @@ -237,7 +240,6 @@ internal class DefaultGooglePayDelegate( return GooglePayButtonParameters(allowedPaymentMethods) } - @Suppress("USELESS_IS_CHECK") override fun isConfirmationRequired(): Boolean = _viewFlow.value is ButtonComponentViewType override fun shouldShowSubmitButton(): Boolean = isConfirmationRequired() && componentParams.isSubmitButtonVisible diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt index f6255bdb3d..927e380caf 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt @@ -16,14 +16,16 @@ 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 import com.adyen.checkout.ui.core.internal.ui.view.PayButton +import com.adyen.checkout.ui.core.internal.ui.view.ProcessingPaymentView -internal object GooglePayViewProvider : ViewProvider { +internal class GooglePayViewProvider : ViewProvider { override fun getView( viewType: ComponentViewType, context: Context, ): ComponentView = when (viewType) { GooglePayComponentViewType -> GooglePayView(context) + PaymentInProgressViewType -> ProcessingPaymentView(context) else -> throw IllegalArgumentException("Unsupported view type") } @@ -32,6 +34,7 @@ internal object GooglePayViewProvider : ViewProvider { layoutInflater: LayoutInflater ): ComponentView = when (viewType) { GooglePayComponentViewType -> GooglePayView(layoutInflater) + PaymentInProgressViewType -> ProcessingPaymentView(layoutInflater.context) else -> throw IllegalArgumentException("Unsupported view type") } } @@ -43,7 +46,12 @@ internal class GooglePayButtonViewProvider : ButtonViewProvider { internal object GooglePayComponentViewType : ButtonComponentViewType { override val buttonViewProvider: ButtonViewProvider get() = GooglePayButtonViewProvider() - override val viewProvider: ViewProvider = GooglePayViewProvider + override val viewProvider: ViewProvider get() = GooglePayViewProvider() override val buttonTextResId: Int = ButtonComponentViewType.DEFAULT_BUTTON_TEXT_RES_ID } + +internal object PaymentInProgressViewType : ComponentViewType { + + override val viewProvider: ViewProvider get() = GooglePayViewProvider() +} From bac4adf3c84315b79e1f7dec24670159d08d2193 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Fri, 18 Oct 2024 14:20:37 +0200 Subject: [PATCH 29/44] Show loading state in Twint COAND-1012 --- .../checkout/twint/internal/ui/DefaultTwintDelegate.kt | 2 ++ .../adyen/checkout/twint/internal/ui/TwintViewProvider.kt | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/twint/src/main/java/com/adyen/checkout/twint/internal/ui/DefaultTwintDelegate.kt b/twint/src/main/java/com/adyen/checkout/twint/internal/ui/DefaultTwintDelegate.kt index 4fb4c70b86..2b8788813d 100644 --- a/twint/src/main/java/com/adyen/checkout/twint/internal/ui/DefaultTwintDelegate.kt +++ b/twint/src/main/java/com/adyen/checkout/twint/internal/ui/DefaultTwintDelegate.kt @@ -159,6 +159,8 @@ internal class DefaultTwintDelegate( val event = GenericEvents.submit(paymentMethod.type.orEmpty()) analyticsManager.trackEvent(event) + _viewFlow.tryEmit(PaymentInProgressViewType) + val state = _componentStateFlow.value submitHandler.onSubmit(state = state) } diff --git a/twint/src/main/java/com/adyen/checkout/twint/internal/ui/TwintViewProvider.kt b/twint/src/main/java/com/adyen/checkout/twint/internal/ui/TwintViewProvider.kt index 15907709d4..8b6f33709d 100644 --- a/twint/src/main/java/com/adyen/checkout/twint/internal/ui/TwintViewProvider.kt +++ b/twint/src/main/java/com/adyen/checkout/twint/internal/ui/TwintViewProvider.kt @@ -14,11 +14,13 @@ 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 import com.adyen.checkout.ui.core.internal.ui.ViewProvider +import com.adyen.checkout.ui.core.internal.ui.view.ProcessingPaymentView internal class TwintViewProvider : ViewProvider { override fun getView(viewType: ComponentViewType, context: Context): ComponentView = when (viewType) { TwintComponentViewType -> TwintView(context) + PaymentInProgressViewType -> ProcessingPaymentView(context) else -> throw IllegalArgumentException("Unsupported view type") } } @@ -29,3 +31,8 @@ internal object TwintComponentViewType : ButtonComponentViewType { override val buttonTextResId: Int = ButtonComponentViewType.DEFAULT_BUTTON_TEXT_RES_ID } + +internal object PaymentInProgressViewType : ComponentViewType { + + override val viewProvider: ViewProvider get() = TwintViewProvider() +} From b5be4577dabede734b1d22aa1e3565480151a3e7 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Fri, 18 Oct 2024 14:42:52 +0200 Subject: [PATCH 30/44] Show loading state in iDeal COAND-1012 --- .../adyen/checkout/ideal/IdealComponent.kt | 12 ++++--- .../ideal/internal/ui/DefaultIdealDelegate.kt | 4 +++ .../ideal/internal/ui/IdealDelegate.kt | 6 +++- .../ideal/internal/ui/IdealViewProvider.kt | 31 +++++++++++++++++++ .../checkout/ideal/IdealComponentTest.kt | 27 +++++++++++++--- 5 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 ideal/src/main/java/com/adyen/checkout/ideal/internal/ui/IdealViewProvider.kt diff --git a/ideal/src/main/java/com/adyen/checkout/ideal/IdealComponent.kt b/ideal/src/main/java/com/adyen/checkout/ideal/IdealComponent.kt index 7a4d9111d7..0e0714abb6 100644 --- a/ideal/src/main/java/com/adyen/checkout/ideal/IdealComponent.kt +++ b/ideal/src/main/java/com/adyen/checkout/ideal/IdealComponent.kt @@ -26,6 +26,7 @@ import com.adyen.checkout.ideal.internal.provider.IdealComponentProvider import com.adyen.checkout.ideal.internal.ui.IdealDelegate 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 /** @@ -42,12 +43,13 @@ class IdealComponent internal constructor( ButtonComponent, ActionHandlingComponent by actionHandlingComponent { - @Suppress("ForbiddenComment") - // FIXME: Using actionHandlingComponent.activeDelegate will crash for QR code actions. This is a workaround for the - // actual issue. - override val delegate: ComponentDelegate get() = genericActionDelegate.delegate + override val delegate: ComponentDelegate get() = actionHandlingComponent.activeDelegate - override val viewFlow: Flow = genericActionDelegate.viewFlow + override val viewFlow: Flow = mergeViewFlows( + viewModelScope, + idealDelegate.viewFlow, + genericActionDelegate.viewFlow, + ) init { idealDelegate.initialize(viewModelScope) diff --git a/ideal/src/main/java/com/adyen/checkout/ideal/internal/ui/DefaultIdealDelegate.kt b/ideal/src/main/java/com/adyen/checkout/ideal/internal/ui/DefaultIdealDelegate.kt index ee91075d9e..f80589cc5a 100644 --- a/ideal/src/main/java/com/adyen/checkout/ideal/internal/ui/DefaultIdealDelegate.kt +++ b/ideal/src/main/java/com/adyen/checkout/ideal/internal/ui/DefaultIdealDelegate.kt @@ -23,6 +23,7 @@ import com.adyen.checkout.components.core.paymentmethod.IdealPaymentMethod import com.adyen.checkout.core.AdyenLogLevel import com.adyen.checkout.core.internal.util.adyenLog import com.adyen.checkout.ideal.IdealComponentState +import com.adyen.checkout.ui.core.internal.ui.ComponentViewType import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow @@ -43,6 +44,9 @@ internal class DefaultIdealDelegate( private val submitChannel: Channel = bufferedChannel() override val submitFlow: Flow = submitChannel.receiveAsFlow() + private val _viewFlow = MutableStateFlow(PaymentInProgressViewType) + override val viewFlow: Flow = _viewFlow + init { submitChannel.trySend(componentStateFlow.value) } diff --git a/ideal/src/main/java/com/adyen/checkout/ideal/internal/ui/IdealDelegate.kt b/ideal/src/main/java/com/adyen/checkout/ideal/internal/ui/IdealDelegate.kt index 472a8dc5d7..be0754166b 100644 --- a/ideal/src/main/java/com/adyen/checkout/ideal/internal/ui/IdealDelegate.kt +++ b/ideal/src/main/java/com/adyen/checkout/ideal/internal/ui/IdealDelegate.kt @@ -10,8 +10,12 @@ package com.adyen.checkout.ideal.internal.ui import com.adyen.checkout.components.core.internal.ui.PaymentComponentDelegate import com.adyen.checkout.ideal.IdealComponentState +import com.adyen.checkout.ui.core.internal.ui.ViewProvidingDelegate import kotlinx.coroutines.flow.Flow -internal interface IdealDelegate : PaymentComponentDelegate { +internal interface IdealDelegate : + PaymentComponentDelegate, + ViewProvidingDelegate { + val componentStateFlow: Flow } diff --git a/ideal/src/main/java/com/adyen/checkout/ideal/internal/ui/IdealViewProvider.kt b/ideal/src/main/java/com/adyen/checkout/ideal/internal/ui/IdealViewProvider.kt new file mode 100644 index 0000000000..270a55f1a0 --- /dev/null +++ b/ideal/src/main/java/com/adyen/checkout/ideal/internal/ui/IdealViewProvider.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 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 18/10/2024. + */ + +package com.adyen.checkout.ideal.internal.ui + +import android.content.Context +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 +import com.adyen.checkout.ui.core.internal.ui.view.ProcessingPaymentView + +internal class IdealViewProvider : ViewProvider { + + override fun getView( + viewType: ComponentViewType, + context: Context + ): ComponentView = when (viewType) { + PaymentInProgressViewType -> ProcessingPaymentView(context) + else -> throw IllegalArgumentException("Unsupported view type") + } +} + +internal object PaymentInProgressViewType : ComponentViewType { + + override val viewProvider: ViewProvider get() = IdealViewProvider() +} diff --git a/ideal/src/test/java/com/adyen/checkout/ideal/IdealComponentTest.kt b/ideal/src/test/java/com/adyen/checkout/ideal/IdealComponentTest.kt index 9b35778316..b73985bc0c 100644 --- a/ideal/src/test/java/com/adyen/checkout/ideal/IdealComponentTest.kt +++ b/ideal/src/test/java/com/adyen/checkout/ideal/IdealComponentTest.kt @@ -8,6 +8,7 @@ import com.adyen.checkout.action.core.internal.ui.GenericActionDelegate import com.adyen.checkout.components.core.internal.ComponentEventHandler import com.adyen.checkout.components.core.internal.PaymentComponentEvent import com.adyen.checkout.ideal.internal.ui.IdealDelegate +import com.adyen.checkout.ideal.internal.ui.PaymentInProgressViewType import com.adyen.checkout.test.LoggingExtension import com.adyen.checkout.test.TestDispatcherExtension import com.adyen.checkout.test.extensions.invokeOnCleared @@ -15,7 +16,6 @@ import com.adyen.checkout.ui.core.internal.ui.TestComponentViewType import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -40,6 +40,7 @@ internal class IdealComponentTest( @BeforeEach fun before() { + whenever(idealDelegate.viewFlow) doReturn MutableStateFlow(PaymentInProgressViewType) whenever(genericActionDelegate.viewFlow) doReturn MutableStateFlow(null) component = IdealComponent( @@ -86,9 +87,25 @@ internal class IdealComponentTest( } @Test - fun `when component is initialized then view flow should bu null`() = runTest { + fun `when component is initialized then view flow should match ideal delegate view flow`() = runTest { component.viewFlow.test { - assertNull(awaitItem()) + assertEquals(PaymentInProgressViewType, awaitItem()) + expectNoEvents() + } + } + + @Test + fun `when ideal delegate view flow emits a value then component view flow should match that value`() = runTest { + val idealDelegateViewFlow = MutableStateFlow(TestComponentViewType.VIEW_TYPE_1) + whenever(idealDelegate.viewFlow) doReturn idealDelegateViewFlow + component = IdealComponent(idealDelegate, genericActionDelegate, actionHandlingComponent, componentEventHandler) + + component.viewFlow.test { + assertEquals(TestComponentViewType.VIEW_TYPE_1, awaitItem()) + + idealDelegateViewFlow.emit(TestComponentViewType.VIEW_TYPE_2) + assertEquals(TestComponentViewType.VIEW_TYPE_2, awaitItem()) + expectNoEvents() } } @@ -105,7 +122,9 @@ internal class IdealComponentTest( ) component.viewFlow.test { - assertEquals(TestComponentViewType.VIEW_TYPE_1, awaitItem()) + // this value should match the value of the main delegate and not the action delegate + // and in practice the initial value of the action delegate view flow is always null so it should be ignored + assertEquals(PaymentInProgressViewType, awaitItem()) actionDelegateViewFlow.emit(TestComponentViewType.VIEW_TYPE_2) assertEquals(TestComponentViewType.VIEW_TYPE_2, awaitItem()) From 7ed18836365e6db7035f52857b924c18182e22c1 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Fri, 18 Oct 2024 14:43:10 +0200 Subject: [PATCH 31/44] Show loading state in instant payments COAND-1012 --- .../instant/InstantPaymentComponent.kt | 12 ++++--- .../ui/DefaultInstantPaymentDelegate.kt | 4 +++ .../internal/ui/InstantPaymentDelegate.kt | 6 +++- .../internal/ui/InstantPaymentViewProvider.kt | 31 ++++++++++++++++++ .../instant/InstantPaymentComponentTest.kt | 32 ++++++++++++++++--- 5 files changed, 75 insertions(+), 10 deletions(-) create mode 100644 instant/src/main/java/com/adyen/checkout/instant/internal/ui/InstantPaymentViewProvider.kt diff --git a/instant/src/main/java/com/adyen/checkout/instant/InstantPaymentComponent.kt b/instant/src/main/java/com/adyen/checkout/instant/InstantPaymentComponent.kt index f64efadebd..01c64d1d1b 100644 --- a/instant/src/main/java/com/adyen/checkout/instant/InstantPaymentComponent.kt +++ b/instant/src/main/java/com/adyen/checkout/instant/InstantPaymentComponent.kt @@ -17,6 +17,7 @@ import com.adyen.checkout.instant.internal.provider.InstantPaymentComponentProvi import com.adyen.checkout.instant.internal.ui.InstantPaymentDelegate 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 /** @@ -32,12 +33,13 @@ class InstantPaymentComponent internal constructor( ViewableComponent, ActionHandlingComponent by actionHandlingComponent { - @Suppress("ForbiddenComment") - // FIXME: Using actionHandlingComponent.activeDelegate will crash for QR code actions. This is a workaround for the - // actual issue. - override val delegate: ComponentDelegate get() = genericActionDelegate.delegate + override val delegate: ComponentDelegate get() = actionHandlingComponent.activeDelegate - override val viewFlow: Flow = genericActionDelegate.viewFlow + override val viewFlow: Flow = mergeViewFlows( + viewModelScope, + instantPaymentDelegate.viewFlow, + genericActionDelegate.viewFlow, + ) init { instantPaymentDelegate.initialize(viewModelScope) diff --git a/instant/src/main/java/com/adyen/checkout/instant/internal/ui/DefaultInstantPaymentDelegate.kt b/instant/src/main/java/com/adyen/checkout/instant/internal/ui/DefaultInstantPaymentDelegate.kt index 3fbea06e94..3c19bfa77c 100644 --- a/instant/src/main/java/com/adyen/checkout/instant/internal/ui/DefaultInstantPaymentDelegate.kt +++ b/instant/src/main/java/com/adyen/checkout/instant/internal/ui/DefaultInstantPaymentDelegate.kt @@ -25,6 +25,7 @@ import com.adyen.checkout.core.AdyenLogLevel import com.adyen.checkout.core.internal.util.adyenLog import com.adyen.checkout.instant.InstantComponentState import com.adyen.checkout.instant.internal.ui.model.InstantComponentParams +import com.adyen.checkout.ui.core.internal.ui.ComponentViewType import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow @@ -45,6 +46,9 @@ internal class DefaultInstantPaymentDelegate( private val submitChannel: Channel = bufferedChannel() override val submitFlow: Flow = submitChannel.receiveAsFlow() + private val _viewFlow = MutableStateFlow(PaymentInProgressViewType) + override val viewFlow: Flow = _viewFlow + init { submitChannel.trySend(componentStateFlow.value) } diff --git a/instant/src/main/java/com/adyen/checkout/instant/internal/ui/InstantPaymentDelegate.kt b/instant/src/main/java/com/adyen/checkout/instant/internal/ui/InstantPaymentDelegate.kt index d9b8bc4095..25ff922505 100644 --- a/instant/src/main/java/com/adyen/checkout/instant/internal/ui/InstantPaymentDelegate.kt +++ b/instant/src/main/java/com/adyen/checkout/instant/internal/ui/InstantPaymentDelegate.kt @@ -10,8 +10,12 @@ package com.adyen.checkout.instant.internal.ui import com.adyen.checkout.components.core.internal.ui.PaymentComponentDelegate import com.adyen.checkout.instant.InstantComponentState +import com.adyen.checkout.ui.core.internal.ui.ViewProvidingDelegate import kotlinx.coroutines.flow.Flow -internal interface InstantPaymentDelegate : PaymentComponentDelegate { +internal interface InstantPaymentDelegate : + PaymentComponentDelegate, + ViewProvidingDelegate { + val componentStateFlow: Flow } diff --git a/instant/src/main/java/com/adyen/checkout/instant/internal/ui/InstantPaymentViewProvider.kt b/instant/src/main/java/com/adyen/checkout/instant/internal/ui/InstantPaymentViewProvider.kt new file mode 100644 index 0000000000..dd7f7d653c --- /dev/null +++ b/instant/src/main/java/com/adyen/checkout/instant/internal/ui/InstantPaymentViewProvider.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 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 18/10/2024. + */ + +package com.adyen.checkout.instant.internal.ui + +import android.content.Context +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 +import com.adyen.checkout.ui.core.internal.ui.view.ProcessingPaymentView + +internal class InstantPaymentViewProvider : ViewProvider { + + override fun getView( + viewType: ComponentViewType, + context: Context + ): ComponentView = when (viewType) { + PaymentInProgressViewType -> ProcessingPaymentView(context) + else -> throw IllegalArgumentException("Unsupported view type") + } +} + +internal object PaymentInProgressViewType : ComponentViewType { + + override val viewProvider: ViewProvider get() = InstantPaymentViewProvider() +} diff --git a/instant/src/test/java/com/adyen/checkout/instant/InstantPaymentComponentTest.kt b/instant/src/test/java/com/adyen/checkout/instant/InstantPaymentComponentTest.kt index 0d4bb574a0..db08736bdf 100644 --- a/instant/src/test/java/com/adyen/checkout/instant/InstantPaymentComponentTest.kt +++ b/instant/src/test/java/com/adyen/checkout/instant/InstantPaymentComponentTest.kt @@ -16,6 +16,7 @@ import com.adyen.checkout.action.core.internal.ui.GenericActionDelegate import com.adyen.checkout.components.core.internal.ComponentEventHandler import com.adyen.checkout.components.core.internal.PaymentComponentEvent import com.adyen.checkout.instant.internal.ui.InstantPaymentDelegate +import com.adyen.checkout.instant.internal.ui.PaymentInProgressViewType import com.adyen.checkout.test.LoggingExtension import com.adyen.checkout.test.TestDispatcherExtension import com.adyen.checkout.test.extensions.invokeOnCleared @@ -23,7 +24,6 @@ import com.adyen.checkout.ui.core.internal.ui.TestComponentViewType import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -48,6 +48,7 @@ internal class InstantPaymentComponentTest( @BeforeEach fun before() { + whenever(instantPaymentDelegate.viewFlow) doReturn MutableStateFlow(PaymentInProgressViewType) whenever(genericActionDelegate.viewFlow) doReturn MutableStateFlow(null) component = InstantPaymentComponent( @@ -94,9 +95,30 @@ internal class InstantPaymentComponentTest( } @Test - fun `when component is initialized then view flow should bu null`() = runTest { + fun `when component is initialized then view flow should match instant delegate view flow`() = runTest { component.viewFlow.test { - assertNull(awaitItem()) + assertEquals(PaymentInProgressViewType, awaitItem()) + expectNoEvents() + } + } + + @Test + fun `when instant delegate view flow emits a value then component view flow should match that value`() = runTest { + val instantDelegateViewFlow = MutableStateFlow(TestComponentViewType.VIEW_TYPE_1) + whenever(instantPaymentDelegate.viewFlow) doReturn instantDelegateViewFlow + component = InstantPaymentComponent( + instantPaymentDelegate = instantPaymentDelegate, + genericActionDelegate = genericActionDelegate, + actionHandlingComponent = actionHandlingComponent, + componentEventHandler = componentEventHandler, + ) + + component.viewFlow.test { + assertEquals(TestComponentViewType.VIEW_TYPE_1, awaitItem()) + + instantDelegateViewFlow.emit(TestComponentViewType.VIEW_TYPE_2) + assertEquals(TestComponentViewType.VIEW_TYPE_2, awaitItem()) + expectNoEvents() } } @@ -113,7 +135,9 @@ internal class InstantPaymentComponentTest( ) component.viewFlow.test { - assertEquals(TestComponentViewType.VIEW_TYPE_1, awaitItem()) + // this value should match the value of the main delegate and not the action delegate + // and in practice the initial value of the action delegate view flow is always null so it should be ignored + assertEquals(PaymentInProgressViewType, awaitItem()) actionDelegateViewFlow.emit(TestComponentViewType.VIEW_TYPE_2) assertEquals(TestComponentViewType.VIEW_TYPE_2, awaitItem()) From 2d18d3c4fee3f37a3b15435dc6e76fca9a8e8cb4 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 23 Oct 2024 11:53:03 +0200 Subject: [PATCH 32/44] Remove loading indicator of internal GooglePayFragment COAND-1012 --- googlepay/src/main/res/layout/fragment_google_pay.xml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/googlepay/src/main/res/layout/fragment_google_pay.xml b/googlepay/src/main/res/layout/fragment_google_pay.xml index 1e20bfdcc9..6f03758752 100644 --- a/googlepay/src/main/res/layout/fragment_google_pay.xml +++ b/googlepay/src/main/res/layout/fragment_google_pay.xml @@ -8,10 +8,4 @@ - - - - + android:layout_height="wrap_content" /> From 0e34a067c687d8de3a2783a26bd199668e13d10c Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 23 Oct 2024 12:01:07 +0200 Subject: [PATCH 33/44] Add release note about changed styles and strings COAND-1012 --- RELEASE_NOTES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index fdcfd2a392..79d070fcdd 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -23,3 +23,9 @@ | | | ## Deprecated +- The styles and strings for the Cash App Pay loading indicator. Use the new styles and strings instead. +| Previous | Now | +|-----------------------------------------------------------|------------------------------------------------------------------| +| `AdyenCheckout.CashAppPay.ProgressBar` | `AdyenCheckout.ProcessingPaymentView.ProgressBar` | +| `AdyenCheckout.CashAppPay.WaitingDescriptionTextView` | `AdyenCheckout.ProcessingPaymentView.WaitingDescriptionTextView` | +| `cash_app_pay_waiting_text` | `checkout_processing_payment` | From 327085a771caa71a47bc58e247d827f29f09d366 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 30 Oct 2024 09:27:14 +0100 Subject: [PATCH 34/44] Correct deprecation messages COAND-941 --- .../java/com/adyen/checkout/googlepay/GooglePayComponent.kt | 2 +- .../adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt index 450e0bb7f2..6b65bed094 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt @@ -83,7 +83,7 @@ class GooglePayComponent internal constructor( * @param activity The activity to start the screen and later receive the result. * @param requestCode The code that will be returned on the [Activity.onActivityResult] */ - @Deprecated("Deprecated in favor of startGooglePayScreen()", ReplaceWith("submit()")) + @Deprecated("Deprecated in favor of submit()", ReplaceWith("submit()")) fun startGooglePayScreen(activity: Activity, requestCode: Int) { @Suppress("DEPRECATION") googlePayDelegate.startGooglePayScreen(activity, requestCode) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt index 309a96d08d..f815fe6371 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt @@ -35,7 +35,7 @@ internal interface GooglePayDelegate : val payEventFlow: Flow> - @Deprecated("Deprecated in favor of startGooglePayScreen()", ReplaceWith("onSubmit()")) + @Deprecated("Deprecated in favor of onSubmit()", ReplaceWith("onSubmit()")) fun startGooglePayScreen(activity: Activity, requestCode: Int) fun handleActivityResult(resultCode: Int, data: Intent?) From 6e4e598a5ec90a4f02414f4c00fb4bfe8b75097c Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 30 Oct 2024 15:02:37 +0100 Subject: [PATCH 35/44] Add coroutine scope to PayButton initialization COAND-943 --- .../cashapppay/internal/ui/view/CashAppPayButtonView.kt | 3 ++- .../checkout/googlepay/internal/ui/GooglePayButtonView.kt | 5 ++++- .../java/com/adyen/checkout/ui/core/AdyenComponentView.kt | 2 +- .../checkout/ui/core/internal/ui/view/DefaultPayButton.kt | 3 ++- .../com/adyen/checkout/ui/core/internal/ui/view/PayButton.kt | 3 ++- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/view/CashAppPayButtonView.kt b/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/view/CashAppPayButtonView.kt index 723facce10..727a1d8bc5 100644 --- a/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/view/CashAppPayButtonView.kt +++ b/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/view/CashAppPayButtonView.kt @@ -14,6 +14,7 @@ import android.view.LayoutInflater import com.adyen.checkout.cashapppay.databinding.CashAppPayButtonViewBinding import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate import com.adyen.checkout.ui.core.internal.ui.view.PayButton +import kotlinx.coroutines.CoroutineScope internal class CashAppPayButtonView @JvmOverloads constructor( context: Context, @@ -23,7 +24,7 @@ internal class CashAppPayButtonView @JvmOverloads constructor( private val binding = CashAppPayButtonViewBinding.inflate(LayoutInflater.from(context), this) - override fun initialize(delegate: ButtonDelegate) = Unit + override fun initialize(delegate: ButtonDelegate, coroutineScope: CoroutineScope) = Unit override fun setEnabled(enabled: Boolean) { binding.payButton.isEnabled = enabled diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt index 6b99b440b6..a9ee5b5999 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt @@ -19,6 +19,9 @@ import com.adyen.checkout.googlepay.databinding.ViewGooglePayButtonBinding import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate import com.adyen.checkout.ui.core.internal.ui.view.PayButton import com.google.android.gms.wallet.button.ButtonOptions +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach internal class GooglePayButtonView @JvmOverloads constructor( context: Context, @@ -49,7 +52,7 @@ internal class GooglePayButtonView @JvmOverloads constructor( typedArray.recycle() } - override fun initialize(delegate: ButtonDelegate) { + override fun initialize(delegate: ButtonDelegate, coroutineScope: CoroutineScope) { check(delegate is GooglePayDelegate) val buttonStyle = delegate.componentParams.googlePayButtonStyling 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 ac5a442fbd..26007deda0 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 @@ -147,7 +147,7 @@ class AdyenComponentView @JvmOverloads constructor( binding.frameLayoutButtonContainer.isVisible = buttonDelegate.shouldShowSubmitButton() val buttonView = (viewType as ButtonComponentViewType) .buttonViewProvider.getButton(context) - buttonView.initialize(buttonDelegate) + buttonView.initialize(buttonDelegate, coroutineScope) buttonView.setText(viewType, componentParams, localizedContext) buttonView.setOnClickListener { buttonDelegate.onSubmit() diff --git a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/DefaultPayButton.kt b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/DefaultPayButton.kt index a062e2567e..bf02a0dd19 100644 --- a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/DefaultPayButton.kt +++ b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/DefaultPayButton.kt @@ -13,6 +13,7 @@ import android.util.AttributeSet import android.view.LayoutInflater import com.adyen.checkout.ui.core.databinding.DefaultPayButtonViewBinding import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate +import kotlinx.coroutines.CoroutineScope internal class DefaultPayButton @JvmOverloads constructor( context: Context, @@ -22,7 +23,7 @@ internal class DefaultPayButton @JvmOverloads constructor( private val binding = DefaultPayButtonViewBinding.inflate(LayoutInflater.from(context), this) - override fun initialize(delegate: ButtonDelegate) = Unit + override fun initialize(delegate: ButtonDelegate, coroutineScope: CoroutineScope) = Unit override fun setEnabled(enabled: Boolean) { binding.payButton.isEnabled = enabled diff --git a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/PayButton.kt b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/PayButton.kt index 452c93659c..47aea907bb 100644 --- a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/PayButton.kt +++ b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/PayButton.kt @@ -13,6 +13,7 @@ import android.util.AttributeSet import android.widget.FrameLayout import androidx.annotation.RestrictTo import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate +import kotlinx.coroutines.CoroutineScope @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) abstract class PayButton( @@ -21,7 +22,7 @@ abstract class PayButton( defStyleAttr: Int, ) : FrameLayout(context, attrs, defStyleAttr) { - abstract fun initialize(delegate: ButtonDelegate) + abstract fun initialize(delegate: ButtonDelegate, coroutineScope: CoroutineScope) abstract override fun setEnabled(enabled: Boolean) From 65fa8edb2987c9b290a06563f08259fd7852d3ed Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 30 Oct 2024 15:04:28 +0100 Subject: [PATCH 36/44] Extract Google Pay availability into new class COAND-943 --- .../provider/GooglePayComponentProvider.kt | 33 ++--------- .../util/GooglePayAvailabilityCheck.kt | 55 +++++++++++++++++++ 2 files changed, 61 insertions(+), 27 deletions(-) create mode 100644 googlepay/src/main/java/com/adyen/checkout/googlepay/internal/util/GooglePayAvailabilityCheck.kt diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt index e6e094a0b0..885d6c9d12 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt @@ -31,17 +31,16 @@ import com.adyen.checkout.components.core.internal.ui.model.CommonComponentParam import com.adyen.checkout.components.core.internal.ui.model.DropInOverrideParams import com.adyen.checkout.components.core.internal.util.get import com.adyen.checkout.components.core.internal.util.viewModelFactory -import com.adyen.checkout.core.AdyenLogLevel import com.adyen.checkout.core.exception.CheckoutException import com.adyen.checkout.core.exception.ComponentException import com.adyen.checkout.core.internal.data.api.HttpClientFactory import com.adyen.checkout.core.internal.util.LocaleProvider -import com.adyen.checkout.core.internal.util.adyenLog import com.adyen.checkout.googlepay.GooglePayComponent import com.adyen.checkout.googlepay.GooglePayComponentState import com.adyen.checkout.googlepay.GooglePayConfiguration import com.adyen.checkout.googlepay.internal.ui.DefaultGooglePayDelegate import com.adyen.checkout.googlepay.internal.ui.model.GooglePayComponentParamsMapper +import com.adyen.checkout.googlepay.internal.util.GooglePayAvailabilityCheck import com.adyen.checkout.googlepay.internal.util.GooglePayUtils import com.adyen.checkout.googlepay.toCheckoutConfiguration import com.adyen.checkout.sessions.core.CheckoutSession @@ -54,10 +53,7 @@ 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.ui.SubmitHandler -import com.google.android.gms.common.ConnectionResult -import com.google.android.gms.common.GoogleApiAvailability import com.google.android.gms.wallet.Wallet -import java.lang.ref.WeakReference class GooglePayComponentProvider @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @@ -286,14 +282,6 @@ constructor( checkoutConfiguration: CheckoutConfiguration, callback: ComponentAvailableCallback ) { - if ( - GoogleApiAvailability.getInstance() - .isGooglePlayServicesAvailable(application) != ConnectionResult.SUCCESS - ) { - callback.onAvailabilityResult(false, paymentMethod) - return - } - val callbackWeakReference = WeakReference(callback) val componentParams = GooglePayComponentParamsMapper(CommonComponentParamsMapper()).mapToParams( checkoutConfiguration = checkoutConfiguration, deviceLocale = localeProvider.getLocale(application), @@ -302,20 +290,11 @@ constructor( paymentMethod = paymentMethod, ) - val paymentsClient = Wallet.getPaymentsClient(application, GooglePayUtils.createWalletOptions(componentParams)) - val readyToPayRequest = GooglePayUtils.createIsReadyToPayRequest(componentParams) - val readyToPayTask = paymentsClient.isReadyToPay(readyToPayRequest) - readyToPayTask.addOnSuccessListener { result -> - callbackWeakReference.get()?.onAvailabilityResult(result == true, paymentMethod) - } - readyToPayTask.addOnCanceledListener { - adyenLog(AdyenLogLevel.ERROR) { "GooglePay readyToPay task is cancelled." } - callbackWeakReference.get()?.onAvailabilityResult(false, paymentMethod) - } - readyToPayTask.addOnFailureListener { - adyenLog(AdyenLogLevel.ERROR, it) { "GooglePay readyToPay task is failed." } - callbackWeakReference.get()?.onAvailabilityResult(false, paymentMethod) - } + GooglePayAvailabilityCheck(application).isAvailable( + paymentMethod = paymentMethod, + componentParams = componentParams, + callback = callback, + ) } override fun isAvailable( diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/util/GooglePayAvailabilityCheck.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/util/GooglePayAvailabilityCheck.kt new file mode 100644 index 0000000000..80461df070 --- /dev/null +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/util/GooglePayAvailabilityCheck.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024 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 30/10/2024. + */ + +package com.adyen.checkout.googlepay.internal.util + +import android.app.Application +import com.adyen.checkout.components.core.ComponentAvailableCallback +import com.adyen.checkout.components.core.PaymentMethod +import com.adyen.checkout.core.AdyenLogLevel +import com.adyen.checkout.core.internal.util.adyenLog +import com.adyen.checkout.googlepay.internal.ui.model.GooglePayComponentParams +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.GoogleApiAvailability +import com.google.android.gms.wallet.Wallet +import java.lang.ref.WeakReference + +internal class GooglePayAvailabilityCheck( + private val application: Application, +) { + + fun isAvailable( + paymentMethod: PaymentMethod, + componentParams: GooglePayComponentParams, + callback: ComponentAvailableCallback, + ) { + if (GoogleApiAvailability.getInstance() + .isGooglePlayServicesAvailable(application) != ConnectionResult.SUCCESS + ) { + callback.onAvailabilityResult(false, paymentMethod) + return + } + + val callbackWeakReference = WeakReference(callback) + + val paymentsClient = Wallet.getPaymentsClient(application, GooglePayUtils.createWalletOptions(componentParams)) + val readyToPayRequest = GooglePayUtils.createIsReadyToPayRequest(componentParams) + val readyToPayTask = paymentsClient.isReadyToPay(readyToPayRequest) + readyToPayTask.addOnSuccessListener { result -> + callbackWeakReference.get()?.onAvailabilityResult(result == true, paymentMethod) + } + readyToPayTask.addOnCanceledListener { + adyenLog(AdyenLogLevel.ERROR) { "GooglePay readyToPay task is cancelled." } + callbackWeakReference.get()?.onAvailabilityResult(false, paymentMethod) + } + readyToPayTask.addOnFailureListener { + adyenLog(AdyenLogLevel.ERROR, it) { "GooglePay readyToPay task is failed." } + callbackWeakReference.get()?.onAvailabilityResult(false, paymentMethod) + } + } +} From 3610c9915fb3de3f4850557fab2b4ba48332c298 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 30 Oct 2024 15:05:44 +0100 Subject: [PATCH 37/44] Check Google Pay availability when creating the component COAND-943 --- .../core/ComponentAvailableCallback.kt | 2 +- .../core/PaymentMethodUnavailableException.kt | 16 ++++++++++++++++ .../googlepay/GooglePayUnavailableException.kt | 18 ++++++++++++++++++ .../provider/GooglePayComponentProvider.kt | 2 ++ .../internal/ui/DefaultGooglePayDelegate.kt | 16 ++++++++++++++++ 5 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 components-core/src/main/java/com/adyen/checkout/components/core/PaymentMethodUnavailableException.kt create mode 100644 googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayUnavailableException.kt 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 f5f2c00f36..f187228cea 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,6 +7,6 @@ */ package com.adyen.checkout.components.core -interface ComponentAvailableCallback { +fun interface ComponentAvailableCallback { fun onAvailabilityResult(isAvailable: Boolean, paymentMethod: PaymentMethod) } diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/PaymentMethodUnavailableException.kt b/components-core/src/main/java/com/adyen/checkout/components/core/PaymentMethodUnavailableException.kt new file mode 100644 index 0000000000..4b529e03af --- /dev/null +++ b/components-core/src/main/java/com/adyen/checkout/components/core/PaymentMethodUnavailableException.kt @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2024 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 30/10/2024. + */ + +package com.adyen.checkout.components.core + +import com.adyen.checkout.core.exception.CheckoutException + +open class PaymentMethodUnavailableException( + message: String, + cause: Throwable? = null +) : CheckoutException(message, cause) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayUnavailableException.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayUnavailableException.kt new file mode 100644 index 0000000000..b78803fe78 --- /dev/null +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayUnavailableException.kt @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2024 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 30/10/2024. + */ + +package com.adyen.checkout.googlepay + +import com.adyen.checkout.components.core.PaymentMethodUnavailableException + +class GooglePayUnavailableException( + cause: Throwable? = null, +) : PaymentMethodUnavailableException( + "Google Pay is not available", + cause, +) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt index 885d6c9d12..89d484ec28 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider.kt @@ -116,6 +116,7 @@ constructor( componentParams = componentParams, analyticsManager = analyticsManager, paymentsClient = paymentsClient, + googlePayAvailabilityCheck = GooglePayAvailabilityCheck(application), ) val genericActionDelegate = @@ -208,6 +209,7 @@ constructor( componentParams = componentParams, analyticsManager = analyticsManager, paymentsClient = paymentsClient, + googlePayAvailabilityCheck = GooglePayAvailabilityCheck(application), ) val genericActionDelegate = diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index 201c37f58f..bb7a0d2e3d 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -28,8 +28,10 @@ import com.adyen.checkout.core.internal.data.model.ModelUtils import com.adyen.checkout.core.internal.util.adyenLog import com.adyen.checkout.googlepay.GooglePayButtonParameters import com.adyen.checkout.googlepay.GooglePayComponentState +import com.adyen.checkout.googlepay.GooglePayUnavailableException import com.adyen.checkout.googlepay.internal.data.model.GooglePayPaymentMethodModel import com.adyen.checkout.googlepay.internal.ui.model.GooglePayComponentParams +import com.adyen.checkout.googlepay.internal.util.GooglePayAvailabilityCheck import com.adyen.checkout.googlepay.internal.util.GooglePayUtils import com.adyen.checkout.googlepay.internal.util.awaitTask import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType @@ -58,6 +60,7 @@ internal class DefaultGooglePayDelegate( override val componentParams: GooglePayComponentParams, private val analyticsManager: AnalyticsManager, private val paymentsClient: PaymentsClient, + private val googlePayAvailabilityCheck: GooglePayAvailabilityCheck, ) : GooglePayDelegate { private val _componentStateFlow = MutableStateFlow(createComponentState()) @@ -90,6 +93,19 @@ internal class DefaultGooglePayDelegate( val event = GenericEvents.rendered(paymentMethod.type.orEmpty()) analyticsManager.trackEvent(event) + + checkAvailability() + } + + private fun checkAvailability() { + googlePayAvailabilityCheck.isAvailable( + paymentMethod, + componentParams, + ) { isAvailable, _ -> + if (!isAvailable) { + exceptionChannel.trySend(GooglePayUnavailableException()) + } + } } override fun observe( From 68f2456ec91eb96ec8aa0afb66614d58e3170e63 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 30 Oct 2024 15:06:11 +0100 Subject: [PATCH 38/44] Hide Google Pay button when Google Pay is unavailable COAND-943 --- .../googlepay/internal/ui/GooglePayButtonView.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt index a9ee5b5999..4447fa7454 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt @@ -12,8 +12,10 @@ import android.content.Context import android.content.res.Resources import android.util.AttributeSet import android.view.LayoutInflater +import androidx.core.view.isVisible import com.adyen.checkout.googlepay.GooglePayButtonTheme import com.adyen.checkout.googlepay.GooglePayButtonType +import com.adyen.checkout.googlepay.GooglePayUnavailableException import com.adyen.checkout.googlepay.R import com.adyen.checkout.googlepay.databinding.ViewGooglePayButtonBinding import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate @@ -55,6 +57,8 @@ internal class GooglePayButtonView @JvmOverloads constructor( override fun initialize(delegate: ButtonDelegate, coroutineScope: CoroutineScope) { check(delegate is GooglePayDelegate) + observeDelegate(delegate, coroutineScope) + val buttonStyle = delegate.componentParams.googlePayButtonStyling val buttonType = buttonStyle?.buttonType ?: styledButtonType @@ -82,6 +86,16 @@ internal class GooglePayButtonView @JvmOverloads constructor( ) } + private fun observeDelegate(delegate: GooglePayDelegate, coroutineScope: CoroutineScope) { + delegate.exceptionFlow + .onEach { e -> + if (e is GooglePayUnavailableException) { + binding.payButton.isVisible = false + } + } + .launchIn(coroutineScope) + } + override fun setEnabled(enabled: Boolean) { binding.payButton.isEnabled = enabled } From 0c7b002e9588eb33b5fd14a59f662ea82c58ac08 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 30 Oct 2024 15:10:21 +0100 Subject: [PATCH 39/44] Convert ResultState into a data class This will allow to have customized states. The drawable will now also use it's original color. COAND-943 --- .../checkout/example/ui/compose/ResultContent.kt | 11 ++++------- .../adyen/checkout/example/ui/compose/ResultState.kt | 11 +++++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/compose/ResultContent.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/compose/ResultContent.kt index c3676375b6..1c15cc0408 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/compose/ResultContent.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/compose/ResultContent.kt @@ -20,7 +20,9 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.adyen.checkout.example.ui.theme.ExampleTheme @@ -35,19 +37,14 @@ internal fun ResultContent( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { - val tint = when (resultState) { - ResultState.SUCCESS -> ExampleTheme.customColors.success - ResultState.PENDING -> ExampleTheme.customColors.warning - ResultState.FAILURE -> MaterialTheme.colorScheme.error - } Icon( painter = painterResource(id = resultState.drawable), contentDescription = null, - tint = tint, + tint = Color.Unspecified, modifier = Modifier.size(100.dp), ) Spacer(modifier = Modifier.height(ExampleTheme.dimensions.grid_2)) - Text(text = resultState.text, style = MaterialTheme.typography.displaySmall) + Text(text = resultState.text, style = MaterialTheme.typography.displaySmall, textAlign = TextAlign.Center) } } diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/compose/ResultState.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/compose/ResultState.kt index 2debb57da3..7ba1de3d56 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/compose/ResultState.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/compose/ResultState.kt @@ -10,11 +10,14 @@ package com.adyen.checkout.example.ui.compose import com.adyen.checkout.example.R -enum class ResultState( +data class ResultState( val drawable: Int, val text: String, ) { - SUCCESS(R.drawable.ic_result_success, "Payment successful!"), - PENDING(R.drawable.ic_result_pending, "Payment pending..."), - FAILURE(R.drawable.ic_result_failure, "Payment failed..."), + + companion object { + val SUCCESS = ResultState(R.drawable.ic_result_success, "Payment successful!") + val PENDING = ResultState(R.drawable.ic_result_pending, "Payment pending...") + val FAILURE = ResultState(R.drawable.ic_result_failure, "Payment failed...") + } } From a7620ed21ffa1ae28eea5b3e3320e43580faa84f Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 30 Oct 2024 15:10:57 +0100 Subject: [PATCH 40/44] Remove manual availability check from example integration COAND-943 --- .../compose/SessionsGooglePayViewModel.kt | 54 ++++++++----------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayViewModel.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayViewModel.kt index 5d5df697b4..09a7fcfaa9 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayViewModel.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/compose/SessionsGooglePayViewModel.kt @@ -8,18 +8,16 @@ package com.adyen.checkout.example.ui.googlepay.compose -import android.app.Application import android.content.Intent import android.util.Log import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.adyen.checkout.components.core.CheckoutConfiguration -import com.adyen.checkout.components.core.ComponentAvailableCallback import com.adyen.checkout.components.core.ComponentError -import com.adyen.checkout.components.core.PaymentMethod import com.adyen.checkout.components.core.PaymentMethodTypes import com.adyen.checkout.components.core.action.Action +import com.adyen.checkout.example.R import com.adyen.checkout.example.data.storage.KeyValueStorage import com.adyen.checkout.example.extensions.IODispatcher import com.adyen.checkout.example.extensions.getLogTag @@ -28,8 +26,8 @@ import com.adyen.checkout.example.service.getSessionRequest import com.adyen.checkout.example.service.getSettingsInstallmentOptionsMode import com.adyen.checkout.example.ui.compose.ResultState import com.adyen.checkout.example.ui.configuration.CheckoutConfigurationProvider -import com.adyen.checkout.googlepay.GooglePayComponent import com.adyen.checkout.googlepay.GooglePayComponentState +import com.adyen.checkout.googlepay.GooglePayUnavailableException import com.adyen.checkout.sessions.core.CheckoutSession import com.adyen.checkout.sessions.core.CheckoutSessionProvider import com.adyen.checkout.sessions.core.CheckoutSessionResult @@ -49,13 +47,11 @@ import javax.inject.Inject @HiltViewModel internal class SessionsGooglePayViewModel @Inject constructor( private val savedStateHandle: SavedStateHandle, - private val application: Application, private val paymentsRepository: PaymentsRepository, private val keyValueStorage: KeyValueStorage, checkoutConfigurationProvider: CheckoutConfigurationProvider, ) : ViewModel(), - SessionComponentCallback, - ComponentAvailableCallback { + SessionComponentCallback { private val checkoutConfiguration = checkoutConfigurationProvider.checkoutConfig @@ -92,7 +88,7 @@ internal class SessionsGooglePayViewModel @Inject constructor( ) updateEvent { SessionsGooglePayEvents.ComponentData(componentData) } - checkGooglePayAvailability(paymentMethod, checkoutConfiguration) + updateState { SessionsGooglePayState.ShowButton } } private suspend fun getSession(paymentMethodType: String): CheckoutSession? { @@ -128,35 +124,19 @@ internal class SessionsGooglePayViewModel @Inject constructor( } } - private fun checkGooglePayAvailability( - paymentMethod: PaymentMethod, - checkoutConfiguration: CheckoutConfiguration, - ) { - GooglePayComponent.PROVIDER.isAvailable( - application = application, - paymentMethod = paymentMethod, - checkoutConfiguration = checkoutConfiguration, - callback = this, - ) - } - - override fun onAvailabilityResult(isAvailable: Boolean, paymentMethod: PaymentMethod) { - viewModelScope.launch { - if (isAvailable) { - updateState { SessionsGooglePayState.ShowButton } - } else { - onError() - } - } - } - override fun onAction(action: Action) { updateEvent { SessionsGooglePayEvents.Action(action) } } override fun onError(componentError: ComponentError) { - Log.e(TAG, "Component error occurred") - onError() + val exception = componentError.exception + Log.e(TAG, "Component error occurred", exception) + + if (exception is GooglePayUnavailableException) { + onGooglePayUnavailable() + } else { + onError() + } } override fun onFinished(result: SessionPaymentResult) { @@ -171,6 +151,16 @@ internal class SessionsGooglePayViewModel @Inject constructor( else -> ResultState.FAILURE } + private fun onGooglePayUnavailable() { + updateState { + val result = ResultState( + R.drawable.ic_result_failure, + "Google Pay is not available on this device", + ) + SessionsGooglePayState.FinalResult(result) + } + } + private fun onError() { updateState { SessionsGooglePayState.FinalResult(ResultState.FAILURE) } } From bf0847cedb0cee59300933318d98deaebc6e3247 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 30 Oct 2024 16:45:50 +0100 Subject: [PATCH 41/44] Use output data to manage internal state Exception flow didn't work as the value was only received by one observer. COAND-943 --- .../internal/ui/DefaultGooglePayDelegate.kt | 51 ++++++++++++--- .../internal/ui/GooglePayButtonView.kt | 9 +-- .../internal/ui/GooglePayDelegate.kt | 3 + .../internal/ui/model/GooglePayOutputData.kt | 16 +++++ .../ui/DefaultGooglePayDelegateTest.kt | 63 +++++++++++++++++-- 5 files changed, 123 insertions(+), 19 deletions(-) create mode 100644 googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayOutputData.kt diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index bb7a0d2e3d..e230a18407 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -31,6 +31,7 @@ import com.adyen.checkout.googlepay.GooglePayComponentState import com.adyen.checkout.googlepay.GooglePayUnavailableException import com.adyen.checkout.googlepay.internal.data.model.GooglePayPaymentMethodModel import com.adyen.checkout.googlepay.internal.ui.model.GooglePayComponentParams +import com.adyen.checkout.googlepay.internal.ui.model.GooglePayOutputData import com.adyen.checkout.googlepay.internal.util.GooglePayAvailabilityCheck import com.adyen.checkout.googlepay.internal.util.GooglePayUtils import com.adyen.checkout.googlepay.internal.util.awaitTask @@ -63,6 +64,13 @@ internal class DefaultGooglePayDelegate( private val googlePayAvailabilityCheck: GooglePayAvailabilityCheck, ) : GooglePayDelegate { + private val _outputDataFlow = MutableStateFlow(createOutputData()) + override val outputDataFlow: Flow = _outputDataFlow + private val outputData: GooglePayOutputData get() = _outputDataFlow.value + + private val _viewFlow = MutableStateFlow(GooglePayComponentViewType) + override val viewFlow: Flow = _viewFlow + private val _componentStateFlow = MutableStateFlow(createComponentState()) override val componentStateFlow: Flow = _componentStateFlow @@ -71,9 +79,6 @@ internal class DefaultGooglePayDelegate( override val submitFlow: Flow = submitHandler.submitFlow - private val _viewFlow = MutableStateFlow(GooglePayComponentViewType) - override val viewFlow: Flow = _viewFlow - private var _coroutineScope: CoroutineScope? = null private val coroutineScope: CoroutineScope get() = requireNotNull(_coroutineScope) @@ -102,6 +107,8 @@ internal class DefaultGooglePayDelegate( paymentMethod, componentParams, ) { isAvailable, _ -> + updateOutputData(isButtonVisible = isAvailable) + if (!isAvailable) { exceptionChannel.trySend(GooglePayUnavailableException()) } @@ -127,14 +134,36 @@ internal class DefaultGooglePayDelegate( observerRepository.removeObservers() } + private fun updateOutputData( + isButtonVisible: Boolean = this.outputData.isButtonVisible, + paymentData: PaymentData? = this.outputData.paymentData, + ) { + val newOutputData = createOutputData(isButtonVisible, paymentData) + _outputDataFlow.tryEmit(newOutputData) + updateComponentState(newOutputData) + } + + private fun createOutputData( + isButtonVisible: Boolean = componentParams.isSubmitButtonVisible, + paymentData: PaymentData? = null, + ): GooglePayOutputData { + return GooglePayOutputData( + isButtonVisible = isButtonVisible, + paymentData = paymentData, + ) + } + @VisibleForTesting - internal fun updateComponentState(paymentData: PaymentData?) { + internal fun updateComponentState(outputData: GooglePayOutputData) { adyenLog(AdyenLogLevel.VERBOSE) { "updateComponentState" } - val componentState = createComponentState(paymentData) + val componentState = createComponentState(outputData) _componentStateFlow.tryEmit(componentState) } - private fun createComponentState(paymentData: PaymentData? = null): GooglePayComponentState { + private fun createComponentState( + outputData: GooglePayOutputData = this.outputData + ): GooglePayComponentState { + val paymentData = outputData.paymentData val isValid = paymentData?.let { GooglePayUtils.findToken(it).isNotEmpty() } ?: false @@ -150,10 +179,16 @@ internal class DefaultGooglePayDelegate( amount = componentParams.amount, ) + val isReady = if (shouldShowSubmitButton()) { + outputData.isButtonVisible + } else { + true + } + return GooglePayComponentState( data = paymentComponentData, isInputValid = isValid, - isReady = true, + isReady = isReady, paymentData = paymentData, ) } @@ -243,7 +278,7 @@ internal class DefaultGooglePayDelegate( val event = GenericEvents.submit(paymentMethod.type.orEmpty()) analyticsManager.trackEvent(event) - updateComponentState(paymentData) + updateOutputData(paymentData = paymentData) submitHandler.onSubmit(_componentStateFlow.value) } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt index 4447fa7454..e0fc29e983 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt @@ -15,7 +15,6 @@ import android.view.LayoutInflater import androidx.core.view.isVisible import com.adyen.checkout.googlepay.GooglePayButtonTheme import com.adyen.checkout.googlepay.GooglePayButtonType -import com.adyen.checkout.googlepay.GooglePayUnavailableException import com.adyen.checkout.googlepay.R import com.adyen.checkout.googlepay.databinding.ViewGooglePayButtonBinding import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate @@ -87,11 +86,9 @@ internal class GooglePayButtonView @JvmOverloads constructor( } private fun observeDelegate(delegate: GooglePayDelegate, coroutineScope: CoroutineScope) { - delegate.exceptionFlow - .onEach { e -> - if (e is GooglePayUnavailableException) { - binding.payButton.isVisible = false - } + delegate.outputDataFlow + .onEach { + binding.payButton.isVisible = it.isButtonVisible } .launchIn(coroutineScope) } diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt index f815fe6371..f2f3f70a89 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt @@ -15,6 +15,7 @@ import com.adyen.checkout.core.exception.CheckoutException import com.adyen.checkout.googlepay.GooglePayButtonParameters import com.adyen.checkout.googlepay.GooglePayComponentState import com.adyen.checkout.googlepay.internal.ui.model.GooglePayComponentParams +import com.adyen.checkout.googlepay.internal.ui.model.GooglePayOutputData import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate import com.adyen.checkout.ui.core.internal.ui.ViewProvidingDelegate import com.google.android.gms.tasks.Task @@ -29,6 +30,8 @@ internal interface GooglePayDelegate : override val componentParams: GooglePayComponentParams + val outputDataFlow: Flow + val componentStateFlow: Flow val exceptionFlow: Flow diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayOutputData.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayOutputData.kt new file mode 100644 index 0000000000..0d8a1fb7d6 --- /dev/null +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/model/GooglePayOutputData.kt @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2024 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 30/10/2024. + */ + +package com.adyen.checkout.googlepay.internal.ui.model + +import com.google.android.gms.wallet.PaymentData + +internal data class GooglePayOutputData( + val isButtonVisible: Boolean, + val paymentData: PaymentData?, +) diff --git a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt index 8859ccab67..abdb7ac180 100644 --- a/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt +++ b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt @@ -11,6 +11,7 @@ package com.adyen.checkout.googlepay.internal.ui import app.cash.turbine.test import com.adyen.checkout.components.core.Amount import com.adyen.checkout.components.core.CheckoutConfiguration +import com.adyen.checkout.components.core.ComponentAvailableCallback import com.adyen.checkout.components.core.Configuration import com.adyen.checkout.components.core.OrderRequest import com.adyen.checkout.components.core.PaymentMethod @@ -19,11 +20,15 @@ import com.adyen.checkout.components.core.internal.analytics.GenericEvents import com.adyen.checkout.components.core.internal.analytics.TestAnalyticsManager import com.adyen.checkout.components.core.internal.ui.model.CommonComponentParamsMapper import com.adyen.checkout.core.Environment +import com.adyen.checkout.core.exception.CheckoutException import com.adyen.checkout.core.exception.ComponentException import com.adyen.checkout.googlepay.GooglePayComponentState import com.adyen.checkout.googlepay.GooglePayConfiguration +import com.adyen.checkout.googlepay.GooglePayUnavailableException import com.adyen.checkout.googlepay.googlePay import com.adyen.checkout.googlepay.internal.ui.model.GooglePayComponentParamsMapper +import com.adyen.checkout.googlepay.internal.ui.model.GooglePayOutputData +import com.adyen.checkout.googlepay.internal.util.GooglePayAvailabilityCheck import com.adyen.checkout.googlepay.internal.util.GooglePayUtils import com.adyen.checkout.test.LoggingExtension import com.adyen.checkout.test.extensions.test @@ -54,6 +59,7 @@ import org.junit.jupiter.params.provider.MethodSource import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -64,6 +70,7 @@ import java.util.Locale internal class DefaultGooglePayDelegateTest( @Mock private val submitHandler: SubmitHandler, @Mock private val paymentsClient: PaymentsClient, + @Mock private val googlePayAvailabilityCheck: GooglePayAvailabilityCheck, ) { private lateinit var analyticsManager: TestAnalyticsManager @@ -93,7 +100,7 @@ internal class DefaultGooglePayDelegateTest( @Test fun `when payment data is null, then state is not valid`() = runTest { delegate.componentStateFlow.test { - delegate.updateComponentState(null) + delegate.updateComponentState(createOutputData(paymentData = null)) with(awaitItem()) { assertNull(data.paymentMethod) @@ -113,7 +120,7 @@ internal class DefaultGooglePayDelegateTest( val paymentData = TEST_PAYMENT_DATA - delegate.updateComponentState(paymentData) + delegate.updateComponentState(createOutputData(paymentData = paymentData)) val componentState = awaitItem() @@ -163,7 +170,7 @@ internal class DefaultGooglePayDelegateTest( } delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) delegate.componentStateFlow.test { - delegate.updateComponentState(TEST_PAYMENT_DATA) + delegate.updateComponentState(createOutputData(paymentData = TEST_PAYMENT_DATA)) assertEquals(expectedComponentStateValue, expectMostRecentItem().data.amount) } } @@ -238,7 +245,7 @@ internal class DefaultGooglePayDelegateTest( delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) delegate.componentStateFlow.test { - delegate.updateComponentState(TEST_PAYMENT_DATA) + delegate.updateComponentState(createOutputData(paymentData = TEST_PAYMENT_DATA)) assertEquals(TEST_CHECKOUT_ATTEMPT_ID, expectMostRecentItem().data.paymentMethod?.checkoutAttemptId) } @@ -247,7 +254,7 @@ internal class DefaultGooglePayDelegateTest( @Test fun `when payment is successful and the data is valid, then submit event is tracked`() { delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) - delegate.updateComponentState(TEST_PAYMENT_DATA) + delegate.updateComponentState(createOutputData(paymentData = TEST_PAYMENT_DATA)) val result = ApiTaskResult(TEST_PAYMENT_DATA, Status.RESULT_SUCCESS) delegate.handlePaymentResult(result) @@ -264,6 +271,37 @@ internal class DefaultGooglePayDelegateTest( } } + @ParameterizedTest + @MethodSource("googlePayAvailableSource") + fun `when checking Google Pay availability, then expect isReady and-or exception`( + isSubmitButtonVisible: Boolean, + isAvailable: Boolean, + expectedIsReady: Boolean, + expectedException: CheckoutException?, + ) = runTest { + whenever(googlePayAvailabilityCheck.isAvailable(any(), any(), any())) doAnswer { invocation -> + (invocation.getArgument(2, ComponentAvailableCallback::class.java)) + .onAvailabilityResult(isAvailable, PaymentMethod()) + } + + val config = createCheckoutConfiguration { + setSubmitButtonVisible(isSubmitButtonVisible) + } + delegate = createGooglePayDelegate(config) + val componentStateFlow = delegate.componentStateFlow.test(testScheduler) + val exceptionFlow = delegate.exceptionFlow.test(testScheduler) + + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + assertEquals(expectedIsReady, componentStateFlow.latestValue.isReady) + + if (expectedException != null) { + assertEquals(expectedException.message, exceptionFlow.latestValue.message) + } else { + assertTrue(exceptionFlow.values.isEmpty()) + } + } + @ParameterizedTest @MethodSource("paymentResultSource") fun `when handling payment result, then success or error is emitted`( @@ -309,9 +347,15 @@ internal class DefaultGooglePayDelegateTest( .mapToParams(configuration, Locale.US, null, null, paymentMethod), analyticsManager = analyticsManager, paymentsClient = paymentsClient, + googlePayAvailabilityCheck = googlePayAvailabilityCheck, ) } + private fun createOutputData( + isButtonVisible: Boolean = false, + paymentData: PaymentData? = null, + ) = GooglePayOutputData(isButtonVisible, paymentData) + companion object { private val TEST_ORDER = OrderRequest("PSP", "ORDER_DATA") private const val TEST_CHECKOUT_ATTEMPT_ID = "TEST_CHECKOUT_ATTEMPT_ID" @@ -337,5 +381,14 @@ internal class DefaultGooglePayDelegateTest( arguments(ApiTaskResult(null, Status.RESULT_INTERRUPTED), false), arguments(ApiTaskResult(null, Status(AutoResolveHelper.RESULT_ERROR)), false), ) + + @JvmStatic + fun googlePayAvailableSource() = listOf( + // isSubmitButtonVisible, isAvailable, expectedIsReady, expectedException + arguments(false, false, true, GooglePayUnavailableException()), + arguments(false, true, true, null), + arguments(true, false, false, GooglePayUnavailableException()), + arguments(true, true, true, null), + ) } } From e65d1d5d8742743ba23ca7fa1f62cda45050611e Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Thu, 31 Oct 2024 10:09:51 +0100 Subject: [PATCH 42/44] Dump public api spec COAND-943 --- components-core/api/components-core.api | 5 +++++ googlepay/api/googlepay.api | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/components-core/api/components-core.api b/components-core/api/components-core.api index 6665b06e63..d166078a63 100644 --- a/components-core/api/components-core.api +++ b/components-core/api/components-core.api @@ -935,6 +935,11 @@ public final class com/adyen/checkout/components/core/PaymentMethodTypes { public final fun getUNSUPPORTED_PAYMENT_METHODS ()Ljava/util/List; } +public class com/adyen/checkout/components/core/PaymentMethodUnavailableException : com/adyen/checkout/core/exception/CheckoutException { + public fun (Ljava/lang/String;Ljava/lang/Throwable;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + public final class com/adyen/checkout/components/core/PaymentMethodsApiResponse : com/adyen/checkout/core/internal/data/model/ModelObject { public static final field CREATOR Landroid/os/Parcelable$Creator; public static final field Companion Lcom/adyen/checkout/components/core/PaymentMethodsApiResponse$Companion; diff --git a/googlepay/api/googlepay.api b/googlepay/api/googlepay.api index a56954af9c..71125b8620 100644 --- a/googlepay/api/googlepay.api +++ b/googlepay/api/googlepay.api @@ -227,6 +227,12 @@ public final class com/adyen/checkout/googlepay/GooglePayConfigurationKt { public static synthetic fun googlePay$default (Lcom/adyen/checkout/components/core/CheckoutConfiguration;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/adyen/checkout/components/core/CheckoutConfiguration; } +public final class com/adyen/checkout/googlepay/GooglePayUnavailableException : com/adyen/checkout/components/core/PaymentMethodUnavailableException { + public fun ()V + public fun (Ljava/lang/Throwable;)V + public synthetic fun (Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + public final class com/adyen/checkout/googlepay/MerchantInfo : com/adyen/checkout/core/internal/data/model/ModelObject { public static final field CREATOR Landroid/os/Parcelable$Creator; public static final field Companion Lcom/adyen/checkout/googlepay/MerchantInfo$Companion; From 9152ea3c83d5c5d07c4180df626d091a34c8ce61 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Tue, 5 Nov 2024 16:58:57 +0100 Subject: [PATCH 43/44] Add docs for Google Pay COAND-941 --- docs/payment-methods/GOOGLE_PAY.md | 140 ++++++++++++++++++ .../ui/googlepay/GooglePayViewModel.kt | 35 ++--- .../internal/ui/DefaultGooglePayDelegate.kt | 3 +- 3 files changed, 159 insertions(+), 19 deletions(-) create mode 100644 docs/payment-methods/GOOGLE_PAY.md diff --git a/docs/payment-methods/GOOGLE_PAY.md b/docs/payment-methods/GOOGLE_PAY.md new file mode 100644 index 0000000000..87fb02fe96 --- /dev/null +++ b/docs/payment-methods/GOOGLE_PAY.md @@ -0,0 +1,140 @@ +# Google Pay +On this page, you can find additional configuration and a migration guide for Google Pay. + +## Drop-in +### Sessions +The integration works out of the box for sessions implementation. Check out the integration guide [here](https://docs.adyen.com/online-payments/build-your-integration/sessions-flow/?platform=Android&integration=Drop-in). + +### Advanced +There is no additional configuration required. Follow the [Advanced flow integration guide](https://docs.adyen.com/online-payments/build-your-integration/advanced-flow/?platform=Android&integration=Drop-in). + +## Components +Use the following module and component names: +- To import the module use `googlepay`. + +```groovy +implementation "com.adyen.checkout:googlepay:YOUR_VERSION" +``` + +- To launch and show the Component use `GooglePayComponent`. + +```kotlin +val component = GooglePayComponent.PROVIDER.get( + activity = activity, // or fragment = fragment + checkoutSession = checkoutSession, // Should be passed only for sessions + paymentMethod = paymentMethod, + configuration = checkoutConfiguration, + componentCallback = callback, +) +``` + +### Sessions +Make sure to follow the Android Components integration guide for sessions integration [here](https://docs.adyen.com/online-payments/build-your-integration/sessions-flow?platform=Android&integration=Components). + +### Advanced +Make sure to follow the Android Components integration guide for advanced integration [here](https://docs.adyen.com/online-payments/build-your-integration/advanced-flow/?platform=Android&integration=Components). + +## Optional configurations + +```kotlin +CheckoutConfiguration( + environment = environment, + clientKey = clientKey, + … +) { + googlePay { + setSubmitButtonVisible(true) + setMerchantAccount("YOUR_MERCHANT_ACCOUNT") + setGooglePayEnvironment(WalletConstants.ENVIRONMENT_TEST) + setMerchantInfo(…) + setCountryCode("US") + setAllowedAuthMethods(listOf(AllowedAuthMethods.PAN_ONLY)) + setAllowedCardNetworks(listOf("AMEX", "MASTERCARD")) + setAllowPrepaidCards(false) + setAllowCreditCards(true) + setAssuranceDetailsRequired(false) + setEmailRequired(true) + setExistingPaymentMethodRequired(false) + setShippingAddressRequired(true) + setShippingAddressParameters(…) + setBillingAddressRequired(true) + setBillingAddressParameters(…) + setTotalPriceStatus("FINAL") + setGooglePayButtonStyling(…) + } +} +``` + +| Method | Description | +|------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------| +| `setSubmitButtonVisible` | Set to `true` to display the Google Pay button in the component. The default value is `false`. | +| `setMerchantAccount` | Sets the merchant account to be put in the payment token from Google to Adyen. | +| `setGooglePayEnvironment` | Sets the environment to be used by Google Pay. | +| `setMerchantInfo` | Sets the information about the merchant requesting the payment. | +| `setCountryCode` | Sets the ISO 3166-1 alpha-2 country code where the transaction is processed. | +| `setAllowedAuthMethods` | Sets the supported authentication methods. | +| `setAllowedCardNetworks` | Sets the allowed card networks. The allowed networks are automatically configured based on your account settings, but you can override them here. | +| `setAllowPrepaidCards` | Set to `true` if you support prepaid cards. | +| `setAllowCreditCards` | Set to `true` if you support credit cards. | +| `setAssuranceDetailsRequired` | Set to `true` if you want to request assurance details. | +| `setEmailRequired` | Set to `true` if an email address is required. | +| `setExistingPaymentMethodRequired` | Set to `true` if an existing payment method is required. | +| `setShippingAddressRequired` | Set to `true` if a shipping address is required. | +| `setShippingAddressParameters` | Allows to configure the shipping address parameters. | +| `setBillingAddressRequired` | Set to `true` if a billing address is required. | +| `setBillingAddressParameters` | Allows to configure the billing address parameters. | +| `setTotalPriceStatus` | Sets the status of the total price used. | +| `setGooglePayButtonStyling` | Allows to configure the styling of the Google Pay button. | + +## Migrating to 5.8.0+ +It is not necessary to migrate, but 5.8.0 introduced a simplified integration for Google Pay. This new integration among others gets rid of the deprecated `onActivityResult` and includes the Google Pay button. Follow the steps below to migrate from previous 5.x.x versions to 5.8.0: + +### 1. Remove deprecated Activity Result code + +Add `AdyenComponentView` to your layout and attach the component to it. +```xml + +``` +```kotlin +// Attach the component to the view +binding.componentView.attach(googlePayComponent, lifecycleOwner) + +// Or if you use Jetpack Compose +AdyenComponent(googlePayComponent) +``` + +Now you no longer need activity result related code, so you can clean it up. For example you can remove: +```kotlin +// This function can be deleted +override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + googlePayComponent.handleActivityResult(resultCode, data) +} +``` + +### 2. Display Google Pay button + +If you want to keep displaying a button yourself, then you have to replace the call to `googlePayComponent.startGooglePayScreen(…)` with `googlePayComponent.submit()`. + +To let the component display the Google Pay button inside the `AdyenComponentView` remove your own button and adjust your configuration: +```kotlin +CheckoutConfiguration( + environment = environment, + clientKey = clientKey, + … +) { + googlePay { + setSubmitButtonVisible(true) + setGooglePayButtonStyling(…) // Optionally style the button + } +} +``` + +The `com.google.pay.button:compose-pay-button` dependency can now also be removed from your `build.gradle`. + +### 3. Google Pay availability check + +The `GooglePayComponent` now does the availability check on initialization and will return a `GooglePayUnavailableException` in `onError`. You no longer need to manually call `GooglePayComponent.PROVIDER.isAvailable(…)`. diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewModel.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewModel.kt index d19ba0dd1c..5d0e55c1f9 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewModel.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewModel.kt @@ -79,25 +79,26 @@ internal class GooglePayViewModel @Inject constructor( ), ) - val paymentMethod = paymentMethodResponse - ?.paymentMethods - ?.firstOrNull { GooglePayComponent.PROVIDER.isPaymentMethodSupported(it) } - - if (paymentMethod == null) { - _viewState.emit(GooglePayViewState.Error(UICoreR.string.error_dialog_title)) - return@withContext - } + val paymentMethod = paymentMethodResponse + ?.paymentMethods + ?.firstOrNull { GooglePayComponent.PROVIDER.isPaymentMethodSupported(it) } + + if (paymentMethod == null) { + @Suppress("RestrictedApi") + _viewState.emit(GooglePayViewState.Error(UICoreR.string.error_dialog_title)) + return@withContext + } - _googleComponentDataFlow.emit( - GooglePayComponentData( - paymentMethod, - checkoutConfiguration, - this@GooglePayViewModel, - ), - ) + _googleComponentDataFlow.emit( + GooglePayComponentData( + paymentMethod, + checkoutConfiguration, + this@GooglePayViewModel, + ), + ) - checkGooglePayAvailability(paymentMethod, checkoutConfiguration) - } + checkGooglePayAvailability(paymentMethod, checkoutConfiguration) + } private fun checkGooglePayAvailability( paymentMethod: PaymentMethod, diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index e230a18407..4c01019b2b 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt @@ -90,6 +90,7 @@ internal class DefaultGooglePayDelegate( submitHandler.initialize(coroutineScope, componentStateFlow) initializeAnalytics(coroutineScope) + checkAvailability() } private fun initializeAnalytics(coroutineScope: CoroutineScope) { @@ -98,8 +99,6 @@ internal class DefaultGooglePayDelegate( val event = GenericEvents.rendered(paymentMethod.type.orEmpty()) analyticsManager.trackEvent(event) - - checkAvailability() } private fun checkAvailability() { From afc18b069be16de22fdf8e4f591f3d216620b459 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 13 Nov 2024 17:24:27 +0100 Subject: [PATCH 44/44] Improve migration guide COAND-944 --- docs/payment-methods/GOOGLE_PAY.md | 4 ++- .../ui/googlepay/GooglePayViewModel.kt | 36 +++++++++---------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/docs/payment-methods/GOOGLE_PAY.md b/docs/payment-methods/GOOGLE_PAY.md index 87fb02fe96..1fc5b3fdf2 100644 --- a/docs/payment-methods/GOOGLE_PAY.md +++ b/docs/payment-methods/GOOGLE_PAY.md @@ -137,4 +137,6 @@ The `com.google.pay.button:compose-pay-button` dependency can now also be remove ### 3. Google Pay availability check -The `GooglePayComponent` now does the availability check on initialization and will return a `GooglePayUnavailableException` in `onError`. You no longer need to manually call `GooglePayComponent.PROVIDER.isAvailable(…)`. +You no longer need to call `GooglePayComponent.PROVIDER.isAvailable(…)`. + +The`GooglePayComponent` now checks if Google Pay is available when you initialize it. If Google Pay is not available, you get a `GooglePayUnavailableException` in `onError`. diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewModel.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewModel.kt index 5d0e55c1f9..6f958732b6 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewModel.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/googlepay/GooglePayViewModel.kt @@ -79,26 +79,26 @@ internal class GooglePayViewModel @Inject constructor( ), ) - val paymentMethod = paymentMethodResponse - ?.paymentMethods - ?.firstOrNull { GooglePayComponent.PROVIDER.isPaymentMethodSupported(it) } - - if (paymentMethod == null) { - @Suppress("RestrictedApi") - _viewState.emit(GooglePayViewState.Error(UICoreR.string.error_dialog_title)) - return@withContext - } + val paymentMethod = paymentMethodResponse + ?.paymentMethods + ?.firstOrNull { GooglePayComponent.PROVIDER.isPaymentMethodSupported(it) } + + if (paymentMethod == null) { + @Suppress("RestrictedApi") + _viewState.emit(GooglePayViewState.Error(UICoreR.string.error_dialog_title)) + return@withContext + } - _googleComponentDataFlow.emit( - GooglePayComponentData( - paymentMethod, - checkoutConfiguration, - this@GooglePayViewModel, - ), - ) + _googleComponentDataFlow.emit( + GooglePayComponentData( + paymentMethod, + checkoutConfiguration, + this@GooglePayViewModel, + ), + ) - checkGooglePayAvailability(paymentMethod, checkoutConfiguration) - } + checkGooglePayAvailability(paymentMethod, checkoutConfiguration) + } private fun checkGooglePayAvailability( paymentMethod: PaymentMethod,