diff --git a/CHANGELOG.md b/CHANGELOG.md index 115c18376f7..a960c12ba92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## XX.XX.XX - 2023-XX-XX +### PaymentSheet +* [CHANGED][5927](https://github.com/stripe/stripe-android/pull/5927) Customers can now re-enter the autocomplete flow of the Address Element by tapping an icon in the line 1 text field. + ## 20.18.0 - 2023-01-17 ### Payments * [ADDED][6012](https://github.com/stripe/stripe-android/pull/6012) Support for the predictive back gesture. diff --git a/payments-ui-core/api/payments-ui-core.api b/payments-ui-core/api/payments-ui-core.api index 420cc0217e2..9f29cb39bb4 100644 --- a/payments-ui-core/api/payments-ui-core.api +++ b/payments-ui-core/api/payments-ui-core.api @@ -81,50 +81,6 @@ public final class com/stripe/android/ui/core/elements/AddressSpec$Companion { public final class com/stripe/android/ui/core/elements/AddressTextFieldUIKt { } -public final class com/stripe/android/ui/core/elements/AddressType$Normal : com/stripe/android/ui/core/elements/AddressType { - public static final field $stable I - public fun ()V - public fun (Lcom/stripe/android/ui/core/elements/PhoneNumberState;)V - public synthetic fun (Lcom/stripe/android/ui/core/elements/PhoneNumberState;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lcom/stripe/android/ui/core/elements/PhoneNumberState; - public final fun copy (Lcom/stripe/android/ui/core/elements/PhoneNumberState;)Lcom/stripe/android/ui/core/elements/AddressType$Normal; - public static synthetic fun copy$default (Lcom/stripe/android/ui/core/elements/AddressType$Normal;Lcom/stripe/android/ui/core/elements/PhoneNumberState;ILjava/lang/Object;)Lcom/stripe/android/ui/core/elements/AddressType$Normal; - public fun equals (Ljava/lang/Object;)Z - public fun getPhoneNumberState ()Lcom/stripe/android/ui/core/elements/PhoneNumberState; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/stripe/android/ui/core/elements/AddressType$ShippingCondensed : com/stripe/android/ui/core/elements/AddressType { - public static final field $stable I - public fun (Ljava/lang/String;Ljava/util/Set;Lcom/stripe/android/ui/core/elements/PhoneNumberState;Lkotlin/jvm/functions/Function0;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/util/Set; - public final fun component3 ()Lcom/stripe/android/ui/core/elements/PhoneNumberState; - public final fun component4 ()Lkotlin/jvm/functions/Function0; - public final fun copy (Ljava/lang/String;Ljava/util/Set;Lcom/stripe/android/ui/core/elements/PhoneNumberState;Lkotlin/jvm/functions/Function0;)Lcom/stripe/android/ui/core/elements/AddressType$ShippingCondensed; - public static synthetic fun copy$default (Lcom/stripe/android/ui/core/elements/AddressType$ShippingCondensed;Ljava/lang/String;Ljava/util/Set;Lcom/stripe/android/ui/core/elements/PhoneNumberState;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lcom/stripe/android/ui/core/elements/AddressType$ShippingCondensed; - public fun equals (Ljava/lang/Object;)Z - public final fun getAutocompleteCountries ()Ljava/util/Set; - public final fun getGoogleApiKey ()Ljava/lang/String; - public final fun getOnNavigation ()Lkotlin/jvm/functions/Function0; - public fun getPhoneNumberState ()Lcom/stripe/android/ui/core/elements/PhoneNumberState; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class com/stripe/android/ui/core/elements/AddressType$ShippingExpanded : com/stripe/android/ui/core/elements/AddressType { - public static final field $stable I - public fun (Lcom/stripe/android/ui/core/elements/PhoneNumberState;)V - public final fun component1 ()Lcom/stripe/android/ui/core/elements/PhoneNumberState; - public final fun copy (Lcom/stripe/android/ui/core/elements/PhoneNumberState;)Lcom/stripe/android/ui/core/elements/AddressType$ShippingExpanded; - public static synthetic fun copy$default (Lcom/stripe/android/ui/core/elements/AddressType$ShippingExpanded;Lcom/stripe/android/ui/core/elements/PhoneNumberState;ILjava/lang/Object;)Lcom/stripe/android/ui/core/elements/AddressType$ShippingExpanded; - public fun equals (Ljava/lang/Object;)Z - public fun getPhoneNumberState ()Lcom/stripe/android/ui/core/elements/PhoneNumberState; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - public abstract class com/stripe/android/ui/core/elements/AdministrativeAreaConfig$Country { public static final field $stable I public synthetic fun (ILjava/util/List;Lkotlin/jvm/internal/DefaultConstructorMarker;)V diff --git a/payments-ui-core/res/drawable/stripe_ic_search.xml b/payments-ui-core/res/drawable/stripe_ic_search.xml new file mode 100644 index 00000000000..dbeb569c9b5 --- /dev/null +++ b/payments-ui-core/res/drawable/stripe_ic_search.xml @@ -0,0 +1,10 @@ + + + diff --git a/payments-ui-core/res/values-b+es+419/strings.xml b/payments-ui-core/res/values-b+es+419/strings.xml index d871d907332..34567c420a8 100644 --- a/payments-ui-core/res/values-b+es+419/strings.xml +++ b/payments-ui-core/res/values-b+es+419/strings.xml @@ -38,6 +38,8 @@ Suburbio Villa o localidad + + Buscar cuotas sin interés de con ]]> @@ -64,6 +66,7 @@ El IBAN debe empezar con un código de país de dos letras. Banco iDEAL + Dirección de correo electrónico no válida: %s Compra ahora y paga después con Klarna. diff --git a/payments-ui-core/res/values-ca-rES/strings.xml b/payments-ui-core/res/values-ca-rES/strings.xml index 3da1c3bc31b..952985937f1 100644 --- a/payments-ui-core/res/values-ca-rES/strings.xml +++ b/payments-ui-core/res/values-ca-rES/strings.xml @@ -38,6 +38,8 @@ Suburbi Poble o municipi + + Cerca pagaments sense interessos de amb ]]> @@ -64,6 +66,7 @@ El teu IBAN ha de començar amb un codi de país de dues lletres. Bank iDeal + Adreça electrònica no vàlida: %s Compra ara o paga més endavant amb Klarna. diff --git a/payments-ui-core/res/values-cs-rCZ/strings.xml b/payments-ui-core/res/values-cs-rCZ/strings.xml index 91d1df60f19..ca5d62ed758 100644 --- a/payments-ui-core/res/values-cs-rCZ/strings.xml +++ b/payments-ui-core/res/values-cs-rCZ/strings.xml @@ -38,6 +38,8 @@ Předměstí Vesnice nebo předměstí + + Hledat bezúročných splátkách, každou ve výši , splácených přes ]]> @@ -64,6 +66,7 @@ Číslo IBAN by mělo začínat dvěma písmeny kódu země. Banka iDEAL + Neplatná e-mailová adresa: %s Kupte nyní nebo zaplaťte později pomocí Klarna diff --git a/payments-ui-core/res/values-da/strings.xml b/payments-ui-core/res/values-da/strings.xml index f409b787790..30dc351792f 100644 --- a/payments-ui-core/res/values-da/strings.xml +++ b/payments-ui-core/res/values-da/strings.xml @@ -38,6 +38,8 @@ Forstad Landsby eller byområde + + Søg rentefri afdrag på hver med ]]> @@ -64,6 +66,7 @@ Dit IBAN-nummer skal starte med en landekode på to bogstaver. iDEAL Bank + Ugyldig e-mailadresse: %s Køb nu, eller betal senere med Klarna. diff --git a/payments-ui-core/res/values-de/strings.xml b/payments-ui-core/res/values-de/strings.xml index 533ca249590..2689ed086ee 100644 --- a/payments-ui-core/res/values-de/strings.xml +++ b/payments-ui-core/res/values-de/strings.xml @@ -38,6 +38,8 @@ Vorort Dorf oder Gemeinde + + Suchen in zinslosen Raten zu je .]]> @@ -64,6 +66,7 @@ Ihre IBAN sollte mit einem zweistelligen Ländercode beginnen. iDEAL-Bank + Ungültige E-Mail-Adresse: %s Jetzt kaufen oder später mit Klarna bezahlen. diff --git a/payments-ui-core/res/values-el-rGR/strings.xml b/payments-ui-core/res/values-el-rGR/strings.xml index 920f6a63ca4..a52dae663fd 100644 --- a/payments-ui-core/res/values-el-rGR/strings.xml +++ b/payments-ui-core/res/values-el-rGR/strings.xml @@ -38,6 +38,8 @@ Προάστιο Χωριό ή κοινότητα + + Αναζήτηση άτοκες δόσεις των με ]]> @@ -64,6 +66,7 @@ Ο κωδικός IBAN σας πρέπει να αρχίζει με έναν διψήφιο κωδικό χώρας. iDEAL Bank + Μη έγκυρη διεύθυνση email: %s Αγορά τώρα ή πληρωμή αργότερα με Klarna. diff --git a/payments-ui-core/res/values-en-rGB/strings.xml b/payments-ui-core/res/values-en-rGB/strings.xml index d0f77a70a44..cd186c88332 100644 --- a/payments-ui-core/res/values-en-rGB/strings.xml +++ b/payments-ui-core/res/values-en-rGB/strings.xml @@ -38,6 +38,8 @@ Suburb Village or Township + + Search interest-free payments of with ]]> @@ -64,6 +66,7 @@ Your IBAN should start with a two-letter country code. iDEAL Bank + Invalid email address: %s Buy now or pay later with Klarna. diff --git a/payments-ui-core/res/values-es/strings.xml b/payments-ui-core/res/values-es/strings.xml index e4c96bc35ea..0ddff08bd4c 100644 --- a/payments-ui-core/res/values-es/strings.xml +++ b/payments-ui-core/res/values-es/strings.xml @@ -38,6 +38,8 @@ Zona residencial Villa o comuna + + Buscar cuotas sin intereses de con ]]> @@ -64,6 +66,7 @@ El IBAN debe empezar con un código de país de dos letras. Banco iDEAL + Dirección de correo electrónico no válida: %s Compra ahora y paga después con Klarna. diff --git a/payments-ui-core/res/values-et-rEE/strings.xml b/payments-ui-core/res/values-et-rEE/strings.xml index d53ed3fb6cf..9191d6543b4 100644 --- a/payments-ui-core/res/values-et-rEE/strings.xml +++ b/payments-ui-core/res/values-et-rEE/strings.xml @@ -38,6 +38,8 @@ Eeslinn Küla või alev + + Otsi suuruse intressivaba osamaksega abil]]> @@ -64,6 +66,7 @@ Teie IBAN peab algama kahetähelise riigikoodiga. iDEALi pank + Kehtetu e-posti address: %s Ostke nüüd või makske hiljem Klarnaga. diff --git a/payments-ui-core/res/values-fi/strings.xml b/payments-ui-core/res/values-fi/strings.xml index de33c6bed90..d8439d60b8a 100644 --- a/payments-ui-core/res/values-fi/strings.xml +++ b/payments-ui-core/res/values-fi/strings.xml @@ -38,6 +38,8 @@ Esikaupunki Kylä tai kaupunkikunta + + Etsi korottomassa osassa maksutavalla ]]> @@ -64,6 +66,7 @@ IBAN-tilinumero alkaa kaksikirjaimisella maakoodilla. iDEAL-pankki + Virheellinen sähköpostiosoite: %s Osta nyt tai maksa myöhemmin Klarnalla. diff --git a/payments-ui-core/res/values-fil/strings.xml b/payments-ui-core/res/values-fil/strings.xml index 367b9a44d2e..74c36faf104 100644 --- a/payments-ui-core/res/values-fil/strings.xml +++ b/payments-ui-core/res/values-fil/strings.xml @@ -38,6 +38,8 @@ Suburb Bayan o Township + + Maghanap na walang interes na pagbabayad ng gamit ang ]]> @@ -64,6 +66,7 @@ Ang iyong IBAN ay dapat magsimula sa dalawang letra na code ng bansa. iDEAL Bank + Hindi valid na email address: %s Bumili ngayon o magbayad sa ibang pagkakataon gamit ang Klarna. diff --git a/payments-ui-core/res/values-fr-rCA/strings.xml b/payments-ui-core/res/values-fr-rCA/strings.xml index 5ad58b697ef..3ebf9f455a1 100644 --- a/payments-ui-core/res/values-fr-rCA/strings.xml +++ b/payments-ui-core/res/values-fr-rCA/strings.xml @@ -38,6 +38,8 @@ Banlieue Village ou canton + + Rechercher versements sans intérêts de avec ]]> @@ -64,6 +66,7 @@ Votre IBAN doit commencer par un code pays à deux lettres. Banque iDEAL + Adresse de courriel non valide : %s Payez maintenant ou plus tard avec Klarna. diff --git a/payments-ui-core/res/values-fr/strings.xml b/payments-ui-core/res/values-fr/strings.xml index cf4c478a0cb..7da27e531e6 100644 --- a/payments-ui-core/res/values-fr/strings.xml +++ b/payments-ui-core/res/values-fr/strings.xml @@ -38,6 +38,8 @@ Banlieue Village ou commune + + Rechercher versements sans intérêts de avec ]]> @@ -64,6 +66,7 @@ Votre IBAN doit commencer par un code pays à deux lettres. Banque iDEAL + Adresse e-mail non valide : %s Paiement différé avec Klarna. diff --git a/payments-ui-core/res/values-hr/strings.xml b/payments-ui-core/res/values-hr/strings.xml index da6f8a85a28..6816934f7ad 100644 --- a/payments-ui-core/res/values-hr/strings.xml +++ b/payments-ui-core/res/values-hr/strings.xml @@ -38,6 +38,8 @@ Predgrađe Selo ili grad + + Pretraži beskamatnih obroka po koristeći ]]> @@ -64,6 +66,7 @@ Vaš bi IBAN trebao započeti s kodom države od dva slova. iDEAL Bank + Nevažeća adresa e-pošte: %s Kupite sada ili platite kasnije pomoću Klarne diff --git a/payments-ui-core/res/values-hu/strings.xml b/payments-ui-core/res/values-hu/strings.xml index c0708bfd836..b71cba8761b 100644 --- a/payments-ui-core/res/values-hu/strings.xml +++ b/payments-ui-core/res/values-hu/strings.xml @@ -38,6 +38,8 @@ Külváros Falu vagy község + + Keresés , egyenként összegű kamatmentes részletben használatával]]> @@ -64,6 +66,7 @@ Az IBAN-számának egy kétbetűs országkóddal kell kezdődnie. iDEAL bank + Érvénytelen e-mail-cím: %s Megvásárlás most, vagy fizetés később Klarnával. diff --git a/payments-ui-core/res/values-in/strings.xml b/payments-ui-core/res/values-in/strings.xml index 1fce9261552..1835ef72faa 100644 --- a/payments-ui-core/res/values-in/strings.xml +++ b/payments-ui-core/res/values-in/strings.xml @@ -38,6 +38,8 @@ Suburb Kelurahan atau Kecamatan + + Cari angsuran bebas bunga sebesar dengan ]]> @@ -64,6 +66,7 @@ IBAN Anda harus diawali dengan kode negara dua-huruf. iDEAL Bank + Alamat email tidak valid: %s Beli sekarang atau bayar nanti dengan Klarna. diff --git a/payments-ui-core/res/values-it/strings.xml b/payments-ui-core/res/values-it/strings.xml index 1e33ea89f49..479b84c3c47 100644 --- a/payments-ui-core/res/values-it/strings.xml +++ b/payments-ui-core/res/values-it/strings.xml @@ -38,6 +38,8 @@ Località Città o località + + Cerca rate senza interessi di con ]]> @@ -64,6 +66,7 @@ L\'IBAN deve iniziare con un codice di paese di due lettere. Banca iDEAL + Indirizzo email non valido: %s Acquista ora o paga dopo con Klarna. diff --git a/payments-ui-core/res/values-ja/strings.xml b/payments-ui-core/res/values-ja/strings.xml index d52f7634d29..168230d1fe9 100644 --- a/payments-ui-core/res/values-ja/strings.xml +++ b/payments-ui-core/res/values-ja/strings.xml @@ -38,6 +38,8 @@ 近郊 村またはタウンシップ + + 検索 で毎回 ずつ、無利息の 回の分割払いで支払う]]> @@ -64,6 +66,7 @@ IBANコードの先頭には 2 文字の国コードを入力してください。 iDEAL銀行 + メールアドレスが無効です: %s Klarna で後払いします。 diff --git a/payments-ui-core/res/values-ko/strings.xml b/payments-ui-core/res/values-ko/strings.xml index 4a3a731b22d..93e1c1e9735 100644 --- a/payments-ui-core/res/values-ko/strings.xml +++ b/payments-ui-core/res/values-ko/strings.xml @@ -38,6 +38,8 @@ 교외 마을 또는 면/리 + + 검색 회 무이자 결제로 대금을 지급하십시오.]]> @@ -64,6 +66,7 @@ IBAN은 두 글자의 국가 코드로 시작해야 합니다. iDEAL 은행 + 잘못된 이메일 주소: %s 지금 구매하거나 Klarna로 나중에 결제 diff --git a/payments-ui-core/res/values-lt-rLT/strings.xml b/payments-ui-core/res/values-lt-rLT/strings.xml index 18f4f69a768..4d01a6485a1 100644 --- a/payments-ui-core/res/values-lt-rLT/strings.xml +++ b/payments-ui-core/res/values-lt-rLT/strings.xml @@ -38,6 +38,8 @@ Priemiestis Kaimas arba miestelis + + Paieška be palūkanų mokėjimus naudojant ]]> @@ -64,6 +66,7 @@ Jūsų IBAN turėtų prasidėti šalies kodu iš dviejų raidžių. iDEAL bankas + Netinkamas el. pašto adresas: %s Pirkite dabar arba mokėkite vėliau naudodami \"Klarna\". diff --git a/payments-ui-core/res/values-lv-rLV/strings.xml b/payments-ui-core/res/values-lv-rLV/strings.xml index 4004fefc055..95e17a59e43 100644 --- a/payments-ui-core/res/values-lv-rLV/strings.xml +++ b/payments-ui-core/res/values-lv-rLV/strings.xml @@ -38,6 +38,8 @@ Priekšpilsēta Ciems vai apdzīvota vieta + + Meklēšana bezprocentu maksājumiem ar ]]> @@ -64,6 +66,7 @@ IBAN sākumā jābūt divu burtu valsts kodam. iDEAL banka + Nederīga e-pasta adrese: %s Pērciet tagad vai maksājiet vēlāk ar Klarna. diff --git a/payments-ui-core/res/values-ms-rMY/strings.xml b/payments-ui-core/res/values-ms-rMY/strings.xml index 89143074f99..3dae518d392 100644 --- a/payments-ui-core/res/values-ms-rMY/strings.xml +++ b/payments-ui-core/res/values-ms-rMY/strings.xml @@ -38,6 +38,8 @@ Pinggir Bandar Kampung atau Perbandaran + + Cari kali pembayaran tanpa faedah sebanyak dengan ]]> @@ -64,6 +66,7 @@ IBAN anda harus bermula dengan kod negara dua huruf. Bank iDEAL + Alamat e-mel tidak sah: %s Beli sekarang atau bayar kemudian dengan Klarna. diff --git a/payments-ui-core/res/values-mt/strings.xml b/payments-ui-core/res/values-mt/strings.xml index 81af286b198..709f85999e3 100644 --- a/payments-ui-core/res/values-mt/strings.xml +++ b/payments-ui-core/res/values-mt/strings.xml @@ -38,6 +38,8 @@ Subborg Raħal jew Belt + + Fittex ta\' mingħajr imgħax grazzi għal ]]> @@ -64,6 +66,7 @@ L-IBAN tiegħek għandu jibda b\'kodiċi tal-pajjiż magħmul minn żewġ ittri. Il-Bank iDEAL + Indirizz tal-email ħażin: %s Ixtri issa jew ħallas iktar tard bil-Klarna. diff --git a/payments-ui-core/res/values-nb/strings.xml b/payments-ui-core/res/values-nb/strings.xml index 33e456d1b60..0480e07b00f 100644 --- a/payments-ui-core/res/values-nb/strings.xml +++ b/payments-ui-core/res/values-nb/strings.xml @@ -38,6 +38,8 @@ Forstad Landsby eller tettsted + + Søk rentefri avdrag på med ]]> @@ -64,6 +66,7 @@ IBAN-nummeret skal starte med en tosifret landskode. iDEAL Bank + Ugyldig e-postadresse: %s Kjøp nå eller betal senere med Klarna. diff --git a/payments-ui-core/res/values-nl/strings.xml b/payments-ui-core/res/values-nl/strings.xml index 5eda0776630..82b4651bb29 100644 --- a/payments-ui-core/res/values-nl/strings.xml +++ b/payments-ui-core/res/values-nl/strings.xml @@ -38,6 +38,8 @@ Voorstad Dorp of kleine stad + + Zoeken rentevrije termijnen van met ]]> @@ -64,6 +66,7 @@ Het IBAN-nummer moet beginnen met een landcode van twee letters. iDEAL-bank + Ongeldig e-mailadres: %s Koop nu of betaal later met Klarna. diff --git a/payments-ui-core/res/values-nn-rNO/strings.xml b/payments-ui-core/res/values-nn-rNO/strings.xml index 63c731f8e02..b08de11ef8d 100644 --- a/payments-ui-core/res/values-nn-rNO/strings.xml +++ b/payments-ui-core/res/values-nn-rNO/strings.xml @@ -38,6 +38,8 @@ Forstad Landsby eller township + + Søk rentefrie betalingar på med ]]> @@ -64,6 +66,7 @@ IBAN-nummeret bør starte med ei landskode med to bokstavar. iDEAL Bank + Ugyldig e-postadresse: %s Kjøp no eller betal seinare med Klarna. diff --git a/payments-ui-core/res/values-pl-rPL/strings.xml b/payments-ui-core/res/values-pl-rPL/strings.xml index f7cdb32d9b8..07d98d60697 100644 --- a/payments-ui-core/res/values-pl-rPL/strings.xml +++ b/payments-ui-core/res/values-pl-rPL/strings.xml @@ -38,6 +38,8 @@ Przedmieścia Wieś lub miasto + + Wyszukiwanie nieoprocentowanych ratach z ]]> @@ -64,6 +66,7 @@ IBAN powinien się zaczynać od dwuliterowego kodu kraju. iDEAL Bank + Nieprawidłowy adres e-mail: %s Kup teraz lub zapłać za pomocą Klarna później. diff --git a/payments-ui-core/res/values-pt-rBR/strings.xml b/payments-ui-core/res/values-pt-rBR/strings.xml index f6a0276f758..d324a82922d 100644 --- a/payments-ui-core/res/values-pt-rBR/strings.xml +++ b/payments-ui-core/res/values-pt-rBR/strings.xml @@ -38,6 +38,8 @@ Subúrbio Vilarejo ou município + + Pesquisar vezes sem juros de com ]]> @@ -64,6 +66,7 @@ Seu IBAN deve começar com um código de país de duas letras. Banco iDEAL + Endereço de e-mail inválido: %s Compre agora ou pague depois com Klarna. diff --git a/payments-ui-core/res/values-pt-rPT/strings.xml b/payments-ui-core/res/values-pt-rPT/strings.xml index bd6c200454b..40029a32660 100644 --- a/payments-ui-core/res/values-pt-rPT/strings.xml +++ b/payments-ui-core/res/values-pt-rPT/strings.xml @@ -38,6 +38,8 @@ Subúrbio Município ou localidade + + Pesquisar pagamentos sem juros de com ]]> @@ -64,6 +66,7 @@ O seu IBAN deve começar por um código de duas letras correspondente ao país. Banco iDEAL + Endereço de email inválido: %s Comprar agora ou pagar mais tarde com Klarna. diff --git a/payments-ui-core/res/values-ro-rRO/strings.xml b/payments-ui-core/res/values-ro-rRO/strings.xml index 4345f79aab4..ae9669a8300 100644 --- a/payments-ui-core/res/values-ro-rRO/strings.xml +++ b/payments-ui-core/res/values-ro-rRO/strings.xml @@ -38,6 +38,8 @@ Suburbie Sat sau comună + + Căutare plăți fără dobândă a câte cu ]]> @@ -64,6 +66,7 @@ Codul dvs. IBAN trebuie să înceapă cu un cod de țară format din două litere. iDEAL Bank + Adresă de e-mail nevalidă: %s Cumpărați acum sau plătiți mai târziu folosind Klarna. diff --git a/payments-ui-core/res/values-ru/strings.xml b/payments-ui-core/res/values-ru/strings.xml index 12509b8a8d5..0913d15f9cb 100644 --- a/payments-ui-core/res/values-ru/strings.xml +++ b/payments-ui-core/res/values-ru/strings.xml @@ -38,6 +38,8 @@ Пригород Деревня или поселок + + Поиск выплатами по через ]]> @@ -64,6 +66,7 @@ Код IBAN должен начинаться с двухбуквенного кода страны. Банк в системе iDEAL + Недопустимый эл. адрес: %s Купить немедленно или оплатить позже через систему Klarna. diff --git a/payments-ui-core/res/values-sk-rSK/strings.xml b/payments-ui-core/res/values-sk-rSK/strings.xml index d23ca0a9427..57df22c2af8 100644 --- a/payments-ui-core/res/values-sk-rSK/strings.xml +++ b/payments-ui-core/res/values-sk-rSK/strings.xml @@ -38,6 +38,8 @@ Predmestie Obec alebo obvod + + Vyhľadať bezúročných splátkach čiastku pomocou ]]> @@ -64,6 +66,7 @@ Váš IBAN by mal začínať dvojpísmenovým kódom krajiny. iDEAL banka + Neplatná e-mailová adresa: %s Kúpte teraz alebo zaplaťte neskôr pomocou služby Klarna. diff --git a/payments-ui-core/res/values-sl-rSI/strings.xml b/payments-ui-core/res/values-sl-rSI/strings.xml index b698e656b54..2d7b908c6ca 100644 --- a/payments-ui-core/res/values-sl-rSI/strings.xml +++ b/payments-ui-core/res/values-sl-rSI/strings.xml @@ -38,6 +38,8 @@ Predmestje Vas ali občina + + Iskanje obrokih po brez obresti s storitvijo .]]> @@ -64,6 +66,7 @@ Vaša koda IBAN se mora začeti z dvomestno kodo države. Banka iDEAL + Neveljaven e-poštni naslov: %s Kupite zdaj ali pa plačajte pozneje s storitvijo Klarna. diff --git a/payments-ui-core/res/values-sv/strings.xml b/payments-ui-core/res/values-sv/strings.xml index f51c234c13b..ddacd66a270 100644 --- a/payments-ui-core/res/values-sv/strings.xml +++ b/payments-ui-core/res/values-sv/strings.xml @@ -38,6 +38,8 @@ Förort By eller kommun + + Sök räntefria delbetalningar på med ]]> @@ -64,6 +66,7 @@ Ditt IBAN ska börja med en landskod (två bokstäver). iDEAL Bank + Ogiltig e-postadress: %s Köp nu eller betala senare med Klarna. diff --git a/payments-ui-core/res/values-th/strings.xml b/payments-ui-core/res/values-th/strings.xml index bbf3ea70244..6bbbfc51518 100644 --- a/payments-ui-core/res/values-th/strings.xml +++ b/payments-ui-core/res/values-th/strings.xml @@ -38,6 +38,8 @@ ชานเมือง หมู่บ้านหรือเขตการปกครองท้องถิ่น + + ค้นหา งวดแบบปลอดดอกเบี้ย งวดละ ด้วย ]]> @@ -64,6 +66,7 @@ IBAN ควรเริ่มต้นด้วยรหัสประเทศ 2 ตัวอักษร ธนาคารที่รองรับ iDEAL + ที่อยู่อีเมลไม่ถูกต้อง: %s ซื้อตอนนี้หรือจ่ายทีหลังด้วย Klarna diff --git a/payments-ui-core/res/values-tr/strings.xml b/payments-ui-core/res/values-tr/strings.xml index 3ddfaf70983..c9226b97fed 100644 --- a/payments-ui-core/res/values-tr/strings.xml +++ b/payments-ui-core/res/values-tr/strings.xml @@ -38,6 +38,8 @@ Banliyö Köy veya İlçe + + Ara ile tutarında faizsiz taksitle ödeyin]]> @@ -64,6 +66,7 @@ IBAN numaranız iki harfli bir ülke koduyla başlamalıdır. iDEAL Bank + Geçersiz e-posta adresi: %s Şimdi satın alın veya daha sonra Klarna ile ödeyin. diff --git a/payments-ui-core/res/values-vi/strings.xml b/payments-ui-core/res/values-vi/strings.xml index b4c0f99a763..88f96d8ec08 100644 --- a/payments-ui-core/res/values-vi/strings.xml +++ b/payments-ui-core/res/values-vi/strings.xml @@ -38,6 +38,8 @@ Ngoại ô Làng hoặc thị trấn + + Tìm kiếm khoản thanh toán không lãi suất là bằng ]]> @@ -64,6 +66,7 @@ IBAN của quý vị phải bắt đầu bằng mã quốc gia gồm hai chữ cái. Ngân hàng iDEAL + Địa chỉ email không hợp lệ: %s Mua ngay hoặc thanh toán sau bằng Klarna. diff --git a/payments-ui-core/res/values-zh-rHK/strings.xml b/payments-ui-core/res/values-zh-rHK/strings.xml index 5e9f05f223b..f26565f9b00 100644 --- a/payments-ui-core/res/values-zh-rHK/strings.xml +++ b/payments-ui-core/res/values-zh-rHK/strings.xml @@ -38,6 +38,8 @@ 郊區 村或鎮 + + 搜尋 支付,無利息,每期 ]]> @@ -64,6 +66,7 @@ 您的 IBAN 應以一個兩字母的國家代碼開頭。 iDEAL 銀行 + 電郵地址無效:%s 用 Klarna 先買後付。 diff --git a/payments-ui-core/res/values-zh-rTW/strings.xml b/payments-ui-core/res/values-zh-rTW/strings.xml index 1614ad80cd0..14f311e3d39 100644 --- a/payments-ui-core/res/values-zh-rTW/strings.xml +++ b/payments-ui-core/res/values-zh-rTW/strings.xml @@ -38,6 +38,8 @@ 郊區 村莊或城鎮 + + 搜尋 支付,無利息,每期 ]]> @@ -64,6 +66,7 @@ 您的 IBAN 應以一個兩字母國家代碼開頭。 iDEAL 銀行 + 電郵地址無效:%s 用 Klarna 先買後付。 diff --git a/payments-ui-core/res/values-zh/strings.xml b/payments-ui-core/res/values-zh/strings.xml index 5ebd4010ac6..37d2dfbb8d4 100644 --- a/payments-ui-core/res/values-zh/strings.xml +++ b/payments-ui-core/res/values-zh/strings.xml @@ -38,6 +38,8 @@ 市郊 村庄或城镇 + + 搜索 支付,无利息,每期 ]]> @@ -64,6 +66,7 @@ 您的 IBAN 应以一个两字母国家代码开头。 iDEAL 银行 + 邮箱地址无效:%s 用 Klarna 先买后付。 diff --git a/payments-ui-core/res/values/strings.xml b/payments-ui-core/res/values/strings.xml index f776fd1dce5..a5263ce2646 100644 --- a/payments-ui-core/res/values/strings.xml +++ b/payments-ui-core/res/values/strings.xml @@ -38,6 +38,8 @@ Suburb Village or Township + + Search interest-free payments of with ]]> @@ -64,6 +66,7 @@ Your IBAN should start with a two-letter country code. iDEAL Bank + Invalid email address: %s Buy now or pay later with Klarna. diff --git a/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/AddressElement.kt b/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/AddressElement.kt index ea807a9b7dd..d52763d91e6 100644 --- a/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/AddressElement.kt +++ b/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/AddressElement.kt @@ -2,11 +2,10 @@ package com.stripe.android.ui.core.elements import androidx.annotation.RestrictTo import androidx.annotation.VisibleForTesting -import androidx.compose.ui.text.intl.Locale -import androidx.compose.ui.text.toLowerCase import com.stripe.android.ui.core.R import com.stripe.android.ui.core.address.AddressRepository import com.stripe.android.ui.core.elements.autocomplete.DefaultIsPlacesAvailable +import com.stripe.android.ui.core.elements.autocomplete.IsPlacesAvailable import com.stripe.android.uicore.elements.CountryConfig import com.stripe.android.uicore.elements.DropdownFieldController import com.stripe.android.uicore.elements.PhoneNumberController @@ -20,7 +19,6 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flattenConcat import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) open class AddressElement constructor( @@ -34,7 +32,8 @@ open class AddressElement constructor( rawValuesMap[IdentifierSpec.Country] ), sameAsShippingElement: SameAsShippingElement?, - shippingValuesMap: Map? + shippingValuesMap: Map?, + private val isPlacesAvailable: IsPlacesAvailable = DefaultIsPlacesAvailable(), ) : SectionMultiFieldElement(_identifier) { @VisibleForTesting @@ -75,11 +74,13 @@ open class AddressElement constructor( countryCode?.let { phoneNumberElement.controller.countryDropdownController.onRawValueChange(it) } - addressRepository.get(countryCode) - ?: emptyList() - } - .onEach { fields -> - fields.forEach { field -> + (addressRepository.get(countryCode) ?: emptyList()).onEach { field -> + updateLine1WithAutocompleteAffordance( + field = field, + countryCode = countryCode, + addressType = addressType, + isPlacesAvailable = isPlacesAvailable, + ) field.setRawValue(rawValuesMap) } } @@ -166,13 +167,7 @@ open class AddressElement constructor( is AddressType.ShippingCondensed -> { // If the merchant has supplied Google Places API key, Google Places SDK is // available, and country is supported, use autocomplete - val supportedCountries = addressType.autocompleteCountries - val autocompleteSupportsCountry = supportedCountries - ?.map { it.toLowerCase(Locale.current) } - ?.contains(country?.toLowerCase(Locale.current)) == true - val autocompleteAvailable = DefaultIsPlacesAvailable().invoke() && - !addressType.googleApiKey.isNullOrBlank() - if (autocompleteSupportsCountry && autocompleteAvailable) { + if (addressType.supportsAutoComplete(country, isPlacesAvailable)) { condensed } else { expanded @@ -230,3 +225,47 @@ open class AddressElement constructor( this.rawValuesMap = rawValuesMap } } + +internal suspend fun updateLine1WithAutocompleteAffordance( + field: SectionFieldElement, + countryCode: String?, + addressType: AddressType, + isPlacesAvailable: IsPlacesAvailable, +) { + if (field.identifier == IdentifierSpec.Line1) { + val fieldController = (field as? SimpleTextElement)?.controller + val config = (fieldController as? SimpleTextFieldController?)?.textFieldConfig + val textConfig = config as? SimpleTextFieldConfig? + if (textConfig != null) { + updateLine1ConfigForAutocompleteAffordance( + textConfig = textConfig, + countryCode = countryCode, + addressType = addressType, + isPlacesAvailable = isPlacesAvailable, + ) + } + } +} + +private suspend fun updateLine1ConfigForAutocompleteAffordance( + textConfig: SimpleTextFieldConfig, + countryCode: String?, + addressType: AddressType, + isPlacesAvailable: IsPlacesAvailable, +) { + val supportsAutocomplete = (addressType as? AutocompleteCapableAddressType) + ?.supportsAutoComplete(countryCode, isPlacesAvailable) + val icon: TextFieldIcon.Trailing? = if (supportsAutocomplete == true) { + TextFieldIcon.Trailing( + idRes = R.drawable.stripe_ic_search, + isTintable = true, + contentDescription = R.string.address_search_content_description, + onClick = { + addressType.onNavigation() + } + ) + } else { + null + } + textConfig.trailingIcon.emit(icon) +} diff --git a/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/AddressSpec.kt b/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/AddressSpec.kt index e046205beab..fcaf489d06b 100644 --- a/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/AddressSpec.kt +++ b/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/AddressSpec.kt @@ -1,8 +1,11 @@ package com.stripe.android.ui.core.elements import androidx.annotation.RestrictTo +import androidx.compose.ui.text.intl.Locale +import androidx.compose.ui.text.toLowerCase import com.stripe.android.ui.core.R import com.stripe.android.ui.core.address.AddressRepository +import com.stripe.android.ui.core.elements.autocomplete.IsPlacesAvailable import com.stripe.android.uicore.elements.CountryConfig import com.stripe.android.uicore.elements.DropdownFieldController import com.stripe.android.uicore.elements.supportedBillingCountries @@ -16,21 +19,46 @@ enum class DisplayField { Country } +internal interface AutocompleteCapableAddressType { + val googleApiKey: String? + val autocompleteCountries: Set? + val onNavigation: () -> Unit + + fun supportsAutoComplete( + country: String?, + isPlacesAvailable: IsPlacesAvailable, + ): Boolean { + val supportedCountries = autocompleteCountries + val autocompleteSupportsCountry = supportedCountries + ?.map { it.toLowerCase(Locale.current) } + ?.contains(country?.toLowerCase(Locale.current)) == true + val autocompleteAvailable = isPlacesAvailable() && + !googleApiKey.isNullOrBlank() + return autocompleteSupportsCountry && autocompleteAvailable + } +} + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) sealed class AddressType { abstract val phoneNumberState: PhoneNumberState + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) data class ShippingCondensed( - val googleApiKey: String?, - val autocompleteCountries: Set?, + override val googleApiKey: String?, + override val autocompleteCountries: Set?, override val phoneNumberState: PhoneNumberState, - val onNavigation: () -> Unit - ) : AddressType() + override val onNavigation: () -> Unit + ) : AddressType(), AutocompleteCapableAddressType - data class ShippingExpanded( - override val phoneNumberState: PhoneNumberState - ) : AddressType() + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) + data class ShippingExpanded constructor( + override val googleApiKey: String?, + override val autocompleteCountries: Set?, + override val phoneNumberState: PhoneNumberState, + override val onNavigation: () -> Unit, + ) : AddressType(), AutocompleteCapableAddressType + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) data class Normal( override val phoneNumberState: PhoneNumberState = PhoneNumberState.HIDDEN diff --git a/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/TextFieldController.kt b/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/TextFieldController.kt index e365ac11240..2f9d9068094 100644 --- a/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/TextFieldController.kt +++ b/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/TextFieldController.kt @@ -70,7 +70,7 @@ sealed class TextFieldIcon { */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) class SimpleTextFieldController constructor( - private val textFieldConfig: TextFieldConfig, + val textFieldConfig: TextFieldConfig, override val showOptionalLabel: Boolean = false, initialValue: String? = null ) : TextFieldController, SectionFieldErrorController { diff --git a/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/autocomplete/PlacesClientProxy.kt b/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/autocomplete/PlacesClientProxy.kt index d1e7b7984ca..0baec40954a 100644 --- a/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/autocomplete/PlacesClientProxy.kt +++ b/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/autocomplete/PlacesClientProxy.kt @@ -169,7 +169,7 @@ internal class UnsupportedPlacesClientProxy : PlacesClientProxy { } @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -interface IsPlacesAvailable { +fun interface IsPlacesAvailable { operator fun invoke(): Boolean } diff --git a/payments-ui-core/src/test/java/com/stripe/android/ui/core/elements/AddressElementTest.kt b/payments-ui-core/src/test/java/com/stripe/android/ui/core/elements/AddressElementTest.kt index 8334d99b952..8635043e82c 100644 --- a/payments-ui-core/src/test/java/com/stripe/android/ui/core/elements/AddressElementTest.kt +++ b/payments-ui-core/src/test/java/com/stripe/android/ui/core/elements/AddressElementTest.kt @@ -16,6 +16,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner import org.robolectric.shadows.ShadowLooper +import java.util.concurrent.atomic.AtomicInteger @ExperimentalCoroutinesApi @RunWith(RobolectricTestRunner::class) @@ -174,7 +175,11 @@ class AddressElementTest { IdentifierSpec.Generic("address"), addressRepository, countryDropdownFieldController = countryDropdownFieldController, - addressType = AddressType.ShippingCondensed(null, setOf(), PhoneNumberState.REQUIRED) { }, + addressType = AddressType.ShippingCondensed( + googleApiKey = null, + autocompleteCountries = setOf(), + phoneNumberState = PhoneNumberState.REQUIRED + ) { throw AssertionError("Not Expected") }, sameAsShippingElement = null, shippingValuesMap = null ) @@ -192,7 +197,11 @@ class AddressElementTest { IdentifierSpec.Generic("address"), addressRepository, countryDropdownFieldController = countryDropdownFieldController, - addressType = AddressType.ShippingCondensed(null, setOf(), PhoneNumberState.HIDDEN) { }, + addressType = AddressType.ShippingCondensed( + googleApiKey = null, + autocompleteCountries = setOf(), + phoneNumberState = PhoneNumberState.HIDDEN + ) { throw AssertionError("Not Expected") }, sameAsShippingElement = null, shippingValuesMap = null ) @@ -209,7 +218,11 @@ class AddressElementTest { IdentifierSpec.Generic("address"), addressRepository, countryDropdownFieldController = countryDropdownFieldController, - addressType = AddressType.ShippingCondensed(null, setOf(), PhoneNumberState.OPTIONAL) { }, + addressType = AddressType.ShippingCondensed( + googleApiKey = null, + autocompleteCountries = setOf(), + phoneNumberState = PhoneNumberState.OPTIONAL + ) { throw AssertionError("Not Expected") }, sameAsShippingElement = null, shippingValuesMap = null ) @@ -227,8 +240,10 @@ class AddressElementTest { addressRepository, countryDropdownFieldController = countryDropdownFieldController, addressType = AddressType.ShippingExpanded( - PhoneNumberState.REQUIRED - ), + googleApiKey = null, + autocompleteCountries = null, + phoneNumberState = PhoneNumberState.REQUIRED, + ) { throw AssertionError("Not Expected") }, sameAsShippingElement = null, shippingValuesMap = null ) @@ -247,8 +262,10 @@ class AddressElementTest { addressRepository, countryDropdownFieldController = countryDropdownFieldController, addressType = AddressType.ShippingExpanded( - PhoneNumberState.HIDDEN - ), + googleApiKey = null, + autocompleteCountries = null, + phoneNumberState = PhoneNumberState.HIDDEN, + ) { throw AssertionError("Not Expected") }, sameAsShippingElement = null, shippingValuesMap = null ) @@ -266,8 +283,10 @@ class AddressElementTest { addressRepository, countryDropdownFieldController = countryDropdownFieldController, addressType = AddressType.ShippingExpanded( - PhoneNumberState.OPTIONAL - ), + googleApiKey = null, + autocompleteCountries = null, + phoneNumberState = PhoneNumberState.OPTIONAL, + ) { throw AssertionError("Not Expected") }, sameAsShippingElement = null, shippingValuesMap = null ) @@ -323,7 +342,7 @@ class AddressElementTest { "some key", setOf("US", "CA"), PhoneNumberState.OPTIONAL - ) { }, + ) { throw AssertionError("Not Expected") }, sameAsShippingElement = null, shippingValuesMap = null ) @@ -334,6 +353,81 @@ class AddressElementTest { assertThat(identifierSpecs.contains(IdentifierSpec.OneLineAddress)).isTrue() } + @Test + fun `AddressElement should not have OneLineAddress when places is unavailable`() = runTest { + val addressElement = AddressElement( + IdentifierSpec.Generic("address"), + addressRepository, + countryDropdownFieldController = countryDropdownFieldController, + addressType = AddressType.ShippingCondensed( + "some key", + setOf("US", "CA"), + PhoneNumberState.OPTIONAL + ) { throw AssertionError("Not Expected") }, + sameAsShippingElement = null, + shippingValuesMap = null, + isPlacesAvailable = { false }, + ) + val identifierSpecs = addressElement.fields.first().map { + it.identifier + } + assertThat(identifierSpecs.contains(IdentifierSpec.OneLineAddress)).isFalse() + } + + @Test + fun `AddressElement has no TrailingIcon on Line1 when places is unavailable`() = runTest { + val countryDropdownFieldController = DropdownFieldController( + CountryConfig(setOf("US", "CA", "JP")) + ) + val addressElement = AddressElement( + IdentifierSpec.Generic("address"), + addressRepository, + countryDropdownFieldController = countryDropdownFieldController, + addressType = AddressType.ShippingCondensed( + "some key", + setOf("US", "CA"), + PhoneNumberState.OPTIONAL + ) { throw AssertionError("Not Expected") }, + sameAsShippingElement = null, + shippingValuesMap = null, + isPlacesAvailable = { false }, + ) + countryDropdownFieldController.onValueChange(1) + + val trailingIcon = addressElement.trailingIconFor(IdentifierSpec.Line1) + assertThat(trailingIcon).isNull() + } + + @Test + fun `AddressElement has a TrailingIcon on Line1 when places is available`() = runTest { + val countryDropdownFieldController = DropdownFieldController( + CountryConfig(setOf("US", "CA", "JP")) + ) + val onNavigationCounter = AtomicInteger(0) + val addressElement = AddressElement( + IdentifierSpec.Generic("address"), + addressRepository, + countryDropdownFieldController = countryDropdownFieldController, + addressType = AddressType.ShippingExpanded( + "some key", + setOf("US", "CA"), + PhoneNumberState.OPTIONAL + ) { onNavigationCounter.getAndIncrement() }, + sameAsShippingElement = null, + shippingValuesMap = null, + isPlacesAvailable = { true }, + ) + countryDropdownFieldController.onValueChange(1) + + val line1TrailingIcon = addressElement.trailingIconFor(IdentifierSpec.Line1) + assertThat(line1TrailingIcon?.contentDescription) + .isEqualTo(R.string.address_search_content_description) + assertThat(addressElement.trailingIconFor(IdentifierSpec.Line2)).isNull() + + line1TrailingIcon?.onClick?.invoke() + assertThat(onNavigationCounter.get()).isEqualTo(1) + } + @Test fun `when google api key not supplied, condensed shipping address element is not one line address element`() = runTest { val addressElement = AddressElement( @@ -344,7 +438,7 @@ class AddressElementTest { null, setOf(), PhoneNumberState.OPTIONAL - ) { }, + ) { throw AssertionError("Not Expected") }, sameAsShippingElement = null, shippingValuesMap = null ) @@ -362,8 +456,10 @@ class AddressElementTest { addressRepository, countryDropdownFieldController = countryDropdownFieldController, addressType = AddressType.ShippingExpanded( - PhoneNumberState.OPTIONAL - ), + googleApiKey = null, + autocompleteCountries = null, + phoneNumberState = PhoneNumberState.OPTIONAL, + ) { throw AssertionError("Not Expected") }, sameAsShippingElement = null, shippingValuesMap = null ) @@ -410,3 +506,12 @@ class AddressElementTest { assertThat(country()).isEqualTo("US") } } + +private suspend fun AddressElement.trailingIconFor( + identifierSpec: IdentifierSpec +): TextFieldIcon.Trailing? { + val fieldForSpec = fields.first().first { it.identifier == identifierSpec } + val controllerForSpec = (fieldForSpec as SimpleTextElement).controller + val trailingIcon = (controllerForSpec as SimpleTextFieldController).textFieldConfig.trailingIcon + return trailingIcon.value as? TextFieldIcon.Trailing? +} diff --git a/payments-ui-core/src/test/java/com/stripe/android/ui/core/elements/AutocompleteCapableAddressTypeTest.kt b/payments-ui-core/src/test/java/com/stripe/android/ui/core/elements/AutocompleteCapableAddressTypeTest.kt new file mode 100644 index 00000000000..e2789d5a82f --- /dev/null +++ b/payments-ui-core/src/test/java/com/stripe/android/ui/core/elements/AutocompleteCapableAddressTypeTest.kt @@ -0,0 +1,87 @@ +package com.stripe.android.ui.core.elements + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +internal class AutocompleteCapableAddressTypeTest { + @Test + fun `supportsAutoComplete returns true when available and supported`() { + val subject = createSubject(googleApiKey = "example", autocompleteCountries = setOf("US")) + assertThat( + subject.supportsAutoComplete("US") { + true + } + ).isTrue() + } + + @Test + fun `supportsAutoComplete returns false when not available but supported`() { + val subject = createSubject(googleApiKey = "example", autocompleteCountries = setOf("CAN")) + assertThat( + subject.supportsAutoComplete("US") { + true + } + ).isFalse() + } + + @Test + fun `supportsAutoComplete returns false when available but not supported`() { + val subject = createSubject(googleApiKey = "example", autocompleteCountries = setOf("US")) + assertThat( + subject.supportsAutoComplete("US") { + false + } + ).isFalse() + } + + @Test + fun `supportsAutoComplete returns false when available and supported but missing api key`() { + val subject = createSubject(googleApiKey = null, autocompleteCountries = setOf("US")) + assertThat( + subject.supportsAutoComplete("US") { + true + } + ).isFalse() + } + + @Test + fun `supportsAutoComplete returns true with lowercase autocomplete country`() { + val subject = createSubject(googleApiKey = "example", autocompleteCountries = setOf("us")) + assertThat( + subject.supportsAutoComplete("US") { + true + } + ).isTrue() + } + + @Test + fun `supportsAutoComplete returns true when available and supported with lowercase country`() { + val subject = createSubject(googleApiKey = "example", autocompleteCountries = setOf("US")) + assertThat( + subject.supportsAutoComplete("us") { + true + } + ).isTrue() + } + + @Test + fun `supportsAutoComplete returns true with lowercase countries`() { + val subject = createSubject(googleApiKey = "example", autocompleteCountries = setOf("us")) + assertThat( + subject.supportsAutoComplete("us") { + true + } + ).isTrue() + } + + private fun createSubject( + googleApiKey: String?, + autocompleteCountries: Set?, + ): AutocompleteCapableAddressType { + return AddressType.ShippingExpanded( + googleApiKey = googleApiKey, + autocompleteCountries = autocompleteCountries, + phoneNumberState = PhoneNumberState.REQUIRED + ) {} + } +} diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AddressSpecFactory.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AddressSpecFactory.kt new file mode 100644 index 00000000000..18cadd104ce --- /dev/null +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AddressSpecFactory.kt @@ -0,0 +1,57 @@ +package com.stripe.android.paymentsheet.addresselement + +import com.stripe.android.ui.core.elements.AddressSpec +import com.stripe.android.ui.core.elements.AddressType +import com.stripe.android.ui.core.elements.PhoneNumberState + +internal object AddressSpecFactory { + fun create( + condensedForm: Boolean, + config: AddressLauncher.Configuration?, + onNavigation: () -> Unit + ): AddressSpec { + val phoneNumberState = parsePhoneNumberConfig(config?.additionalFields?.phone) + val addressSpec = if (condensedForm) { + AddressSpec( + showLabel = false, + type = AddressType.ShippingCondensed( + googleApiKey = config?.googlePlacesApiKey, + autocompleteCountries = config?.autocompleteCountries, + phoneNumberState = phoneNumberState, + onNavigation = onNavigation, + ) + ) + } else { + AddressSpec( + showLabel = false, + type = AddressType.ShippingExpanded( + googleApiKey = config?.googlePlacesApiKey, + autocompleteCountries = config?.autocompleteCountries, + phoneNumberState = phoneNumberState, + onNavigation = onNavigation, + ) + ) + } + + val addressSpecWithAllowedCountries = config?.allowedCountries?.run { + addressSpec.copy(allowedCountryCodes = this) + } + + return addressSpecWithAllowedCountries ?: addressSpec + } + + // This mapping is required to prevent merchants from depending on ui-core. + fun parsePhoneNumberConfig( + configuration: AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration? + ): PhoneNumberState { + return when (configuration) { + AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.HIDDEN -> + PhoneNumberState.HIDDEN + AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.OPTIONAL -> + PhoneNumberState.OPTIONAL + AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.REQUIRED -> + PhoneNumberState.REQUIRED + null -> PhoneNumberState.OPTIONAL + } + } +} diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/InputAddressViewModel.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/InputAddressViewModel.kt index 4c371caf43f..55c82de0446 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/InputAddressViewModel.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/InputAddressViewModel.kt @@ -10,11 +10,8 @@ import com.stripe.android.paymentsheet.PaymentSheet import com.stripe.android.paymentsheet.addresselement.analytics.AddressLauncherEventReporter import com.stripe.android.paymentsheet.injection.InputAddressViewModelSubcomponent import com.stripe.android.ui.core.FormController -import com.stripe.android.ui.core.elements.AddressSpec -import com.stripe.android.ui.core.elements.AddressType import com.stripe.android.ui.core.elements.IdentifierSpec import com.stripe.android.ui.core.elements.LayoutSpec -import com.stripe.android.ui.core.elements.PhoneNumberState import com.stripe.android.ui.core.injection.FormControllerSubcomponent import com.stripe.android.uicore.forms.FormFieldEntry import kotlinx.coroutines.flow.MutableStateFlow @@ -48,14 +45,7 @@ internal class InputAddressViewModel @Inject constructor( val oldAddress = _collectedAddress.value val autocompleteAddress = AddressDetails( name = oldAddress?.name ?: it?.name, - address = oldAddress?.address?.copy( - city = oldAddress.address.city ?: it?.address?.city, - country = oldAddress.address.country ?: it?.address?.country, - line1 = oldAddress.address.line1 ?: it?.address?.line1, - line2 = oldAddress.address.line2 ?: it?.address?.line2, - postalCode = oldAddress.address.postalCode ?: it?.address?.postalCode, - state = oldAddress.address.state ?: it?.address?.state - ) ?: it?.address, + address = it?.address ?: oldAddress?.address, phoneNumber = oldAddress?.phoneNumber ?: it?.phoneNumber, isCheckboxSelected = oldAddress?.isCheckboxSelected ?: it?.isCheckboxSelected @@ -109,48 +99,9 @@ internal class InputAddressViewModel @Inject constructor( } private fun buildFormSpec(condensedForm: Boolean): LayoutSpec { - val phoneNumberState = parsePhoneNumberConfig(args.config?.additionalFields?.phone) - val addressSpec = if (condensedForm) { - AddressSpec( - showLabel = false, - type = AddressType.ShippingCondensed( - googleApiKey = args.config?.googlePlacesApiKey, - autocompleteCountries = args.config?.autocompleteCountries, - phoneNumberState = phoneNumberState - ) { - viewModelScope.launch { - val addressDetails = getCurrentAddress() - addressDetails?.let { - _collectedAddress.emit(it) - } - addressDetails?.address?.country?.let { - navigator.navigateTo( - AddressElementScreen.Autocomplete( - country = it - ) - ) - } - } - } - ) - } else { - AddressSpec( - showLabel = false, - type = AddressType.ShippingExpanded( - phoneNumberState = phoneNumberState - ) - ) - } - - val addressSpecWithAllowedCountries = args.config?.allowedCountries?.run { - addressSpec.copy(allowedCountryCodes = this) - } - - return LayoutSpec( - listOf( - addressSpecWithAllowedCountries ?: addressSpec - ) - ) + val config = args.config + val spec = AddressSpecFactory.create(condensedForm, config, ::navigateToAutocompleteScreen) + return LayoutSpec(listOf(spec)) } fun clickPrimaryButton( @@ -193,6 +144,22 @@ internal class InputAddressViewModel @Inject constructor( _checkboxChecked.value = newValue } + private fun navigateToAutocompleteScreen() { + viewModelScope.launch { + val addressDetails = getCurrentAddress() + addressDetails?.let { + _collectedAddress.emit(it) + } + addressDetails?.address?.country?.let { + navigator.navigateTo( + AddressElementScreen.Autocomplete( + country = it + ) + ) + } + } + } + internal class Factory( private val injector: NonFallbackInjector ) : ViewModelProvider.Factory, NonFallbackInjectable { @@ -208,21 +175,4 @@ internal class InputAddressViewModel @Inject constructor( .build().inputAddressViewModel as T } } - - internal companion object { - // This mapping is required to prevent merchants from depending on ui-core - fun parsePhoneNumberConfig( - configuration: AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration? - ): PhoneNumberState { - return when (configuration) { - AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.HIDDEN -> - PhoneNumberState.HIDDEN - AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.OPTIONAL -> - PhoneNumberState.OPTIONAL - AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.REQUIRED -> - PhoneNumberState.REQUIRED - null -> PhoneNumberState.OPTIONAL - } - } - } } diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/addresselement/AddressSpecFactoryTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/addresselement/AddressSpecFactoryTest.kt new file mode 100644 index 00000000000..d93502ba441 --- /dev/null +++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/addresselement/AddressSpecFactoryTest.kt @@ -0,0 +1,122 @@ +package com.stripe.android.paymentsheet.addresselement + +import com.google.common.truth.Truth.assertThat +import com.stripe.android.ui.core.elements.AddressType +import com.stripe.android.ui.core.elements.PhoneNumberState +import org.junit.Test + +class AddressSpecFactoryTest { + private val required = AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.REQUIRED + + @Test + fun `null config creates condensed addressSpec`() { + val addressSpec = AddressSpecFactory.create( + condensedForm = true, + config = null, + onNavigation = {} + ) + assertThat(addressSpec.showLabel).isFalse() + assertThat(addressSpec.allowedCountryCodes).isNotEmpty() + val type = addressSpec.type as AddressType.ShippingCondensed + assertThat(type.googleApiKey).isNull() + assertThat(type.autocompleteCountries).isNull() + assertThat(type.phoneNumberState).isEqualTo(PhoneNumberState.OPTIONAL) + } + + @Test + fun `config creates condensed addressSpec`() { + val addressSpec = AddressSpecFactory.create( + condensedForm = true, + config = AddressLauncher.Configuration( + googlePlacesApiKey = "apiKey", + autocompleteCountries = setOf("US", "CA"), + additionalFields = AddressLauncher.AdditionalFieldsConfiguration(phone = required) + ), + onNavigation = {} + ) + assertThat(addressSpec.showLabel).isFalse() + assertThat(addressSpec.allowedCountryCodes).isEmpty() + val type = addressSpec.type as AddressType.ShippingCondensed + assertThat(type.googleApiKey).isEqualTo("apiKey") + assertThat(type.autocompleteCountries).isEqualTo(setOf("US", "CA")) + assertThat(type.phoneNumberState).isEqualTo(PhoneNumberState.REQUIRED) + } + + @Test + fun `null config creates expanded addressSpec`() { + val addressSpec = AddressSpecFactory.create( + condensedForm = false, + config = null, + onNavigation = {} + ) + assertThat(addressSpec.showLabel).isFalse() + assertThat(addressSpec.allowedCountryCodes).isNotEmpty() + val type = addressSpec.type as AddressType.ShippingExpanded + assertThat(type.googleApiKey).isNull() + assertThat(type.autocompleteCountries).isNull() + assertThat(type.phoneNumberState).isEqualTo(PhoneNumberState.OPTIONAL) + } + + @Test + fun `config creates expanded addressSpec`() { + val addressSpec = AddressSpecFactory.create( + condensedForm = false, + config = AddressLauncher.Configuration( + googlePlacesApiKey = "apiKey", + autocompleteCountries = setOf("US", "CA"), + additionalFields = AddressLauncher.AdditionalFieldsConfiguration(phone = required) + ), + onNavigation = {} + ) + assertThat(addressSpec.showLabel).isFalse() + assertThat(addressSpec.allowedCountryCodes).isEmpty() + val type = addressSpec.type as AddressType.ShippingExpanded + assertThat(type.googleApiKey).isEqualTo("apiKey") + assertThat(type.autocompleteCountries).isEqualTo(setOf("US", "CA")) + assertThat(type.phoneNumberState).isEqualTo(PhoneNumberState.REQUIRED) + } + + @Test + fun `onNavigation is used in address spec`() { + var navigationCounter = 0 + val onNavigation: () -> Unit = { + navigationCounter++ + } + val addressSpec = AddressSpecFactory.create( + condensedForm = false, + config = null, + onNavigation = onNavigation + ) + val type = addressSpec.type as AddressType.ShippingExpanded + assertThat(navigationCounter).isEqualTo(0) + type.onNavigation() + assertThat(navigationCounter).isEqualTo(1) + } + + @Test + fun `parsePhoneNumberConfig HIDDEN`() { + val config = AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.HIDDEN + assertThat(AddressSpecFactory.parsePhoneNumberConfig(config)) + .isEqualTo(PhoneNumberState.HIDDEN) + } + + @Test + fun `parsePhoneNumberConfig OPTIONAL`() { + val config = AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.OPTIONAL + assertThat(AddressSpecFactory.parsePhoneNumberConfig(config)) + .isEqualTo(PhoneNumberState.OPTIONAL) + } + + @Test + fun `parsePhoneNumberConfig REQUIRED`() { + val config = AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.REQUIRED + assertThat(AddressSpecFactory.parsePhoneNumberConfig(config)) + .isEqualTo(PhoneNumberState.REQUIRED) + } + + @Test + fun `parsePhoneNumberConfig null`() { + assertThat(AddressSpecFactory.parsePhoneNumberConfig(null)) + .isEqualTo(PhoneNumberState.OPTIONAL) + } +} diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/addresselement/InputAddressViewModelTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/addresselement/InputAddressViewModelTest.kt index d2e80b6e33d..7936acda8c6 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/addresselement/InputAddressViewModelTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/addresselement/InputAddressViewModelTest.kt @@ -89,6 +89,23 @@ class InputAddressViewModelTest { assertThat(viewModel.collectedAddress.value).isEqualTo(expectedAddress) } + @Test + fun `takes only fields in new address`() = runTest(UnconfinedTestDispatcher()) { + val usAddress = AddressDetails(name = "skyler", address = PaymentSheet.Address(country = "US")) + val flow = MutableStateFlow(usAddress) + whenever(navigator.getResultFlow(any())).thenReturn(flow) + + val viewModel = createViewModel() + assertThat(viewModel.collectedAddress.value).isEqualTo(usAddress) + + val expectedAddress = AddressDetails( + name = "skyler", + address = PaymentSheet.Address(country = "CAN", line1 = "foobar") + ) + flow.tryEmit(expectedAddress) + assertThat(viewModel.collectedAddress.value).isEqualTo(expectedAddress) + } + @Test fun `default address from merchant is parsed`() = runTest(UnconfinedTestDispatcher()) { val expectedAddress = AddressDetails(name = "skyler", address = PaymentSheet.Address(country = "US"))