diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index efe3a28d75..79d070fcdd 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -8,7 +8,24 @@ [//]: # (## 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. +- For drop-in, show a toolbar on every intermediary screen, so shoppers can always easily navigate back. + ## 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 +- 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` | 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/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..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 @@ -12,7 +12,9 @@ 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 +import kotlinx.coroutines.CoroutineScope internal class CashAppPayButtonView @JvmOverloads constructor( context: Context, @@ -22,6 +24,8 @@ internal class CashAppPayButtonView @JvmOverloads constructor( private val binding = CashAppPayButtonViewBinding.inflate(LayoutInflater.from(context), this) + override fun initialize(delegate: ButtonDelegate, coroutineScope: CoroutineScope) = Unit + override fun setEnabled(enabled: Boolean) { binding.payButton.isEnabled = enabled } 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-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/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 - - - - - diff --git a/googlepay/api/googlepay.api b/googlepay/api/googlepay.api index dc8b4cabca..71125b8620 100644 --- a/googlepay/api/googlepay.api +++ b/googlepay/api/googlepay.api @@ -65,7 +65,59 @@ 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/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; public static final field PROVIDER Lcom/adyen/checkout/googlepay/internal/provider/GooglePayComponentProvider; @@ -76,9 +128,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 { @@ -102,9 +156,9 @@ public final class com/adyen/checkout/googlepay/GooglePayComponentState : com/ad public fun toString ()Ljava/lang/String; } -public final class com/adyen/checkout/googlepay/GooglePayConfiguration : com/adyen/checkout/components/core/internal/Configuration { +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;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/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; @@ -114,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; @@ -127,10 +182,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 @@ -147,11 +203,14 @@ 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; 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; } @@ -168,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; diff --git a/googlepay/build.gradle b/googlepay/build.gradle index ecd8f4e789..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 { @@ -40,6 +44,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/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..4d48323a70 --- /dev/null +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayButtonStyling.kt @@ -0,0 +1,49 @@ +/* + * 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 + +/** + * Object to style the Google Pay button. Check [the Google docs](https://developers.google.com/pay/api/android/guides/resources/pay-button-api) for more details. + * + * @param buttonTheme Affects the color scheme of the button. + * @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, + val buttonType: GooglePayButtonType? = null, + @Dimension(Dimension.DP) val cornerRadius: Int? = null, +) : 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/GooglePayComponent.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayComponent.kt index f484ff2b2b..6b65bed094 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 @@ -25,9 +26,12 @@ 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 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 +46,17 @@ class GooglePayComponent internal constructor( ) : ViewModel(), PaymentComponent, ActivityResultHandlingComponent, + ButtonComponent, ViewableComponent, ActionHandlingComponent by actionHandlingComponent { 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) @@ -74,10 +83,27 @@ 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 submit()", ReplaceWith("submit()")) fun startGooglePayScreen(activity: Activity, requestCode: Int) { + @Suppress("DEPRECATION") googlePayDelegate.startGooglePayScreen(activity, requestCode) } + /** + * Start the GooglePay screen. + */ + override fun submit() { + (delegate as? ButtonDelegate)?.onSubmit() + ?: adyenLog(AdyenLogLevel.ERROR) { "Component is currently not submittable, ignoring." } + } + + override fun setInteractionBlocked(isInteractionBlocked: Boolean) { + (delegate as? DefaultGooglePayDelegate)?.setInteractionBlocked(isInteractionBlocked) + ?: adyenLog(AdyenLogLevel.ERROR) { "Payment component is not interactable, 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). */ @@ -92,14 +118,11 @@ 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 submit() this method is no longer needed.", ReplaceWith("")) override fun handleActivityResult(resultCode: Int, data: Intent?) { 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/GooglePayConfiguration.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/GooglePayConfiguration.kt index 70a2bb0b4f..09a5e5a4da 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?, @@ -51,15 +54,17 @@ class GooglePayConfiguration private constructor( val shippingAddressParameters: ShippingAddressParameters?, val isBillingAddressRequired: Boolean?, val billingAddressParameters: BillingAddressParameters?, + val googlePayButtonStyling: GooglePayButtonStyling?, 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 +81,8 @@ 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 /** * Initialize a configuration builder with the required fields. @@ -378,15 +385,38 @@ class GooglePayConfiguration private constructor( return super.setAmount(amount) } + /** + * Set a [GooglePayButtonStyling] object for customization of the Google Pay button. + * + * @param googlePayButtonStyling The customization object. + */ + fun setGooglePayButtonStyling(googlePayButtonStyling: GooglePayButtonStyling): Builder { + this.googlePayButtonStyling = googlePayButtonStyling + return this + } + + /** + * 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, @@ -401,6 +431,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/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 be68f24472..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 @@ -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 @@ -53,10 +52,8 @@ import com.adyen.checkout.sessions.core.internal.data.api.SessionRepository import com.adyen.checkout.sessions.core.internal.data.api.SessionService import com.adyen.checkout.sessions.core.internal.provider.SessionPaymentComponentProvider import com.adyen.checkout.sessions.core.internal.ui.model.SessionParamsFactory -import com.google.android.gms.common.ConnectionResult -import com.google.android.gms.common.GoogleApiAvailability +import com.adyen.checkout.ui.core.internal.ui.SubmitHandler import com.google.android.gms.wallet.Wallet -import java.lang.ref.WeakReference class GooglePayComponentProvider @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @@ -108,12 +105,18 @@ constructor( sessionId = null, ) + val paymentsClient = + Wallet.getPaymentsClient(application, GooglePayUtils.createWalletOptions(componentParams)) + val googlePayDelegate = DefaultGooglePayDelegate( + submitHandler = SubmitHandler(savedStateHandle), observerRepository = PaymentObserverRepository(), paymentMethod = paymentMethod, order = order, componentParams = componentParams, analyticsManager = analyticsManager, + paymentsClient = paymentsClient, + googlePayAvailabilityCheck = GooglePayAvailabilityCheck(application), ) val genericActionDelegate = @@ -195,12 +198,18 @@ constructor( sessionId = checkoutSession.sessionSetupResponse.id, ) + val paymentsClient = + Wallet.getPaymentsClient(application, GooglePayUtils.createWalletOptions(componentParams)) + val googlePayDelegate = DefaultGooglePayDelegate( + submitHandler = SubmitHandler(savedStateHandle), observerRepository = PaymentObserverRepository(), paymentMethod = paymentMethod, order = checkoutSession.order, componentParams = componentParams, analyticsManager = analyticsManager, + paymentsClient = paymentsClient, + googlePayAvailabilityCheck = GooglePayAvailabilityCheck(application), ) val genericActionDelegate = @@ -275,14 +284,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), @@ -291,20 +292,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/ui/DefaultGooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegate.kt index af2b87eb71..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 @@ -28,44 +28,69 @@ 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.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 +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 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 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?, override val componentParams: GooglePayComponentParams, private val analyticsManager: AnalyticsManager, + private val paymentsClient: PaymentsClient, + 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 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 var _coroutineScope: CoroutineScope? = null + private val coroutineScope: CoroutineScope get() = requireNotNull(_coroutineScope) + + private val payEventChannel: Channel> = bufferedChannel() + override val payEventFlow: Flow> = payEventChannel.receiveAsFlow() override fun initialize(coroutineScope: CoroutineScope) { - initializeAnalytics(coroutineScope) + _coroutineScope = coroutineScope + submitHandler.initialize(coroutineScope, componentStateFlow) - componentStateFlow.onEach { - onState(it) - }.launchIn(coroutineScope) + initializeAnalytics(coroutineScope) + checkAvailability() } private fun initializeAnalytics(coroutineScope: CoroutineScope) { @@ -76,12 +101,16 @@ internal class DefaultGooglePayDelegate( analyticsManager.trackEvent(event) } - private fun onState(state: GooglePayComponentState) { - if (state.isValid) { - val event = GenericEvents.submit(paymentMethod.type.orEmpty()) - analyticsManager.trackEvent(event) + private fun checkAvailability() { + googlePayAvailabilityCheck.isAvailable( + paymentMethod, + componentParams, + ) { isAvailable, _ -> + updateOutputData(isButtonVisible = isAvailable) - submitChannel.trySend(state) + if (!isAvailable) { + exceptionChannel.trySend(GooglePayUnavailableException()) + } } } @@ -104,14 +133,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 @@ -127,23 +178,71 @@ 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, ) } + @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)) 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) } + override fun onSubmit() { + adyenLog(AdyenLogLevel.DEBUG) { "onSubmit" } + + _viewFlow.tryEmit(PaymentInProgressViewType) + + val paymentDataRequest = GooglePayUtils.createPaymentDataRequest(componentParams) + val paymentDataTask = paymentsClient.loadPaymentData(paymentDataRequest) + coroutineScope.launch { + payEventChannel.send(paymentDataTask.awaitTask()) + } + } + + override fun handlePaymentResult(paymentDataTaskResult: ApiTaskResult) { + when (val statusCode = paymentDataTaskResult.status.statusCode) { + CommonStatusCodes.SUCCESS -> { + adyenLog(AdyenLogLevel.INFO) { "GooglePay payment result successful" } + initiatePayment(paymentDataTaskResult.result) + } + + 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) { @@ -152,8 +251,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 -> { @@ -165,9 +263,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) + + updateOutputData(paymentData = paymentData) + submitHandler.onSubmit(_componentStateFlow.value) } override fun getGooglePayButtonParameters(): GooglePayButtonParameters { @@ -179,6 +290,14 @@ internal class DefaultGooglePayDelegate( return GooglePayButtonParameters(allowedPaymentMethods) } + override fun isConfirmationRequired(): Boolean = _viewFlow.value is ButtonComponentViewType + + 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/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..e0fc29e983 --- /dev/null +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayButtonView.kt @@ -0,0 +1,130 @@ +/* + * 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.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.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 +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, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : PayButton(context, attrs, defStyleAttr) { + + 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, coroutineScope: CoroutineScope) { + check(delegate is GooglePayDelegate) + + observeDelegate(delegate, coroutineScope) + + val buttonStyle = delegate.componentParams.googlePayButtonStyling + + 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 { + if (buttonType != null) { + setButtonType(buttonType.value) + } + + if (buttonTheme != null) { + setButtonTheme(buttonTheme.value) + } + + if (cornerRadius != null) { + setCornerRadius(cornerRadius) + } + + setAllowedPaymentMethods(delegate.getGooglePayButtonParameters().allowedPaymentMethods) + }.build(), + ) + } + + private fun observeDelegate(delegate: GooglePayDelegate, coroutineScope: CoroutineScope) { + delegate.outputDataFlow + .onEach { + binding.payButton.isVisible = it.isButtonVisible + } + .launchIn(coroutineScope) + } + + override fun setEnabled(enabled: Boolean) { + binding.payButton.isEnabled = enabled + } + + override fun setOnClickListener(listener: OnClickListener?) { + binding.payButton.setOnClickListener(listener) + } + + override fun setText(text: String?) = Unit + + @Suppress("MagicNumber") + 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/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayDelegate.kt index 2d393b6d19..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 @@ -14,17 +14,36 @@ 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.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 +import com.google.android.gms.wallet.PaymentData +import com.google.android.gms.wallet.contract.ApiTaskResult import kotlinx.coroutines.flow.Flow -internal interface GooglePayDelegate : PaymentComponentDelegate { +internal interface GooglePayDelegate : + PaymentComponentDelegate, + ViewProvidingDelegate, + ButtonDelegate { + + override val componentParams: GooglePayComponentParams + + val outputDataFlow: Flow val componentStateFlow: Flow val exceptionFlow: Flow + val payEventFlow: Flow> + + @Deprecated("Deprecated in favor of onSubmit()", ReplaceWith("onSubmit()")) fun startGooglePayScreen(activity: Activity, requestCode: Int) fun handleActivityResult(resultCode: Int, data: Intent?) fun getGooglePayButtonParameters(): GooglePayButtonParameters + + fun handlePaymentResult(paymentDataTaskResult: ApiTaskResult) } 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..1f54d1b571 --- /dev/null +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayFragment.kt @@ -0,0 +1,50 @@ +/* + * 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 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() { + + private var _binding: FragmentGooglePayBinding? = null + private val binding: FragmentGooglePayBinding get() = requireNotNull(_binding) + + 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) + return binding.root + } + + fun initialize(delegate: GooglePayDelegate) { + this.delegate = delegate + delegate.payEventFlow + .onEach { googlePayLauncher.launch(it) } + .launchIn(viewLifecycleOwner.lifecycleScope) + } + + 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..25256e3a32 --- /dev/null +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayView.kt @@ -0,0 +1,53 @@ +/* + * 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 +@JvmOverloads +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..927e380caf --- /dev/null +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/internal/ui/GooglePayViewProvider.kt @@ -0,0 +1,57 @@ +/* + * 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.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 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") + } + + override fun getView( + viewType: ComponentViewType, + layoutInflater: LayoutInflater + ): ComponentView = when (viewType) { + GooglePayComponentViewType -> GooglePayView(layoutInflater) + PaymentInProgressViewType -> ProcessingPaymentView(layoutInflater.context) + else -> throw IllegalArgumentException("Unsupported view type") + } +} + +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 get() = GooglePayViewProvider() + + override val buttonTextResId: Int = ButtonComponentViewType.DEFAULT_BUTTON_TEXT_RES_ID +} + +internal object PaymentInProgressViewType : ComponentViewType { + + override val viewProvider: ViewProvider get() = GooglePayViewProvider() +} 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..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 @@ -9,15 +9,18 @@ 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 +import com.adyen.checkout.googlepay.GooglePayButtonStyling import com.adyen.checkout.googlepay.MerchantInfo 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 +37,5 @@ internal data class GooglePayComponentParams( val shippingAddressParameters: ShippingAddressParameters?, val isBillingAddressRequired: Boolean, val billingAddressParameters: BillingAddressParameters?, -) : ComponentParams by commonComponentParams + 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 ee737a5192..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 @@ -60,6 +60,7 @@ internal class GooglePayComponentParamsMapper( return GooglePayComponentParams( commonComponentParams = commonComponentParams, amount = commonComponentParams.amount ?: DEFAULT_AMOUNT, + isSubmitButtonVisible = shouldShowSubmitButton(commonComponentParams, googlePayConfiguration), gatewayMerchantId = googlePayConfiguration.getPreferredGatewayMerchantId(paymentMethod), allowedAuthMethods = googlePayConfiguration.getAvailableAuthMethods(), allowedCardNetworks = googlePayConfiguration.getAvailableCardNetworks(paymentMethod), @@ -76,9 +77,22 @@ internal class GooglePayComponentParamsMapper( shippingAddressParameters = googlePayConfiguration?.shippingAddressParameters, isBillingAddressRequired = googlePayConfiguration?.isBillingAddressRequired ?: false, billingAddressParameters = googlePayConfiguration?.billingAddressParameters, + googlePayButtonStyling = googlePayConfiguration?.googlePayButtonStyling, ) } + private fun shouldShowSubmitButton( + commonComponentParams: CommonComponentParams, + googlePayConfiguration: GooglePayConfiguration?, + ): Boolean { + return if (commonComponentParams.isCreatedByDropIn) { + false + } else { + // TODO return true by default in v6 + 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/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/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) + } + } +} 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..8087675b87 --- /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 class DirectExecutor : Executor { + override fun execute(runnable: Runnable) { + runnable.run() + } +} 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..6f03758752 --- /dev/null +++ b/googlepay/src/main/res/layout/fragment_google_pay.xml @@ -0,0 +1,11 @@ + + + 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 @@ + + + + + + + + + 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..5adc62558e --- /dev/null +++ b/googlepay/src/main/res/layout/view_google_pay_button.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/example-app/src/main/res/values-night/styles.xml b/googlepay/src/main/res/values-night/styles.xml similarity index 52% rename from example-app/src/main/res/values-night/styles.xml rename to googlepay/src/main/res/values-night/styles.xml index b8704d3158..ad24e59f4c 100644 --- a/example-app/src/main/res/values-night/styles.xml +++ b/googlepay/src/main/res/values-night/styles.xml @@ -1,14 +1,15 @@ - + 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..afba028b53 --- /dev/null +++ b/googlepay/src/main/res/values/styles.xml @@ -0,0 +1,17 @@ + + + + + + + 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..f303433530 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.GooglePayDelegate +import com.adyen.checkout.googlepay.internal.ui.DefaultGooglePayDelegate +import com.adyen.checkout.googlepay.internal.ui.GooglePayComponentViewType 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,12 +35,13 @@ 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 @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, @@ -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 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 + 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,71 @@ 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() + } + + @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() + } } 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/DefaultGooglePayDelegateTest.kt b/googlepay/src/test/java/com/adyen/checkout/googlepay/internal/ui/DefaultGooglePayDelegateTest.kt index b44f4b74be..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,41 +20,65 @@ 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 +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 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 +import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments.arguments import org.junit.jupiter.params.provider.MethodSource +import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever import java.util.Locale @OptIn(ExperimentalCoroutinesApi::class) -@ExtendWith(MockitoExtension::class) -internal class DefaultGooglePayDelegateTest { +@ExtendWith(MockitoExtension::class, LoggingExtension::class) +internal class DefaultGooglePayDelegateTest( + @Mock private val submitHandler: SubmitHandler, + @Mock private val paymentsClient: PaymentsClient, + @Mock private val googlePayAvailabilityCheck: GooglePayAvailabilityCheck, +) { 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() { + whenever(paymentsClient.loadPaymentData(any())) doReturn Tasks.forResult(TEST_PAYMENT_DATA) analyticsManager = TestAnalyticsManager() delegate = createGooglePayDelegate() } @@ -75,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) @@ -93,9 +118,9 @@ internal class DefaultGooglePayDelegateTest { delegate.componentStateFlow.test { skipItems(1) - val paymentData = paymentData + val paymentData = TEST_PAYMENT_DATA - delegate.updateComponentState(paymentData) + delegate.updateComponentState(createOutputData(paymentData = paymentData)) val componentState = awaitItem() @@ -121,6 +146,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`( @@ -133,11 +170,56 @@ internal class DefaultGooglePayDelegateTest { } delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) delegate.componentStateFlow.test { - delegate.updateComponentState(paymentData) + delegate.updateComponentState(createOutputData(paymentData = TEST_PAYMENT_DATA)) assertEquals(expectedComponentStateValue, expectMostRecentItem().data.amount) } } + @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 { @@ -156,16 +238,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(paymentData) - - 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) @@ -173,12 +245,24 @@ internal class DefaultGooglePayDelegateTest { delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) delegate.componentStateFlow.test { - delegate.updateComponentState(paymentData) + delegate.updateComponentState(createOutputData(paymentData = TEST_PAYMENT_DATA)) assertEquals(TEST_CHECKOUT_ATTEMPT_ID, expectMostRecentItem().data.paymentMethod?.checkoutAttemptId) } } + @Test + fun `when payment is successful and the data is valid, then submit event is tracked`() { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + delegate.updateComponentState(createOutputData(paymentData = 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() @@ -187,6 +271,55 @@ 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`( + 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 = {} @@ -206,19 +339,29 @@ internal class DefaultGooglePayDelegateTest { ), ): DefaultGooglePayDelegate { return DefaultGooglePayDelegate( + submitHandler = submitHandler, observerRepository = PaymentObserverRepository(), paymentMethod = PaymentMethod(type = TEST_PAYMENT_METHOD_TYPE), order = TEST_ORDER, componentParams = GooglePayComponentParamsMapper(CommonComponentParamsMapper()) .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" 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( @@ -228,5 +371,24 @@ 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), + ) + + @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), + ) } } 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..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 @@ -33,6 +36,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 @@ -71,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, @@ -95,6 +106,7 @@ internal class GooglePayComponentParamsMapperTest { setShippingAddressParameters(shippingAddressParameters) setShippingAddressRequired(true) setTotalPriceStatus("STATUS") + setGooglePayButtonStyling(googlePayButtonStyling) } } @@ -111,9 +123,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, @@ -128,6 +141,7 @@ internal class GooglePayComponentParamsMapperTest { shippingAddressParameters = shippingAddressParameters, isBillingAddressRequired = true, billingAddressParameters = billingAddressParameters, + googlePayButtonStyling = googlePayButtonStyling, ) assertEquals(expected, params) @@ -148,6 +162,7 @@ internal class GooglePayComponentParamsMapperTest { googlePay { setAmount(Amount("USD", 1L)) setAnalyticsConfiguration(AnalyticsConfiguration(AnalyticsLevel.ALL)) + setSubmitButtonVisible(true) setMerchantAccount(TEST_GATEWAY_MERCHANT_ID) } } @@ -172,6 +187,7 @@ internal class GooglePayComponentParamsMapperTest { currency = "CAD", value = 123L, ), + isSubmitButtonVisible = false, ) assertEquals(expected, params) @@ -480,6 +496,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, @@ -524,9 +591,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, @@ -541,6 +609,7 @@ internal class GooglePayComponentParamsMapperTest { shippingAddressParameters: ShippingAddressParameters? = null, isBillingAddressRequired: Boolean = false, billingAddressParameters: BillingAddressParameters? = null, + googlePayButtonStyling: GooglePayButtonStyling? = null, ) = GooglePayComponentParams( commonComponentParams = CommonComponentParams( shopperLocale = shopperLocale, @@ -550,9 +619,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, @@ -567,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 a5f45707d4..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 @@ -248,6 +248,7 @@ internal class GooglePayUtilsTest { amount = null, ), amount = Amount("USD", 0), + isSubmitButtonVisible = false, gatewayMerchantId = "", googlePayEnvironment = WalletConstants.ENVIRONMENT_TEST, totalPriceStatus = "NOT_CURRENTLY_KNOWN", @@ -264,6 +265,7 @@ internal class GooglePayUtilsTest { shippingAddressParameters = null, isBillingAddressRequired = false, billingAddressParameters = null, + googlePayButtonStyling = null, ) } @@ -278,6 +280,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", @@ -303,6 +306,7 @@ internal class GooglePayUtilsTest { format = "FORMAT", isPhoneNumberRequired = true, ), + googlePayButtonStyling = null, ) } } 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 @@ + + + + + + + + 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()) 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()) 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 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() +} 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/java/com/adyen/checkout/ui/core/AdyenComponentView.kt b/ui-core/src/main/java/com/adyen/checkout/ui/core/AdyenComponentView.kt index 8988f179d0..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,6 +147,7 @@ class AdyenComponentView @JvmOverloads constructor( binding.frameLayoutButtonContainer.isVisible = buttonDelegate.shouldShowSubmitButton() val buttonView = (viewType as ButtonComponentViewType) .buttonViewProvider.getButton(context) + 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 96c12a09b2..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 @@ -12,6 +12,8 @@ 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 +import kotlinx.coroutines.CoroutineScope internal class DefaultPayButton @JvmOverloads constructor( context: Context, @@ -21,6 +23,8 @@ internal class DefaultPayButton @JvmOverloads constructor( private val binding = DefaultPayButtonViewBinding.inflate(LayoutInflater.from(context), this) + 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 f818ba1070..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 @@ -12,6 +12,8 @@ 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 +import kotlinx.coroutines.CoroutineScope @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) abstract class PayButton( @@ -20,6 +22,8 @@ abstract class PayButton( defStyleAttr: Int, ) : FrameLayout(context, attrs, defStyleAttr) { + abstract fun initialize(delegate: ButtonDelegate, coroutineScope: CoroutineScope) + abstract override fun setEnabled(enabled: Boolean) abstract override fun setOnClickListener(listener: OnClickListener?) diff --git a/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/view/CashAppPayWaitingView.kt b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/ProcessingPaymentView.kt similarity index 61% rename from cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/view/CashAppPayWaitingView.kt rename to ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/ProcessingPaymentView.kt index 5bb4d79566..521649ee01 100644 --- a/cashapppay/src/main/java/com/adyen/checkout/cashapppay/internal/ui/view/CashAppPayWaitingView.kt +++ b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/ui/view/ProcessingPaymentView.kt @@ -1,4 +1,12 @@ -package com.adyen.checkout.cashapppay.internal.ui.view +/* + * 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 16/10/2024. + */ + +package com.adyen.checkout.ui.core.internal.ui.view import android.content.Context import android.util.AttributeSet @@ -6,27 +14,28 @@ import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.widget.LinearLayout -import com.adyen.checkout.cashapppay.R -import com.adyen.checkout.cashapppay.databinding.CashAppPayWaitingViewBinding +import androidx.annotation.RestrictTo import com.adyen.checkout.components.core.internal.ui.ComponentDelegate +import com.adyen.checkout.ui.core.R +import com.adyen.checkout.ui.core.databinding.ProcessingPaymentViewBinding import com.adyen.checkout.ui.core.internal.ui.ComponentView import com.adyen.checkout.ui.core.internal.util.setLocalizedTextFromStyle import kotlinx.coroutines.CoroutineScope -import com.adyen.checkout.ui.core.R as UICoreR -internal class CashAppPayWaitingView @JvmOverloads constructor( +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +class ProcessingPaymentView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, ) : LinearLayout(context, attrs, defStyleAttr), ComponentView { - private val binding = CashAppPayWaitingViewBinding.inflate(LayoutInflater.from(context), this) + private val binding = ProcessingPaymentViewBinding.inflate(LayoutInflater.from(context), this) init { orientation = HORIZONTAL gravity = Gravity.CENTER - val padding = resources.getDimension(UICoreR.dimen.standard_margin).toInt() + val padding = resources.getDimension(R.dimen.standard_margin).toInt() setPadding(padding, padding, padding, padding) } @@ -36,8 +45,8 @@ internal class CashAppPayWaitingView @JvmOverloads constructor( private fun initLocalizedStrings(localizedContext: Context) { binding.textViewPaymentInProgressDescription.setLocalizedTextFromStyle( - R.style.AdyenCheckout_CashAppPay_WaitingDescriptionTextView, - localizedContext + R.style.AdyenCheckout_ProcessingPaymentView_WaitingDescriptionTextView, + localizedContext, ) } diff --git a/cashapppay/src/main/res/layout/cash_app_pay_waiting_view.xml b/ui-core/src/main/res/layout/processing_payment_view.xml similarity index 80% rename from cashapppay/src/main/res/layout/cash_app_pay_waiting_view.xml rename to ui-core/src/main/res/layout/processing_payment_view.xml index d002b61f8b..ece00fae24 100644 --- a/cashapppay/src/main/res/layout/cash_app_pay_waiting_view.xml +++ b/ui-core/src/main/res/layout/processing_payment_view.xml @@ -9,12 +9,12 @@ diff --git a/ui-core/src/main/res/template/values/strings.xml.tt b/ui-core/src/main/res/template/values/strings.xml.tt index 518685147b..676d8239d5 100644 --- a/ui-core/src/main/res/template/values/strings.xml.tt +++ b/ui-core/src/main/res/template/values/strings.xml.tt @@ -55,4 +55,6 @@ %%address.enterManually%% %%address.lookup.submit%% %%address.lookup.item.validationFailureMessage.empty%% + + %%paypal.processingPayment%% 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 @@ 手動輸入地址 使用此地址 必須填寫地址 + + 正在處理付款…… diff --git a/ui-core/src/main/res/values/strings.xml b/ui-core/src/main/res/values/strings.xml index f4d78d9e87..6214b33801 100644 --- a/ui-core/src/main/res/values/strings.xml +++ b/ui-core/src/main/res/values/strings.xml @@ -55,4 +55,6 @@ Enter address manually Use this address Address required + + Processing payment… diff --git a/ui-core/src/main/res/values/styles.xml b/ui-core/src/main/res/values/styles.xml index 9596fdc49a..af11d3136b 100644 --- a/ui-core/src/main/res/values/styles.xml +++ b/ui-core/src/main/res/values/styles.xml @@ -407,4 +407,19 @@ someColor3 --> + + + +