From 0e9d83b42ba8d43060df88c520c8f536f7810bfa Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Mon, 1 May 2023 15:03:46 +0200 Subject: [PATCH 001/161] Don't set a default value for threeDSRequestorAppURL COAND-744 --- .../provider/Adyen3DS2ComponentProvider.kt | 3 --- .../ui/model/Adyen3DS2ComponentParams.kt | 2 +- .../ui/model/Adyen3DS2ComponentParamsMapper.kt | 9 +++------ .../internal/ui/DefaultAdyen3DS2DelegateTest.kt | 2 +- .../model/Adyen3DS2ComponentParamsMapperTest.kt | 17 ++++++++--------- 5 files changed, 13 insertions(+), 20 deletions(-) diff --git a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/provider/Adyen3DS2ComponentProvider.kt b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/provider/Adyen3DS2ComponentProvider.kt index 58e3b706a8..15b27d0407 100644 --- a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/provider/Adyen3DS2ComponentProvider.kt +++ b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/provider/Adyen3DS2ComponentProvider.kt @@ -40,7 +40,6 @@ import com.adyen.checkout.components.core.internal.util.viewModelFactory import com.adyen.checkout.core.internal.data.api.HttpClientFactory import com.adyen.checkout.ui.core.internal.DefaultRedirectHandler import com.adyen.threeds2.ThreeDS2Service -import com.adyen.threeds2.parameters.ChallengeParameters import kotlinx.coroutines.Dispatchers @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @@ -79,11 +78,9 @@ class Adyen3DS2ComponentProvider( savedStateHandle: SavedStateHandle, application: Application, ): Adyen3DS2Delegate { - val defaultThreeDSRequestorAppURL = ChallengeParameters.getEmbeddedRequestorAppURL(application) val componentParams = componentParamsMapper.mapToParams( adyen3DS2Configuration = configuration, sessionParams = null, - defaultThreeDSRequestorAppURL = defaultThreeDSRequestorAppURL, ) val httpClient = HttpClientFactory.getHttpClient(componentParams.environment) val submitFingerprintService = SubmitFingerprintService(httpClient) diff --git a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/model/Adyen3DS2ComponentParams.kt b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/model/Adyen3DS2ComponentParams.kt index b82afa0def..3f81e86b59 100644 --- a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/model/Adyen3DS2ComponentParams.kt +++ b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/model/Adyen3DS2ComponentParams.kt @@ -24,5 +24,5 @@ internal data class Adyen3DS2ComponentParams( override val isCreatedByDropIn: Boolean, override val amount: Amount, val uiCustomization: UiCustomization?, - val threeDSRequestorAppURL: String, + val threeDSRequestorAppURL: String?, ) : ComponentParams diff --git a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/model/Adyen3DS2ComponentParamsMapper.kt b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/model/Adyen3DS2ComponentParamsMapper.kt index 73a838a3eb..414d1060b9 100644 --- a/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/model/Adyen3DS2ComponentParamsMapper.kt +++ b/3ds2/src/main/java/com/adyen/checkout/adyen3ds2/internal/ui/model/Adyen3DS2ComponentParamsMapper.kt @@ -20,17 +20,14 @@ internal class Adyen3DS2ComponentParamsMapper( fun mapToParams( adyen3DS2Configuration: Adyen3DS2Configuration, sessionParams: SessionParams?, - defaultThreeDSRequestorAppURL: String, ): Adyen3DS2ComponentParams { return adyen3DS2Configuration - .mapToParamsInternal(defaultThreeDSRequestorAppURL) + .mapToParamsInternal() .override(overrideComponentParams) .override(sessionParams ?: overrideSessionParams) } - private fun Adyen3DS2Configuration.mapToParamsInternal( - defaultThreeDSRequestorAppURL: String, - ): Adyen3DS2ComponentParams { + private fun Adyen3DS2Configuration.mapToParamsInternal(): Adyen3DS2ComponentParams { return Adyen3DS2ComponentParams( shopperLocale = shopperLocale, environment = environment, @@ -39,7 +36,7 @@ internal class Adyen3DS2ComponentParamsMapper( isCreatedByDropIn = false, amount = amount, uiCustomization = uiCustomization, - threeDSRequestorAppURL = threeDSRequestorAppURL ?: defaultThreeDSRequestorAppURL, + threeDSRequestorAppURL = threeDSRequestorAppURL, ) } diff --git a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt index 6eef3c770e..defd658cfc 100644 --- a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt +++ b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/DefaultAdyen3DS2DelegateTest.kt @@ -91,7 +91,7 @@ internal class DefaultAdyen3DS2DelegateTest( observerRepository = ActionObserverRepository(), savedStateHandle = SavedStateHandle(), componentParams = Adyen3DS2ComponentParamsMapper(null, null) - .mapToParams(configuration, null, "embeddedRequestorAppUrl"), + .mapToParams(configuration, null), submitFingerprintRepository = submitFingerprintRepository, paymentDataRepository = paymentDataRepository, adyen3DS2Serializer = adyen3DS2Serializer, diff --git a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/model/Adyen3DS2ComponentParamsMapperTest.kt b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/model/Adyen3DS2ComponentParamsMapperTest.kt index 376df0a642..cb0f29589b 100644 --- a/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/model/Adyen3DS2ComponentParamsMapperTest.kt +++ b/3ds2/src/test/java/com/adyen/checkout/adyen3ds2/internal/ui/model/Adyen3DS2ComponentParamsMapperTest.kt @@ -13,7 +13,7 @@ import com.adyen.checkout.components.core.Amount import com.adyen.checkout.components.core.internal.ui.model.GenericComponentParams import com.adyen.checkout.core.Environment import com.adyen.threeds2.customization.UiCustomization -import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import java.util.Locale @@ -25,11 +25,11 @@ internal class Adyen3DS2ComponentParamsMapperTest { .build() val params = Adyen3DS2ComponentParamsMapper(null, null) - .mapToParams(adyen3DS2Configuration, null, TEST_REQUESTOR_APP_URL) + .mapToParams(adyen3DS2Configuration, null) val expected = getAdyen3DS2ComponentParams() - Assertions.assertEquals(expected, params) + assertEquals(expected, params) } @Test @@ -43,14 +43,14 @@ internal class Adyen3DS2ComponentParamsMapperTest { .build() val params = Adyen3DS2ComponentParamsMapper(null, null) - .mapToParams(adyen3DS2Configuration, null, TEST_REQUESTOR_APP_URL) + .mapToParams(adyen3DS2Configuration, null) val expected = getAdyen3DS2ComponentParams( uiCustomization = uiCustomization, threeDSRequestorAppURL = testUrl, ) - Assertions.assertEquals(expected, params) + assertEquals(expected, params) } @Test @@ -73,7 +73,7 @@ internal class Adyen3DS2ComponentParamsMapperTest { ) val params = Adyen3DS2ComponentParamsMapper(overrideParams, null) - .mapToParams(adyen3DS2Configuration, null, TEST_REQUESTOR_APP_URL) + .mapToParams(adyen3DS2Configuration, null) val expected = getAdyen3DS2ComponentParams( shopperLocale = Locale.GERMAN, @@ -87,7 +87,7 @@ internal class Adyen3DS2ComponentParamsMapperTest { ), ) - Assertions.assertEquals(expected, params) + assertEquals(expected, params) } private fun getAdyen3DS2ConfigurationBuilder() = Adyen3DS2Configuration.Builder( @@ -105,7 +105,7 @@ internal class Adyen3DS2ComponentParamsMapperTest { isCreatedByDropIn: Boolean = false, amount: Amount = Amount.EMPTY, uiCustomization: UiCustomization? = null, - threeDSRequestorAppURL: String = TEST_REQUESTOR_APP_URL, + threeDSRequestorAppURL: String? = null, ) = Adyen3DS2ComponentParams( shopperLocale = shopperLocale, environment = environment, @@ -120,6 +120,5 @@ internal class Adyen3DS2ComponentParamsMapperTest { companion object { private const val TEST_CLIENT_KEY_1 = "test_qwertyuiopasdfghjklzxcvbnmqwerty" private const val TEST_CLIENT_KEY_2 = "live_qwertyui34566776787zxcvbnmqwerty" - private const val TEST_REQUESTOR_APP_URL = "TEST_REQUESTOR_APP_URL" } } From e51da0983d2b2cfa188a8905c853f08323d02bff Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Tue, 2 May 2023 16:35:50 +0200 Subject: [PATCH 002/161] Correctly serialize ShippingAddressParameters COAND-753 --- .../com/adyen/checkout/googlepay/ShippingAddressParameters.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/googlepay/src/main/java/com/adyen/checkout/googlepay/ShippingAddressParameters.kt b/googlepay/src/main/java/com/adyen/checkout/googlepay/ShippingAddressParameters.kt index 5a77bacff8..133d6f2b5a 100644 --- a/googlepay/src/main/java/com/adyen/checkout/googlepay/ShippingAddressParameters.kt +++ b/googlepay/src/main/java/com/adyen/checkout/googlepay/ShippingAddressParameters.kt @@ -41,7 +41,7 @@ data class ShippingAddressParameters( return try { JSONObject().apply { putOpt(ALLOWED_COUNTRY_CODES, serializeOptStringList(modelObject.allowedCountryCodes)) - putOpt(ALLOWED_COUNTRY_CODES, modelObject.isPhoneNumberRequired) + putOpt(PHONE_NUMBER_REQUIRED, modelObject.isPhoneNumberRequired) } } catch (e: JSONException) { throw ModelSerializationException(ShippingAddressParameters::class.java, e) @@ -49,7 +49,7 @@ data class ShippingAddressParameters( } override fun deserialize(jsonObject: JSONObject) = ShippingAddressParameters( - allowedCountryCodes = parseOptStringList(jsonObject.optJSONArray(PHONE_NUMBER_REQUIRED)), + allowedCountryCodes = parseOptStringList(jsonObject.optJSONArray(ALLOWED_COUNTRY_CODES)), isPhoneNumberRequired = jsonObject.optBoolean(PHONE_NUMBER_REQUIRED), ) } From 62b43d65d722b31f7343141f67f041af183256de Mon Sep 17 00:00:00 2001 From: jreij Date: Wed, 3 May 2023 16:56:25 +0200 Subject: [PATCH 003/161] Remove MERCHANT_RECURRING_SERVER_URL from default.local.gradle COAND-754 --- example-app/default.local.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/example-app/default.local.gradle b/example-app/default.local.gradle index 628b1fb278..34679f12c5 100644 --- a/example-app/default.local.gradle +++ b/example-app/default.local.gradle @@ -13,7 +13,6 @@ android { buildConfigField "String", "CHECKOUT_API_KEY", "\"\"" buildConfigField "String", "CLIENT_KEY", "\"\"" buildConfigField "String", "SHOPPER_REFERENCE", "\"\"" - buildConfigField "String", "MERCHANT_RECURRING_SERVER_URL", "\"\"" } release { From 0e26960b2a38b895c32bf860fdde220b64a694d3 Mon Sep 17 00:00:00 2001 From: jreij Date: Wed, 3 May 2023 17:03:18 +0200 Subject: [PATCH 004/161] Fix kdocs for removing stored payment methods COAND-754 --- .../com/adyen/checkout/dropin/BaseDropInServiceContract.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/BaseDropInServiceContract.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/BaseDropInServiceContract.kt index d0b2cacd45..f4de6d5cfc 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/BaseDropInServiceContract.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/BaseDropInServiceContract.kt @@ -19,14 +19,14 @@ interface BaseDropInServiceContract { * [DropInConfiguration.Builder.setEnableRemovingStoredPaymentMethods] to enable this feature. * * In this method you should make the network call to tell your server to make a call to the - * /Recurring//disable endpoint. This method is called when the user initiates + * DELETE /storedPaymentMethods endpoint. This method is called when the user initiates * removing a stored payment method using the remove button. * * We provide [storedPaymentMethod] that contains the id of the stored payment method to be removed * in the field [StoredPaymentMethod.id]. * * Asynchronous handling: since this method runs on the main thread, you should make sure the - * /Recurring//disable call and any other long running operation is made on a background thread. + * DELETE /storedPaymentMethods call and any other long running operation is made on a background thread. * * Use [sendRecurringResult] to send the final result of this call back to the Drop-in. * @@ -73,7 +73,7 @@ interface BaseDropInServiceContract { fun sendOrderResult(result: OrderDropInServiceResult) /** - * Allows sending the result of the /Recurring/ network call. + * Allows sending the result of the DELETE /storedPaymentMethods network call. * * Call this method with a [RecurringDropInServiceResult] depending on the response of the corresponding network * call. From b424f6543e4b3be19014af820985f0f1baad960e Mon Sep 17 00:00:00 2001 From: jreij Date: Wed, 3 May 2023 17:19:48 +0200 Subject: [PATCH 005/161] Use new DELETE /storedPaymentMethods endpoint instead of the endpoint on the classic recurring API COAND-754 --- .../example/data/api/CheckoutApiService.kt | 11 ++++++ .../example/data/api/RecurringApiService.kt | 29 --------------- .../model/RemoveStoredPaymentMethodRequest.kt | 18 ---------- .../checkout/example/di/NetworkModule.kt | 24 ------------- .../checkout/example/di/RepositoryModule.kt | 8 ----- .../repositories/PaymentsRepository.kt | 20 +++++++++++ .../repositories/RecurringRepository.kt | 28 --------------- .../service/ExampleAdvancedDropInService.kt | 35 +++++++++---------- .../example/service/ExampleDropInService.kt | 32 +++++++---------- .../checkout/example/service/RequestUtils.kt | 11 ------ 10 files changed, 61 insertions(+), 155 deletions(-) delete mode 100644 example-app/src/main/java/com/adyen/checkout/example/data/api/RecurringApiService.kt delete mode 100644 example-app/src/main/java/com/adyen/checkout/example/data/api/model/RemoveStoredPaymentMethodRequest.kt delete mode 100644 example-app/src/main/java/com/adyen/checkout/example/repositories/RecurringRepository.kt diff --git a/example-app/src/main/java/com/adyen/checkout/example/data/api/CheckoutApiService.kt b/example-app/src/main/java/com/adyen/checkout/example/data/api/CheckoutApiService.kt index b26a87fa41..120661852d 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/data/api/CheckoutApiService.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/data/api/CheckoutApiService.kt @@ -18,8 +18,12 @@ import com.adyen.checkout.example.data.api.model.SessionRequest import com.adyen.checkout.sessions.core.SessionModel import org.json.JSONObject import retrofit2.Call +import retrofit2.Response import retrofit2.http.Body +import retrofit2.http.DELETE import retrofit2.http.POST +import retrofit2.http.Path +import retrofit2.http.Query internal interface CheckoutApiService { @@ -57,4 +61,11 @@ internal interface CheckoutApiService { @POST("orders/cancel") suspend fun cancelOrderAsync(@Body request: CancelOrderRequest): JSONObject + + @DELETE("storedPaymentMethods/{recurringId}") + suspend fun removeStoredPaymentMethodAsync( + @Path("recurringId") recurringId: String, + @Query("merchantAccount") merchantAccount: String, + @Query("shopperReference") shopperReference: String, + ): Response } diff --git a/example-app/src/main/java/com/adyen/checkout/example/data/api/RecurringApiService.kt b/example-app/src/main/java/com/adyen/checkout/example/data/api/RecurringApiService.kt deleted file mode 100644 index 7a3ba61f6c..0000000000 --- a/example-app/src/main/java/com/adyen/checkout/example/data/api/RecurringApiService.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2022 Adyen N.V. - * - * This file is open source and available under the MIT license. See the LICENSE file for more info. - * - * Created by ozgur on 17/1/2022. - */ - -package com.adyen.checkout.example.data.api - -import com.adyen.checkout.example.BuildConfig -import com.adyen.checkout.example.data.api.model.RemoveStoredPaymentMethodRequest -import org.json.JSONObject -import retrofit2.http.Body -import retrofit2.http.POST - -internal interface RecurringApiService { - - companion object { - private const val defaultGradleUrl = "" - - fun isRealUrlAvailable(): Boolean { - return BuildConfig.MERCHANT_RECURRING_SERVER_URL != defaultGradleUrl - } - } - - @POST("disable") - suspend fun removeStoredPaymentMethodAsync(@Body request: RemoveStoredPaymentMethodRequest): JSONObject -} diff --git a/example-app/src/main/java/com/adyen/checkout/example/data/api/model/RemoveStoredPaymentMethodRequest.kt b/example-app/src/main/java/com/adyen/checkout/example/data/api/model/RemoveStoredPaymentMethodRequest.kt deleted file mode 100644 index d408bf0b94..0000000000 --- a/example-app/src/main/java/com/adyen/checkout/example/data/api/model/RemoveStoredPaymentMethodRequest.kt +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2022 Adyen N.V. - * - * This file is open source and available under the MIT license. See the LICENSE file for more info. - * - * Created by josephj on 29/3/2022. - */ - -package com.adyen.checkout.example.data.api.model - -import androidx.annotation.Keep - -@Keep -data class RemoveStoredPaymentMethodRequest( - val recurringDetailReference: String, - val merchantAccount: String, - val shopperReference: String -) diff --git a/example-app/src/main/java/com/adyen/checkout/example/di/NetworkModule.kt b/example-app/src/main/java/com/adyen/checkout/example/di/NetworkModule.kt index 5b60b0df80..e4ad77f02f 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/di/NetworkModule.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/di/NetworkModule.kt @@ -10,7 +10,6 @@ package com.adyen.checkout.example.di import com.adyen.checkout.example.BuildConfig import com.adyen.checkout.example.data.api.CheckoutApiService -import com.adyen.checkout.example.data.api.RecurringApiService import com.adyen.checkout.example.data.api.adapter.JSONObjectAdapter import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory @@ -36,12 +35,6 @@ object NetworkModule { "http://myserver.com/my/endpoint/" } - private val BASE_URL_RECURRING = if (RecurringApiService.isRealUrlAvailable()) { - BuildConfig.MERCHANT_RECURRING_SERVER_URL - } else { - "http://myserver.com/my/endpoint/" - } - @Singleton @Provides internal fun provideOkHttpClient(): OkHttpClient { @@ -86,24 +79,7 @@ object NetworkModule { .addConverterFactory(converterFactory) .build() - @Singleton - @Provides - @Named("RetrofitRecurring") - internal fun provideRetrofitRecurring( - okHttpClient: OkHttpClient, - converterFactory: Converter.Factory, - ): Retrofit = - Retrofit.Builder() - .baseUrl(BASE_URL_RECURRING) - .client(okHttpClient) - .addConverterFactory(converterFactory) - .build() - @Provides internal fun provideApiService(@Named("RetrofitCheckout") retrofit: Retrofit): CheckoutApiService = retrofit.create(CheckoutApiService::class.java) - - @Provides - internal fun provideRecurringApiService(@Named("RetrofitRecurring") retrofit: Retrofit): RecurringApiService = - retrofit.create(RecurringApiService::class.java) } diff --git a/example-app/src/main/java/com/adyen/checkout/example/di/RepositoryModule.kt b/example-app/src/main/java/com/adyen/checkout/example/di/RepositoryModule.kt index 3243003ede..7d363600d4 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/di/RepositoryModule.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/di/RepositoryModule.kt @@ -9,11 +9,8 @@ package com.adyen.checkout.example.di import com.adyen.checkout.example.data.api.CheckoutApiService -import com.adyen.checkout.example.data.api.RecurringApiService import com.adyen.checkout.example.repositories.PaymentsRepository import com.adyen.checkout.example.repositories.PaymentsRepositoryImpl -import com.adyen.checkout.example.repositories.RecurringRepository -import com.adyen.checkout.example.repositories.RecurringRepositoryImpl import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -27,9 +24,4 @@ object RepositoryModule { internal fun providePaymentsRepository( checkoutApiService: CheckoutApiService ): PaymentsRepository = PaymentsRepositoryImpl(checkoutApiService) - - @Provides - internal fun provideRecurringRepository( - recurringApiService: RecurringApiService - ): RecurringRepository = RecurringRepositoryImpl(recurringApiService) } diff --git a/example-app/src/main/java/com/adyen/checkout/example/repositories/PaymentsRepository.kt b/example-app/src/main/java/com/adyen/checkout/example/repositories/PaymentsRepository.kt index 90d1e97cb0..bd214f4d35 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/repositories/PaymentsRepository.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/repositories/PaymentsRepository.kt @@ -30,6 +30,11 @@ interface PaymentsRepository { suspend fun getBalance(request: BalanceRequest): JSONObject? suspend fun createOrder(orderRequest: CreateOrderRequest): JSONObject? suspend fun cancelOrder(request: CancelOrderRequest): JSONObject? + suspend fun removeStoredPaymentMethod( + storedPaymentMethodId: String, + merchantAccount: String, + shopperReference: String, + ): Boolean } @Suppress("TooManyFunctions") @@ -84,4 +89,19 @@ internal class PaymentsRepositoryImpl(private val checkoutApiService: CheckoutAp override suspend fun cancelOrder(request: CancelOrderRequest): JSONObject? = safeApiCall { checkoutApiService.cancelOrderAsync(request) } + + override suspend fun removeStoredPaymentMethod( + storedPaymentMethodId: String, + merchantAccount: String, + shopperReference: String + ): Boolean { + val response = safeApiCall { + checkoutApiService.removeStoredPaymentMethodAsync( + recurringId = storedPaymentMethodId, + merchantAccount = merchantAccount, + shopperReference = shopperReference, + ) + } + return response?.isSuccessful == true + } } diff --git a/example-app/src/main/java/com/adyen/checkout/example/repositories/RecurringRepository.kt b/example-app/src/main/java/com/adyen/checkout/example/repositories/RecurringRepository.kt deleted file mode 100644 index 0c36f703bb..0000000000 --- a/example-app/src/main/java/com/adyen/checkout/example/repositories/RecurringRepository.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2022 Adyen N.V. - * - * This file is open source and available under the MIT license. See the LICENSE file for more info. - * - * Created by ozgur on 14/1/2022. - */ - -package com.adyen.checkout.example.repositories - -import com.adyen.checkout.example.data.api.RecurringApiService -import com.adyen.checkout.example.data.api.model.RemoveStoredPaymentMethodRequest -import org.json.JSONObject - -interface RecurringRepository { - suspend fun removeStoredPaymentMethod(request: RemoveStoredPaymentMethodRequest): JSONObject? -} - -internal class RecurringRepositoryImpl( - private val recurringApiService: RecurringApiService -) : RecurringRepository { - - override suspend fun removeStoredPaymentMethod( - request: RemoveStoredPaymentMethodRequest - ): JSONObject? = safeApiCall { - recurringApiService.removeStoredPaymentMethodAsync(request) - } -} diff --git a/example-app/src/main/java/com/adyen/checkout/example/service/ExampleAdvancedDropInService.kt b/example-app/src/main/java/com/adyen/checkout/example/service/ExampleAdvancedDropInService.kt index 405e04ef32..936044d29e 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/service/ExampleAdvancedDropInService.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/service/ExampleAdvancedDropInService.kt @@ -31,7 +31,6 @@ import com.adyen.checkout.dropin.OrderDropInServiceResult import com.adyen.checkout.dropin.RecurringDropInServiceResult import com.adyen.checkout.example.data.storage.KeyValueStorage import com.adyen.checkout.example.repositories.PaymentsRepository -import com.adyen.checkout.example.repositories.RecurringRepository import com.adyen.checkout.redirect.RedirectComponent import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers @@ -54,9 +53,6 @@ class ExampleAdvancedDropInService : DropInService() { @Inject lateinit var paymentsRepository: PaymentsRepository - @Inject - lateinit var recurringRepository: RecurringRepository - @Inject lateinit var keyValueStorage: KeyValueStorage @@ -130,21 +126,25 @@ class ExampleAdvancedDropInService : DropInService() { Logger.e(TAG, "FAILED") DropInServiceResult.Error(reason = "IOException") } + isRefusedInPartialPaymentFlow(jsonResponse) -> { Logger.d(TAG, "Refused") DropInServiceResult.Error(reason = "Refused") } + isAction(jsonResponse) -> { Logger.d(TAG, "Received action") val action = Action.SERIALIZER.deserialize(jsonResponse.getJSONObject("action")) DropInServiceResult.Action(action) } + isNonFullyPaidOrder(jsonResponse) -> { Logger.d(TAG, "Received a non fully paid order") val order = getOrderFromResponse(jsonResponse) fetchPaymentMethods(order) null } + else -> { Logger.d(TAG, "Final result - ${jsonResponse.toStringPretty()}") val resultCode = if (jsonResponse.has("resultCode")) { @@ -246,6 +246,7 @@ class ExampleAdvancedDropInService : DropInService() { ) } } + else -> BalanceDropInServiceResult.Error(reason = resultCode, dismissDropIn = false) } } else { @@ -308,6 +309,7 @@ class ExampleAdvancedDropInService : DropInService() { if (shouldUpdatePaymentMethods) fetchPaymentMethods() null } + else -> DropInServiceResult.Error(reason = resultCode, dismissDropIn = false) } } else { @@ -320,27 +322,24 @@ class ExampleAdvancedDropInService : DropInService() { storedPaymentMethod: StoredPaymentMethod, ) { launch(Dispatchers.IO) { - val request = createRemoveStoredPaymentMethodRequest( - storedPaymentMethod.id.orEmpty(), - keyValueStorage.getMerchantAccount(), - keyValueStorage.getShopperReference() + val storedPaymentMethodId = storedPaymentMethod.id.orEmpty() + val isSuccessfullyRemoved = paymentsRepository.removeStoredPaymentMethod( + storedPaymentMethodId = storedPaymentMethodId, + merchantAccount = keyValueStorage.getMerchantAccount(), + shopperReference = keyValueStorage.getShopperReference(), ) - val response = recurringRepository.removeStoredPaymentMethod(request) - val result = handleRemoveStoredPaymentMethodResult(response, storedPaymentMethod.id.orEmpty()) + val result = handleRemoveStoredPaymentMethodResult(storedPaymentMethodId, isSuccessfullyRemoved) sendRecurringResult(result) } } private fun handleRemoveStoredPaymentMethodResult( - jsonResponse: JSONObject?, - id: String + storedPaymentMethodId: String, + isSuccessfullyRemoved: Boolean, ): RecurringDropInServiceResult { - return if (jsonResponse != null) { - Logger.v(TAG, "removeStoredPaymentMethod response - ${jsonResponse.toStringPretty()}") - when (val responseCode = jsonResponse.getStringOrNull("response")) { - "[detail-successfully-disabled]" -> RecurringDropInServiceResult.PaymentMethodRemoved(id) - else -> RecurringDropInServiceResult.Error(reason = responseCode, dismissDropIn = false) - } + return if (isSuccessfullyRemoved) { + Logger.v(TAG, "removeStoredPaymentMethod response successful") + RecurringDropInServiceResult.PaymentMethodRemoved(storedPaymentMethodId) } else { Logger.e(TAG, "FAILED") RecurringDropInServiceResult.Error(reason = "IOException") diff --git a/example-app/src/main/java/com/adyen/checkout/example/service/ExampleDropInService.kt b/example-app/src/main/java/com/adyen/checkout/example/service/ExampleDropInService.kt index e230a666e2..cc33b37882 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/service/ExampleDropInService.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/service/ExampleDropInService.kt @@ -14,7 +14,6 @@ import com.adyen.checkout.components.core.PaymentComponentData import com.adyen.checkout.components.core.PaymentComponentState import com.adyen.checkout.components.core.StoredPaymentMethod import com.adyen.checkout.components.core.action.Action -import com.adyen.checkout.core.internal.data.model.getStringOrNull import com.adyen.checkout.core.internal.data.model.toStringPretty import com.adyen.checkout.core.internal.util.LogUtil import com.adyen.checkout.core.internal.util.Logger @@ -23,7 +22,6 @@ import com.adyen.checkout.dropin.DropInServiceResult import com.adyen.checkout.dropin.RecurringDropInServiceResult import com.adyen.checkout.example.data.storage.KeyValueStorage import com.adyen.checkout.example.repositories.PaymentsRepository -import com.adyen.checkout.example.repositories.RecurringRepository import com.adyen.checkout.redirect.RedirectComponent import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers @@ -45,9 +43,6 @@ class ExampleDropInService : DropInService() { @Inject lateinit var paymentsRepository: PaymentsRepository - @Inject - lateinit var recurringRepository: RecurringRepository - @Inject lateinit var keyValueStorage: KeyValueStorage @@ -111,11 +106,13 @@ class ExampleDropInService : DropInService() { Logger.e(TAG, "FAILED") DropInServiceResult.Error(reason = "IOException") } + isAction(jsonResponse) -> { Logger.d(TAG, "Received action") val action = Action.SERIALIZER.deserialize(jsonResponse.getJSONObject("action")) DropInServiceResult.Action(action) } + else -> { Logger.d(TAG, "Final result - ${jsonResponse.toStringPretty()}") val resultCode = if (jsonResponse.has("resultCode")) { @@ -136,27 +133,24 @@ class ExampleDropInService : DropInService() { storedPaymentMethod: StoredPaymentMethod, ) { launch(Dispatchers.IO) { - val request = createRemoveStoredPaymentMethodRequest( - storedPaymentMethod.id.orEmpty(), - keyValueStorage.getMerchantAccount(), - keyValueStorage.getShopperReference() + val storedPaymentMethodId = storedPaymentMethod.id.orEmpty() + val isSuccessfullyRemoved = paymentsRepository.removeStoredPaymentMethod( + storedPaymentMethodId = storedPaymentMethodId, + merchantAccount = keyValueStorage.getMerchantAccount(), + shopperReference = keyValueStorage.getShopperReference(), ) - val response = recurringRepository.removeStoredPaymentMethod(request) - val result = handleRemoveStoredPaymentMethodResult(response, storedPaymentMethod.id.orEmpty()) + val result = handleRemoveStoredPaymentMethodResult(storedPaymentMethodId, isSuccessfullyRemoved) sendRecurringResult(result) } } private fun handleRemoveStoredPaymentMethodResult( - jsonResponse: JSONObject?, - id: String + storedPaymentMethodId: String, + isSuccessfullyRemoved: Boolean, ): RecurringDropInServiceResult { - return if (jsonResponse != null) { - Logger.v(TAG, "removeStoredPaymentMethod response - ${jsonResponse.toStringPretty()}") - when (val responseCode = jsonResponse.getStringOrNull("response")) { - "[detail-successfully-disabled]" -> RecurringDropInServiceResult.PaymentMethodRemoved(id) - else -> RecurringDropInServiceResult.Error(reason = responseCode, dismissDropIn = false) - } + return if (isSuccessfullyRemoved) { + Logger.v(TAG, "removeStoredPaymentMethod response successful") + RecurringDropInServiceResult.PaymentMethodRemoved(storedPaymentMethodId) } else { Logger.e(TAG, "FAILED") RecurringDropInServiceResult.Error(reason = "IOException") diff --git a/example-app/src/main/java/com/adyen/checkout/example/service/RequestUtils.kt b/example-app/src/main/java/com/adyen/checkout/example/service/RequestUtils.kt index c6981787f6..d6592b56b6 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/service/RequestUtils.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/service/RequestUtils.kt @@ -22,7 +22,6 @@ import com.adyen.checkout.example.data.api.model.PaymentMethodsRequest import com.adyen.checkout.example.data.api.model.PaymentsRequest import com.adyen.checkout.example.data.api.model.PaymentsRequestData import com.adyen.checkout.example.data.api.model.RecurringProcessingModel -import com.adyen.checkout.example.data.api.model.RemoveStoredPaymentMethodRequest import com.adyen.checkout.example.data.api.model.SessionRequest import com.adyen.checkout.example.data.api.model.StorePaymentMethodMode import com.adyen.checkout.example.data.api.model.ThreeDS2RequestDataRequest @@ -155,16 +154,6 @@ fun createCancelOrderRequest( merchantAccount = merchantAccount ) -fun createRemoveStoredPaymentMethodRequest( - recurringDetailReference: String, - merchantAccount: String, - shopperReference: String -) = RemoveStoredPaymentMethodRequest( - recurringDetailReference = recurringDetailReference, - merchantAccount = merchantAccount, - shopperReference = shopperReference -) - private const val SHOPPER_IP = "142.12.31.22" private const val CHANNEL = "android" private val LINE_ITEMS = listOf(Item()) From e6d442b3ed387286c0214095b274a2042d741273 Mon Sep 17 00:00:00 2001 From: jreij Date: Thu, 4 May 2023 15:28:27 +0200 Subject: [PATCH 006/161] Persist use sessions switch in the shared preferences COAND-756 --- .../example/data/storage/KeyValueStorage.kt | 18 ++++++++++++++++++ .../checkout/example/ui/main/MainActivity.kt | 5 +++++ .../checkout/example/ui/main/MainViewModel.kt | 5 +++++ .../src/main/res/layout/activity_main.xml | 3 ++- example-app/src/main/res/values/strings.xml | 1 + 5 files changed, 31 insertions(+), 1 deletion(-) diff --git a/example-app/src/main/java/com/adyen/checkout/example/data/storage/KeyValueStorage.kt b/example-app/src/main/java/com/adyen/checkout/example/data/storage/KeyValueStorage.kt index d9e4b044f0..a9de2f1998 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/data/storage/KeyValueStorage.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/data/storage/KeyValueStorage.kt @@ -10,6 +10,7 @@ package com.adyen.checkout.example.data.storage import android.content.Context import android.content.SharedPreferences +import androidx.core.content.edit import com.adyen.checkout.components.core.Amount import com.adyen.checkout.example.BuildConfig import com.adyen.checkout.example.R @@ -29,6 +30,8 @@ interface KeyValueStorage { fun isAddressFormEnabled(): Int fun getInstantPaymentMethodType(): String fun getInstallmentOptionsMode(): Int + fun useSessions(): Boolean + fun setUseSessions(useSessions: Boolean) } @Suppress("TooManyFunctions") @@ -105,6 +108,20 @@ internal class DefaultKeyValueStorage( ).toInt() } + override fun useSessions(): Boolean { + return sharedPreferences.get( + appContext, + R.string.use_sessions_key, + DEFAULT_USE_SESSIONS + ) + } + + override fun setUseSessions(useSessions: Boolean) { + sharedPreferences.edit { + putBoolean(appContext.getString(R.string.use_sessions_key), useSessions) + } + } + companion object { private const val DEFAULT_COUNTRY = "NL" private const val DEFAULT_LOCALE = "en-US" @@ -116,5 +133,6 @@ internal class DefaultKeyValueStorage( private const val DEFAULT_ENABLE_ADDRESS_FORM = "0" private const val DEFAULT_INSTALLMENT_OPTIONS_MODE = "0" private const val DEFAULT_INSTANT_PAYMENT_METHOD = "paypal" + private const val DEFAULT_USE_SESSIONS = true } } diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainActivity.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainActivity.kt index 76eccc6771..b5ccce8d4e 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainActivity.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainActivity.kt @@ -83,6 +83,7 @@ class MainActivity : AppCompatActivity() { launch { viewModel.listItems.collect(::onListItems) } launch { viewModel.eventFlow.collect(::onMainEvent) } launch { viewModel.isLoading.collect(::setLoading) } + launch { viewModel.useSessions.collect(::setUseSessionsSwitchChecked) } } } } @@ -180,6 +181,10 @@ class MainActivity : AppCompatActivity() { } } + private fun setUseSessionsSwitchChecked(isChecked: Boolean) { + binding.switchSessions.isChecked = isChecked + } + override fun onDestroy() { super.onDestroy() componentItemAdapter = null diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainViewModel.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainViewModel.kt index 5be1bbe5ce..a508f0bc6f 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainViewModel.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainViewModel.kt @@ -48,6 +48,9 @@ internal class MainViewModel @Inject constructor( private val _isLoading = MutableStateFlow(false) val isLoading: Flow = _isLoading + private val _useSessions = MutableStateFlow(keyValueStorage.useSessions()) + val useSessions: Flow = _useSessions + private val _eventFlow = MutableSharedFlow(extraBufferCapacity = 1) val eventFlow: Flow = _eventFlow @@ -160,6 +163,8 @@ internal class MainViewModel @Inject constructor( } fun onSessionsToggled(enable: Boolean) { + _useSessions.value = enable + keyValueStorage.setUseSessions(enable) val items = if (enable) { ComponentItemProvider.getSessionItems() } else { diff --git a/example-app/src/main/res/layout/activity_main.xml b/example-app/src/main/res/layout/activity_main.xml index 55e32a78f6..26cdcf52b1 100644 --- a/example-app/src/main/res/layout/activity_main.xml +++ b/example-app/src/main/res/layout/activity_main.xml @@ -51,7 +51,8 @@ android:id="@+id/component_list" android:layout_width="match_parent" android:layout_height="match_parent" - app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + tools:listitem="@layout/item_component_entry" /> diff --git a/example-app/src/main/res/values/strings.xml b/example-app/src/main/res/values/strings.xml index ff66a2c32f..9f7f745464 100644 --- a/example-app/src/main/res/values/strings.xml +++ b/example-app/src/main/res/values/strings.xml @@ -53,6 +53,7 @@ Enable card address form instant_payment_method_type Instant Payment Method Type + use_sessions Theme From 6ca11aae6bcbaa635676440128b1d121b18fa196 Mon Sep 17 00:00:00 2001 From: jreij Date: Thu, 4 May 2023 16:16:04 +0200 Subject: [PATCH 007/161] Merge main activity state in one class COAND-756 --- .../checkout/example/ui/main/MainActivity.kt | 10 ++- .../checkout/example/ui/main/MainViewModel.kt | 63 ++++++++++++++----- .../checkout/example/ui/main/MainViewState.kt | 19 ++++++ 3 files changed, 73 insertions(+), 19 deletions(-) create mode 100644 example-app/src/main/java/com/adyen/checkout/example/ui/main/MainViewState.kt diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainActivity.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainActivity.kt index b5ccce8d4e..5bcfaac96c 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainActivity.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainActivity.kt @@ -80,10 +80,8 @@ class MainActivity : AppCompatActivity() { lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { - launch { viewModel.listItems.collect(::onListItems) } + launch { viewModel.mainViewState.collect(::onMainViewState) } launch { viewModel.eventFlow.collect(::onMainEvent) } - launch { viewModel.isLoading.collect(::setLoading) } - launch { viewModel.useSessions.collect(::setUseSessionsSwitchChecked) } } } } @@ -103,6 +101,12 @@ class MainActivity : AppCompatActivity() { return super.onOptionsItemSelected(item) } + private fun onMainViewState(mainViewState: MainViewState) { + onListItems(mainViewState.listItems) + setLoading(mainViewState.showLoading) + setUseSessionsSwitchChecked(mainViewState.useSessions) + } + private fun onListItems(items: List) { componentItemAdapter?.submitList(items) } diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainViewModel.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainViewModel.kt index a508f0bc6f..6685a3aa0a 100644 --- a/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainViewModel.kt +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainViewModel.kt @@ -30,6 +30,8 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import javax.inject.Inject @@ -42,18 +44,25 @@ internal class MainViewModel @Inject constructor( private val checkoutConfigurationProvider: CheckoutConfigurationProvider, ) : ViewModel() { - private val _listItems = MutableStateFlow(ComponentItemProvider.getDefaultItems()) - val listItems: Flow> = _listItems + private val _useSessions: MutableStateFlow = MutableStateFlow(keyValueStorage.useSessions()) + private val _showLoading: MutableStateFlow = MutableStateFlow(false) - private val _isLoading = MutableStateFlow(false) - val isLoading: Flow = _isLoading + private val _mainViewState: MutableStateFlow = MutableStateFlow(getViewState()) + val mainViewState: Flow = _mainViewState - private val _useSessions = MutableStateFlow(keyValueStorage.useSessions()) - val useSessions: Flow = _useSessions - - private val _eventFlow = MutableSharedFlow(extraBufferCapacity = 1) + private val _eventFlow: MutableSharedFlow = MutableSharedFlow(extraBufferCapacity = 1) val eventFlow: Flow = _eventFlow + init { + _useSessions.onEach { + loadViewState() + }.launchIn(viewModelScope) + + _showLoading.onEach { + loadViewState() + }.launchIn(viewModelScope) + } + fun onComponentEntryClick(entry: ComponentItem.Entry) { when (entry) { ComponentItem.Entry.Bacs -> _eventFlow.tryEmit(MainEvent.NavigateTo(MainNavigation.Bacs)) @@ -75,11 +84,11 @@ internal class MainViewModel @Inject constructor( private fun startDropInFlow() { viewModelScope.launch { - _isLoading.emit(true) + showLoading(true) val paymentMethods = getPaymentMethods() - _isLoading.emit(false) + showLoading(false) if (paymentMethods != null) { val dropInConfiguration = checkoutConfigurationProvider.getDropInConfiguration() @@ -92,13 +101,13 @@ internal class MainViewModel @Inject constructor( private fun startSessionDropInFlow(takeOverSession: Boolean) { viewModelScope.launch { - _isLoading.emit(true) + showLoading(true) val dropInConfiguration = checkoutConfigurationProvider.getDropInConfiguration() val session = getSession(dropInConfiguration) - _isLoading.emit(false) + showLoading(false) if (session != null) { val navigation = if (takeOverSession) { @@ -163,14 +172,36 @@ internal class MainViewModel @Inject constructor( } fun onSessionsToggled(enable: Boolean) { - _useSessions.value = enable - keyValueStorage.setUseSessions(enable) - val items = if (enable) { + viewModelScope.launch { + keyValueStorage.setUseSessions(enable) + _useSessions.emit(enable) + } + } + + private suspend fun showLoading(loading: Boolean) { + _showLoading.emit(loading) + } + + private suspend fun loadViewState() { + _mainViewState.emit(getViewState()) + } + + private fun getViewState(): MainViewState { + val useSessions = _useSessions.value + val showLoading = _showLoading.value + return MainViewState( + listItems = getListItems(useSessions), + useSessions = useSessions, + showLoading = showLoading, + ) + } + + private fun getListItems(useSessions: Boolean): List { + return if (useSessions) { ComponentItemProvider.getSessionItems() } else { ComponentItemProvider.getDefaultItems() } - _listItems.tryEmit(items) } fun onDropInResult(dropInResult: DropInResult?) { diff --git a/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainViewState.kt b/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainViewState.kt new file mode 100644 index 0000000000..9145d1c8cf --- /dev/null +++ b/example-app/src/main/java/com/adyen/checkout/example/ui/main/MainViewState.kt @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2023 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by josephj on 4/5/2023. + */ + +package com.adyen.checkout.example.ui.main + +/* +This is a data class instead of a sealed class because the list will always be displayed. The loading indicator only +shows up on top of the list but does not hide it from the view. + */ +internal data class MainViewState( + val listItems: List, + val useSessions: Boolean, + val showLoading: Boolean, +) From 62cf53cf95cf689334e89c738418a9373e2e75af Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 4 May 2023 23:33:57 +0000 Subject: [PATCH 008/161] Update dependency org.robolectric:robolectric to v4.10.2 --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 3037464b36..473f2b5f19 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -63,7 +63,7 @@ ext { junit_jupiter_version = "5.9.1" mockito_kotlin_version = "4.1.0" mockito_version = "4.9.0" - robolectric_version = "4.10" + robolectric_version = "4.10.2" test_ext_version = "1.1.4" test_rules_version = "1.5.0" turbine_version = "0.12.1" From 61f8c314f9c95e96d93392c54484b31e983e57b5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 4 May 2023 23:34:03 +0000 Subject: [PATCH 009/161] Update dependency com.google.android.material:material to v1.9.0 --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 473f2b5f19..58ba0d310b 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -36,7 +36,7 @@ ext { coroutines_version = "1.6.4" fragment_version = "1.5.7" lifecycle_version = "2.5.1" - material_version = "1.8.0" + material_version = "1.9.0" recyclerview_version = "1.3.0" constraintlayout_version = '2.1.4' From e8ec8d9a558e7ab68a086337949d83e5c7e6b6fd Mon Sep 17 00:00:00 2001 From: jreij Date: Fri, 5 May 2023 10:59:23 +0200 Subject: [PATCH 010/161] Update checksum for material --- config/gradle/checksums.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/gradle/checksums.gradle b/config/gradle/checksums.gradle index e4580c0164..bad5c5ef50 100644 --- a/config/gradle/checksums.gradle +++ b/config/gradle/checksums.gradle @@ -35,7 +35,7 @@ if (!hasProperty("checksums")) { "androidx.browser:browser:1.5.0:6f312f71b7ae84c28c6b9b322ddadbcc:MD5", // Google - "com.google.android.material:material:1.8.0:be66d71684f2b388946239a6771f5b0f:MD5", + "com.google.android.material:material:1.9.0:3287103cfb083fb998a35ef8a1983c58:MD5", "com.google.android.gms:play-services-wallet:19.1.0:3dc5c1194f1bc98ccb98b88633b50b52:MD5", // WeChatPay From 14e140ef3ed675acf83b7e975e04d4dba4dd7c4d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 13 Apr 2023 20:04:59 +0000 Subject: [PATCH 011/161] Update dependency com.android.tools.build:gradle to v8 --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 58ba0d310b..ba0935d6a5 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -19,7 +19,7 @@ ext { version_name = "5.0.0-alpha01" // Build Script - android_gradle_plugin_version = '7.4.2' + android_gradle_plugin_version = '8.0.0' kotlin_version = '1.8.21' detekt_gradle_plugin_version = "1.22.0" dokka_version = "1.8.10" From 52fae47157ad86476bcc41dd0b53d3a19d0808aa Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Fri, 14 Apr 2023 16:34:02 +0200 Subject: [PATCH 012/161] Update AGP checksums --- config/gradle/checksums.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/gradle/checksums.gradle b/config/gradle/checksums.gradle index bad5c5ef50..615afc33cf 100644 --- a/config/gradle/checksums.gradle +++ b/config/gradle/checksums.gradle @@ -22,7 +22,7 @@ if (!hasProperty("checksums")) { "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4:7aad79016f327ae9cfd0b560b0b47558:MD5", // Android Gradle Plugin - "androidx.databinding:viewbinding:7.4.2:c3d1fbf450fc578ca2d69d6f80645bf4:MD5", + "androidx.databinding:viewbinding:8.0.0:8c4389ee71e6da481b621f1eadca9b57:MD5", // AndroidX "androidx.annotation:annotation:1.5.0:5f8c96f5310d39845efa73dd82b717e4:MD5", From ebbeced911c6a26ad40af866d6df00345f14cdc2 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Fri, 14 Apr 2023 16:34:39 +0200 Subject: [PATCH 013/161] Enable buildconfig and transitive R class gradle properties --- gradle.properties | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gradle.properties b/gradle.properties index 9f8a6ef1ce..62eaa483d3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,9 +7,11 @@ android.enableJetifier=false android.useAndroidX=true +android.nonTransitiveRClass=false # Disable some unused build features in the Android Gradle Plugin to improve build speed. android.defaults.buildfeatures.aidl=false +android.defaults.buildfeatures.buildconfig=true android.defaults.buildfeatures.renderscript=false android.defaults.buildfeatures.resvalues=false android.defaults.buildfeatures.shaders=false From a414293d135bbd1f51175b901ce29abbbd44347b Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Fri, 14 Apr 2023 16:40:34 +0200 Subject: [PATCH 014/161] Use JDK 17 on CI --- .github/workflows/check_pr.yml | 6 +++--- .github/workflows/check_release.yml | 6 +++--- .github/workflows/publish_release.yml | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/check_pr.yml b/.github/workflows/check_pr.yml index 68bf4dd447..5ea50a663f 100644 --- a/.github/workflows/check_pr.yml +++ b/.github/workflows/check_pr.yml @@ -17,13 +17,13 @@ jobs: # https://github.com/marketplace/actions/checkout - uses: actions/checkout@v3 - # Setup Java 11 + # Setup Java 17 # https://github.com/marketplace/actions/setup-java-jdk - - name: Set up JDK 11 + - name: Set up JDK uses: actions/setup-java@v3 with: distribution: 'zulu' - java-version: 11 + java-version: 17 cache: 'gradle' - name: Grant execute permission for gradlew diff --git a/.github/workflows/check_release.yml b/.github/workflows/check_release.yml index 205a12cb26..305fd606b6 100644 --- a/.github/workflows/check_release.yml +++ b/.github/workflows/check_release.yml @@ -17,13 +17,13 @@ jobs: # https://github.com/marketplace/actions/checkout - uses: actions/checkout@v3 - # Setup Java 11 + # Setup Java 17 # https://github.com/marketplace/actions/setup-java-jdk - - name: Set up JDK 11 + - name: Set up JDK uses: actions/setup-java@v3 with: distribution: 'zulu' - java-version: 11 + java-version: 17 cache: 'gradle' - name: Grant execute permission for gradlew diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 52f445438c..6802194b69 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -16,11 +16,11 @@ jobs: with: ref: main - - name: Set up JDK 11 + - name: Set up JDK uses: actions/setup-java@v3 with: distribution: 'zulu' - java-version: 11 + java-version: 17 cache: 'gradle' - name: Grant execute permission for gradlew From eeb97be5f686bbfc3749893f7ec2a3f562496b39 Mon Sep 17 00:00:00 2001 From: jreij Date: Fri, 5 May 2023 11:47:22 +0200 Subject: [PATCH 015/161] Update release notes to reflect breaking changes caused by the 8.0.0 gradle plugin update --- RELEASE_NOTES.md | 71 ++---------------------------------------------- 1 file changed, 3 insertions(+), 68 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 2d9a0e7e6e..8646d46b83 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -9,72 +9,7 @@ [//]: # ( - Configurations public constructor are deprecated, please use each Configuration's builder to make a Configuration object) ## Breaking Changes - -- For Drop in, you can no longer get the result using `onActivityResult()`. Drop-in now uses the [Activity Result API](https://developer.android.com/training/basics/intents/result) instead. -- For Components, you can no longer use `requiresView()` for action Component providers. -- Restructured packages and moved classes. If you're upgrading, you only need to re-import the them because most classes names haven't changed. -- All public classes that should not be directly used are now marked as internal. -- You now must configure `environment`. The default value is no longer **TEST**. -- Build configuration: [`compileSdkVersion` and `targetSdkVersion`](https://developer.android.com/about/versions/11/setup-sdk#update-build): **33**. - Dependency versions: - | Name | Version | - |--------------------------------------------------------------------------------------------------------|------------| - | [Android Gradle plugin](https://developer.android.com/build/releases/gradle-plugin) | **7.4.2** | - | [Kotlin Gradle plugin](https://plugins.gradle.org/plugin/org.jetbrains.kotlin.android) | **1.8.21** | - | [Appcompat](https://developer.android.com/jetpack/androidx/releases/appcompat) | **1.6.1** | - | [Kotlin coroutines](https://kotlinlang.org/docs/coroutines-overview.html) | **1.6.4** | - | [AndroidX Fragment](https://developer.android.com/jetpack/androidx/releases/fragment) | **1.5.7** | - | [AndroidX Lifecycle](https://developer.android.com/jetpack/androidx/releases/lifecycle) | **2.5.1** | - | [AndroidX Recyclerview](https://developer.android.com/jetpack/androidx/releases/recyclerview) | **1.3.0** | - | [AndroidX Constraintlayout](https://developer.android.com/jetpack/androidx/releases/constraintlayout) | **2.1.4** | - | [Material Design](https://m2.material.io/) | **1.8.0** | - -## Removed -- `requiresConfiguration()` in action Component providers. For all Components, configuration is optional. -- `CardConfiguration.Builder.setAddressVisibility()`. Use `CardConfiguration.Builder.setAddressConfiguration()` instead. -- `Environment.LIVE`. Use the same live environment as your backend instead. You can find that value in your Customer Area. -- `saveState()` and `restoreState()` in action components. The component will automatically handle the state now. -- `DropInServiceResult.Action` constructor from JSON string. Use the constructor with the `Action` and `Action.SERIALIZER` instead. - -## New -- Sessions flow using the single `/sessions` request is now supported. -- For Components: - - Payment method Components now handle actions. You no longer need a payment Component and action Components for a payment method with additional actions. - - The `GenericActionComponent` that can handle all action types. You no longer need to implement separate Components for redirects and 3D Secure 2 authentication, for example. - - A **Pay** button that you can configure to be hidden. - - The `submit()` method that can be used to add your own pay/submit button. - - You can now add `amount` to the configuration to show it on the pay/submit button. - - The `onSubmit()` event that gets emitted when the shopper pays. -- When the shopper is redirected back from an external app or website, an intermediate view with a loading spinner and a **Cancel** button now shows. The shopper can select to cancel the redirect back to your app. -- Localisation for the Portuguese (Portugal) language. -- Payment methods: - - [ACH Direct Debit](https://docs.adyen.com/payment-methods/ach-direct-debit). [Payment method type](https://docs.adyen.com/payment-methods/payment-method-types): **ach**. - - [DuitNow](https://docs.adyen.com/payment-methods/duitnow). Payment method type: **duitnow**. - - [Open banking](https://docs.adyen.com/payment-methods/open-banking). Payment method type: **paybybank**. - - [Online banking Czech Republic](https://docs.adyen.com/payment-methods/online-banking-czech-republic). Payment method type: **onlineBanking_CZ**. - - [Online banking Slovakia](https://docs.adyen.com/payment-methods/online-banking-slovakia). Payment method type: **onlineBanking_SK**. - - [Pay Now](https://docs.adyen.com/payment-methods/paynow). Payment method type: **paynow**. - - [PromptPay](https://docs.adyen.com/payment-methods/promptpay). Payment method type: **promptpay**. - - [UPI](https://docs.adyen.com/payment-methods/upi): - - UPI Collect: The shopper pays by entering their virtual payment address (VPA). Payment method type: **upi_collect**. - - UPI QR: The shopper pays by scanning a QR code. Payment method type: **upi_qr**. -- Express payment methods like PayPal and Klarna. These payment methods don't require the shopper to enter their payment details before they pay. Use `InstantPaymentComponent`. - -## Changed -- For cards: - - The supported brand logo icons now show below the card number input field. - - US Debit brand logo icons no longer show. -- For Drop-in, values set in `DropInConfiguration` now override conflicting configurations for individual payment methods. -- For Google Pay, you can now set `GooglePayConfiguration.merchantAccount` to override the `gatewayMerchantId` configured in your Customer Area. For Advanced flow, this is the `paymentMethod.configuration.gatewayMerchantId` parameter in the `/paymentMethods` response. -- For Components, when a payment method doesn't require input from the shopper, the Component that launches automatically returns the `onSubmit()` callback. For example, for the stored cards without a CVC input field. -- For gift cards and partial payments, you must now implement `onBalanceCheck()` and `onRequestOrder()` to launch payment methods with an order and make [partial payments](https://docs.adyen.com/online-payments/partial-payments). - -## Improved -- You can now instantiate more than one instance of the same Component within the same lifecycle. Passing the `key` parameter to the Component provider `get()` method. For example, you can show cards and stored cards on the same screen. -- For Components, you no longer need to handle duplicate events such as submit callbacks or errors with because they're only emitted once. [Flows](https://developer.android.com/kotlin/flow) are now used instead of [LiveData](https://developer.android.com/topic/libraries/architecture/livedata). -- More UI theme customization options like dark mode. -- The expiry date input field now has more specific validation rules and error messages. -- The email address input field now has more specific validation rules. - -## Fixed -- The redirect flow on Android 11. + | Name | Version | + |--------------------------------------------------------------------------------------------------------|-------------------------------| + | [Android Gradle plugin](https://developer.android.com/build/releases/gradle-plugin) | **8.0.0** (requires Java 17) | From 53443d9e8902e18352fe0a6ed6750534d832297d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 May 2023 11:06:12 +0000 Subject: [PATCH 016/161] Update dependency com.android.tools.build:gradle to v8.0.1 --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index ba0935d6a5..b21c588bc9 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -19,7 +19,7 @@ ext { version_name = "5.0.0-alpha01" // Build Script - android_gradle_plugin_version = '8.0.0' + android_gradle_plugin_version = '8.0.1' kotlin_version = '1.8.21' detekt_gradle_plugin_version = "1.22.0" dokka_version = "1.8.10" From 9b26c7d49104e0159cfb3cbca0bf88aaeaff4bd1 Mon Sep 17 00:00:00 2001 From: jreij Date: Fri, 5 May 2023 13:10:56 +0200 Subject: [PATCH 017/161] Update checksums for android gradle plugin --- config/gradle/checksums.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/gradle/checksums.gradle b/config/gradle/checksums.gradle index 615afc33cf..b631fd65d7 100644 --- a/config/gradle/checksums.gradle +++ b/config/gradle/checksums.gradle @@ -22,7 +22,7 @@ if (!hasProperty("checksums")) { "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4:7aad79016f327ae9cfd0b560b0b47558:MD5", // Android Gradle Plugin - "androidx.databinding:viewbinding:8.0.0:8c4389ee71e6da481b621f1eadca9b57:MD5", + "androidx.databinding:viewbinding:8.0.1:a2dec5262b471f43750c2f0f24c4f64c:MD5", // AndroidX "androidx.annotation:annotation:1.5.0:5f8c96f5310d39845efa73dd82b717e4:MD5", From 201960242d63c411d7d4a5fdcda31559abfb4230 Mon Sep 17 00:00:00 2001 From: jreij Date: Fri, 5 May 2023 13:11:20 +0200 Subject: [PATCH 018/161] Update release notes with android gradle plugin version 8.0.1 --- RELEASE_NOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 8646d46b83..71ef15b0ec 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -12,4 +12,4 @@ - Dependency versions: | Name | Version | |--------------------------------------------------------------------------------------------------------|-------------------------------| - | [Android Gradle plugin](https://developer.android.com/build/releases/gradle-plugin) | **8.0.0** (requires Java 17) | + | [Android Gradle plugin](https://developer.android.com/build/releases/gradle-plugin) | **8.0.1** (requires Java 17) | From 0b47a32f71bc881eb76f10a59c2c159ab4291798 Mon Sep 17 00:00:00 2001 From: Atef Etman Date: Fri, 31 Mar 2023 14:52:52 +0200 Subject: [PATCH 019/161] Add Boleto component module COAND-366 --- boleto/.gitignore | 1 + boleto/build.gradle | 53 +++++++++++++++++++++++++++++ boleto/consumer-rules.pro | 0 boleto/proguard-rules.pro | 21 ++++++++++++ boleto/src/main/AndroidManifest.xml | 9 +++++ settings.gradle | 1 + 6 files changed, 85 insertions(+) create mode 100644 boleto/.gitignore create mode 100644 boleto/build.gradle create mode 100644 boleto/consumer-rules.pro create mode 100644 boleto/proguard-rules.pro create mode 100644 boleto/src/main/AndroidManifest.xml diff --git a/boleto/.gitignore b/boleto/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/boleto/.gitignore @@ -0,0 +1 @@ +/build diff --git a/boleto/build.gradle b/boleto/build.gradle new file mode 100644 index 0000000000..201811abba --- /dev/null +++ b/boleto/build.gradle @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by atef on 3/3/2023. + */ + +plugins { + id 'com.android.library' + id 'kotlin-android' + id 'kotlin-parcelize' +} + +ext.mavenArtifactId = "boleto" +ext.mavenArtifactName = "Adyen checkout Boleto component" +ext.mavenArtifactDescription = "Adyen checkout Boleto component client for Adyen's Checkout API." + +apply from: "${rootDir}/config/gradle/sharedTasks.gradle" + +android { + compileSdkVersion compile_sdk_version + + defaultConfig { + minSdkVersion min_sdk_version + targetSdkVersion target_sdk_version + versionCode version_code + versionName version_name + + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + consumerProguardFiles "consumer-rules.pro" + } + + buildFeatures { + viewBinding true + } +} + +dependencies { + // Checkout + api project(':action') + api project(':ui-core') + api project(':sessions-core') + + // Dependencies + implementation libraries.material + + //Tests + testImplementation project(':test-core') + testImplementation testLibraries.junit5 + testImplementation testLibraries.kotlinCoroutines + testImplementation testLibraries.mockito +} diff --git a/boleto/consumer-rules.pro b/boleto/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/boleto/proguard-rules.pro b/boleto/proguard-rules.pro new file mode 100644 index 0000000000..f1b424510d --- /dev/null +++ b/boleto/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/boleto/src/main/AndroidManifest.xml b/boleto/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..902aff78ec --- /dev/null +++ b/boleto/src/main/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + diff --git a/settings.gradle b/settings.gradle index 512ffb1145..37fab08ce2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,6 +5,7 @@ include ':3ds2', ':bacs', ':bcmc', ':blik', + ':boleto', ':card', ':checkout-core', ':components-core', From c7a8a9ade148c0ae96bd12feebc6e8776b5d115a Mon Sep 17 00:00:00 2001 From: Atef Etman Date: Fri, 31 Mar 2023 14:53:14 +0200 Subject: [PATCH 020/161] Add Boleto configuration COAND-366 --- .../checkout/boleto/BoletoConfiguration.kt | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 boleto/src/main/java/com/adyen/checkout/boleto/BoletoConfiguration.kt diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/BoletoConfiguration.kt b/boleto/src/main/java/com/adyen/checkout/boleto/BoletoConfiguration.kt new file mode 100644 index 0000000000..7a50e1b38d --- /dev/null +++ b/boleto/src/main/java/com/adyen/checkout/boleto/BoletoConfiguration.kt @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2023 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by atef on 31/3/2023. + */ + +package com.adyen.checkout.boleto + +import android.content.Context +import com.adyen.checkout.action.GenericActionConfiguration +import com.adyen.checkout.action.internal.ActionHandlingPaymentMethodConfigurationBuilder +import com.adyen.checkout.components.core.Amount +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.core.Environment +import kotlinx.parcelize.Parcelize +import java.util.Locale + +/** + * Configuration class for the [BoletoComponent]. + */ +@Parcelize +@Suppress("LongParameterList") +class BoletoConfiguration private constructor( + override val shopperLocale: Locale, + override val environment: Environment, + override val clientKey: String, + override val isAnalyticsEnabled: Boolean?, + override val amount: Amount, + override val isSubmitButtonVisible: Boolean?, + val genericActionConfiguration: GenericActionConfiguration, + val isSendEmailVisible: Boolean? +) : Configuration, ButtonConfiguration { + + /** + * Builder to create a [BoletoConfiguration]. + */ + class Builder : + ActionHandlingPaymentMethodConfigurationBuilder, + ButtonConfigurationBuilder { + private var isSubmitButtonVisible: Boolean? = null + private var isSendEmailVisible: Boolean? = null + + /** + * Constructor for Builder with default values. + * + * @param context A context + * @param environment The [Environment] to be used for network calls to Adyen. + * @param clientKey Your Client Key used for network calls from the SDK to Adyen. + */ + constructor(context: Context, environment: Environment, clientKey: String) : super( + context, + environment, + clientKey + ) + + /** + * Builder with required parameters. + * + * @param shopperLocale The Locale of the shopper. + * @param environment The [Environment] to be used for network calls to Adyen. + * @param clientKey Your Client Key used for network calls from the SDK to Adyen. + */ + constructor(shopperLocale: Locale, environment: Environment, clientKey: String) : super( + shopperLocale, + environment, + clientKey + ) + + /** + * Sets if submit button will be visible or not. + * + * Default is True. + * + * @param isSubmitButtonVisible Is submit button should be visible or not. + */ + override fun setSubmitButtonVisible(isSubmitButtonVisible: Boolean): Builder { + this.isSubmitButtonVisible = isSubmitButtonVisible + return this + } + + /** + * Sets the visibility of send email copy filed + * + * Default value is false + * @param isSendEmailVisible + */ + fun setIsSendEmailFieldVisibility(isSendEmailVisible: Boolean): Builder { + this.isSendEmailVisible = isSendEmailVisible + return this + } + + override fun buildInternal() = BoletoConfiguration( + shopperLocale = shopperLocale, + environment = environment, + clientKey = clientKey, + isAnalyticsEnabled = isAnalyticsEnabled, + amount = amount, + isSubmitButtonVisible = isSubmitButtonVisible, + genericActionConfiguration = genericActionConfigurationBuilder.build(), + isSendEmailVisible = isSendEmailVisible + ) + } +} From 74d08cc348d63c56789ff608ac4e0d4aaa99d48d Mon Sep 17 00:00:00 2001 From: Atef Etman Date: Fri, 31 Mar 2023 14:53:58 +0200 Subject: [PATCH 021/161] Add Boleto component state COAND-366 --- .../checkout/boleto/BoletoComponentState.kt | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 boleto/src/main/java/com/adyen/checkout/boleto/BoletoComponentState.kt diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/BoletoComponentState.kt b/boleto/src/main/java/com/adyen/checkout/boleto/BoletoComponentState.kt new file mode 100644 index 0000000000..c248ee4143 --- /dev/null +++ b/boleto/src/main/java/com/adyen/checkout/boleto/BoletoComponentState.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by atef on 31/3/2023. + */ + +package com.adyen.checkout.boleto + +import com.adyen.checkout.components.core.PaymentComponentData +import com.adyen.checkout.components.core.PaymentComponentState +import com.adyen.checkout.components.core.paymentmethod.GenericPaymentMethod + +/** + * Represents the state of [BoletoComponent]. + */ +data class BoletoComponentState( + override val data: PaymentComponentData, + override val isInputValid: Boolean, + override val isReady: Boolean, +) : PaymentComponentState From 611bad4524bb5d6985d6dfd1e95409fc8cc95a03 Mon Sep 17 00:00:00 2001 From: Atef Etman Date: Fri, 31 Mar 2023 14:54:46 +0200 Subject: [PATCH 022/161] Add Boleto ui models COAND-366 --- .../ui/model/AddressFieldPolicyParams.kt | 22 ++++++ .../ui/model/BoletoComponentParams.kt | 30 +++++++ .../ui/model/BoletoComponentParamsMapper.kt | 78 +++++++++++++++++++ .../internal/ui/model/BoletoInputData.kt | 21 +++++ .../internal/ui/model/BoletoOutputData.kt | 32 ++++++++ 5 files changed, 183 insertions(+) create mode 100644 boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/AddressFieldPolicyParams.kt create mode 100644 boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParams.kt create mode 100644 boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParamsMapper.kt create mode 100644 boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoInputData.kt create mode 100644 boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoOutputData.kt diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/AddressFieldPolicyParams.kt b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/AddressFieldPolicyParams.kt new file mode 100644 index 0000000000..964c6e6af1 --- /dev/null +++ b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/AddressFieldPolicyParams.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by atef on 31/3/2023. + */ + +package com.adyen.checkout.boleto.internal.ui.model + +import com.adyen.checkout.ui.core.internal.ui.model.AddressFieldPolicy +import kotlinx.parcelize.Parcelize + +@Parcelize +internal sealed class AddressFieldPolicyParams : AddressFieldPolicy { + + /** + * Address form fields will be required. + */ + @Parcelize + object Required : AddressFieldPolicyParams() +} diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParams.kt b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParams.kt new file mode 100644 index 0000000000..3eb2668923 --- /dev/null +++ b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParams.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by atef on 31/3/2023. + */ + +package com.adyen.checkout.boleto.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.ComponentParams +import com.adyen.checkout.core.Environment +import com.adyen.checkout.ui.core.internal.ui.model.AddressParams +import kotlinx.parcelize.Parcelize +import java.util.Locale + +@Parcelize +internal data class BoletoComponentParams( + override val isSubmitButtonVisible: Boolean, + override val shopperLocale: Locale, + override val environment: Environment, + override val clientKey: String, + override val isAnalyticsEnabled: Boolean, + override val isCreatedByDropIn: Boolean, + override val amount: Amount, + val addressParams: AddressParams, + val isSendEmailVisible: Boolean +) : ComponentParams, ButtonParams diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParamsMapper.kt b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParamsMapper.kt new file mode 100644 index 0000000000..891392032b --- /dev/null +++ b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParamsMapper.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by atef on 31/3/2023. + */ + +package com.adyen.checkout.boleto.internal.ui.model + +import com.adyen.checkout.boleto.BoletoConfiguration +import com.adyen.checkout.components.core.internal.ui.model.ComponentParams +import com.adyen.checkout.components.core.internal.ui.model.SessionParams +import com.adyen.checkout.ui.core.internal.ui.model.AddressParams + +internal class BoletoComponentParamsMapper( + private val overrideComponentParams: ComponentParams?, + private val overrideSessionParams: SessionParams?, +) { + + fun mapToParams( + configuration: BoletoConfiguration, + sessionParams: SessionParams? + ): BoletoComponentParams { + return configuration + .mapToParamsInternal() + .override(overrideComponentParams) + .override(sessionParams ?: overrideSessionParams) + } + + private fun BoletoConfiguration.mapToParamsInternal(): BoletoComponentParams { + return BoletoComponentParams( + isSubmitButtonVisible = isSubmitButtonVisible ?: true, + shopperLocale = shopperLocale, + environment = environment, + clientKey = clientKey, + isAnalyticsEnabled = isAnalyticsEnabled ?: true, + isCreatedByDropIn = false, + amount = amount, + addressParams = AddressParams.FullAddress( + defaultCountryCode = BRAZIL_COUNTRY_CODE, + supportedCountryCodes = DEFAULT_SUPPORTED_COUNTRY_LIST, + addressFieldPolicy = AddressFieldPolicyParams.Required + ), + isSendEmailVisible = isSendEmailVisible ?: false + ) + } + + private fun BoletoComponentParams.override( + overrideComponentParams: ComponentParams? + ): BoletoComponentParams { + if (overrideComponentParams == null) return this + return copy( + shopperLocale = overrideComponentParams.shopperLocale, + environment = overrideComponentParams.environment, + clientKey = overrideComponentParams.clientKey, + isAnalyticsEnabled = overrideComponentParams.isAnalyticsEnabled, + isCreatedByDropIn = overrideComponentParams.isCreatedByDropIn, + amount = overrideComponentParams.amount, + ) + } + + private fun BoletoComponentParams.override( + sessionParams: SessionParams? = null + ): BoletoComponentParams { + if (sessionParams == null) return this + return copy( + amount = sessionParams.amount ?: amount, + ) + } + + companion object { + private const val BRAZIL_COUNTRY_CODE = "BR" + + // this payment method only works for Brazil so we don't need other countries inside country drop down + private val DEFAULT_SUPPORTED_COUNTRY_LIST = listOf(BRAZIL_COUNTRY_CODE) + } +} diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoInputData.kt b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoInputData.kt new file mode 100644 index 0000000000..30c5c82260 --- /dev/null +++ b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoInputData.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by atef on 31/3/2023. + */ + +package com.adyen.checkout.boleto.internal.ui.model + +import com.adyen.checkout.components.core.internal.ui.model.InputData +import com.adyen.checkout.ui.core.internal.ui.model.AddressInputModel + +internal data class BoletoInputData( + var firstName: String = "", + var lastName: String = "", + var socialSecurityNumber: String = "", + var address: AddressInputModel = AddressInputModel(), + var isSendEmailSelected: Boolean = false, + var shopperEmail: String = "" +) : InputData diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoOutputData.kt b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoOutputData.kt new file mode 100644 index 0000000000..46b9fe992c --- /dev/null +++ b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoOutputData.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by atef on 31/3/2023. + */ + +package com.adyen.checkout.boleto.internal.ui.model + +import com.adyen.checkout.components.core.internal.ui.model.FieldState +import com.adyen.checkout.components.core.internal.ui.model.OutputData +import com.adyen.checkout.ui.core.internal.ui.AddressFormUIState +import com.adyen.checkout.ui.core.internal.ui.model.AddressOutputData + +internal data class BoletoOutputData( + val firstNameState: FieldState, + val lastNameState: FieldState, + val socialSecurityNumberState: FieldState, + val addressState: AddressOutputData, + val addressUIState: AddressFormUIState, + val isSendEmailSelected: Boolean, + val shopperEmailState: FieldState +) : OutputData { + + override val isValid: Boolean + get() = firstNameState.validation.isValid() && + lastNameState.validation.isValid() && + socialSecurityNumberState.validation.isValid() && + addressState.isValid && + shopperEmailState.validation.isValid() +} From 5de5a54e4a8f1ca0c237e8d35facc1168ae9d1fe Mon Sep 17 00:00:00 2001 From: Atef Etman Date: Fri, 31 Mar 2023 15:14:52 +0200 Subject: [PATCH 023/161] Move social security number input to ui-core module COAND-366 --- .../checkout/card/internal/ui/DefaultCardDelegate.kt | 2 +- card/src/main/res/layout/card_view.xml | 2 +- .../card/internal/util/SocialSecurityNumberUtilsTest.kt | 1 + .../core}/internal/ui/view/SocialSecurityNumberInput.kt | 5 ++--- .../ui/core}/internal/util/SocialSecurityNumberUtils.kt | 8 +++++--- ui-core/src/main/res/template/values/strings.xml.tt | 1 + ui-core/src/main/res/values-ar/strings.xml | 2 +- ui-core/src/main/res/values-cs-rCZ/strings.xml | 2 +- ui-core/src/main/res/values-da-rDK/strings.xml | 2 +- ui-core/src/main/res/values-de-rDE/strings.xml | 2 +- ui-core/src/main/res/values-el-rGR/strings.xml | 2 +- ui-core/src/main/res/values-es-rES/strings.xml | 2 +- ui-core/src/main/res/values-fi-rFI/strings.xml | 2 +- ui-core/src/main/res/values-fr-rFR/strings.xml | 2 +- ui-core/src/main/res/values-hr-rHR/strings.xml | 2 +- ui-core/src/main/res/values-hu-rHU/strings.xml | 2 +- ui-core/src/main/res/values-it-rIT/strings.xml | 2 +- ui-core/src/main/res/values-ja-rJP/strings.xml | 2 +- ui-core/src/main/res/values-ko-rKR/strings.xml | 2 +- ui-core/src/main/res/values-nb-rNO/strings.xml | 2 +- ui-core/src/main/res/values-nl-rNL/strings.xml | 2 +- ui-core/src/main/res/values-pl-rPL/strings.xml | 2 +- ui-core/src/main/res/values-pt-rBR/strings.xml | 2 +- ui-core/src/main/res/values-pt-rPT/strings.xml | 2 +- ui-core/src/main/res/values-ro-rRO/strings.xml | 2 +- ui-core/src/main/res/values-ru-rRU/strings.xml | 2 +- ui-core/src/main/res/values-sk-rSK/strings.xml | 2 +- ui-core/src/main/res/values-sl-rSI/strings.xml | 2 +- ui-core/src/main/res/values-sv-rSE/strings.xml | 2 +- ui-core/src/main/res/values-zh-rCN/strings.xml | 2 +- ui-core/src/main/res/values-zh-rTW/strings.xml | 2 +- ui-core/src/main/res/values/dimens.xml | 2 +- ui-core/src/main/res/values/strings.xml | 1 + 33 files changed, 38 insertions(+), 34 deletions(-) rename {card/src/main/java/com/adyen/checkout/card => ui-core/src/main/java/com/adyen/checkout/ui/core}/internal/ui/view/SocialSecurityNumberInput.kt (87%) rename {card/src/main/java/com/adyen/checkout/card => ui-core/src/main/java/com/adyen/checkout/ui/core}/internal/util/SocialSecurityNumberUtils.kt (93%) diff --git a/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt b/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt index 6881c179b1..3dc2ec8a59 100644 --- a/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt +++ b/card/src/main/java/com/adyen/checkout/card/internal/ui/DefaultCardDelegate.kt @@ -31,7 +31,6 @@ import com.adyen.checkout.card.internal.util.CardValidationUtils import com.adyen.checkout.card.internal.util.DetectedCardTypesUtils import com.adyen.checkout.card.internal.util.InstallmentUtils import com.adyen.checkout.card.internal.util.KcpValidationUtils -import com.adyen.checkout.card.internal.util.SocialSecurityNumberUtils import com.adyen.checkout.components.core.OrderRequest import com.adyen.checkout.components.core.PaymentComponentData import com.adyen.checkout.components.core.PaymentMethod @@ -67,6 +66,7 @@ import com.adyen.checkout.ui.core.internal.ui.model.AddressOutputData import com.adyen.checkout.ui.core.internal.ui.model.AddressParams import com.adyen.checkout.ui.core.internal.util.AddressFormUtils import com.adyen.checkout.ui.core.internal.util.AddressValidationUtils +import com.adyen.checkout.ui.core.internal.util.SocialSecurityNumberUtils import com.adyen.threeds2.ThreeDS2Service import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel diff --git a/card/src/main/res/layout/card_view.xml b/card/src/main/res/layout/card_view.xml index ee62c13b3d..8ba391e68c 100644 --- a/card/src/main/res/layout/card_view.xml +++ b/card/src/main/res/layout/card_view.xml @@ -156,7 +156,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - %%storedPaymentMethod.disable.cancelButton%% %%validationAlert.title%% + %%validationAlert.title%% %%billingAddress%% %%street%% diff --git a/ui-core/src/main/res/values-ar/strings.xml b/ui-core/src/main/res/values-ar/strings.xml index 3dcc8d93e6..2156c8b7d8 100644 --- a/ui-core/src/main/res/values-ar/strings.xml +++ b/ui-core/src/main/res/values-ar/strings.xml @@ -16,6 +16,7 @@ إلغاء إدخال غير صحيح + إدخال غير صحيح عنوان الفواتير الشارع @@ -41,5 +42,4 @@ الرمز البريدي (اختياري) المقاطعة أو الإقليم (اختياري) المدينة / البلدة (اختياري) - 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 3d49266365..9808cc86a0 100644 --- a/ui-core/src/main/res/values-cs-rCZ/strings.xml +++ b/ui-core/src/main/res/values-cs-rCZ/strings.xml @@ -16,6 +16,7 @@ Zrušit Neplatný vstup + Neplatný vstup Fakturační adresa Ulice @@ -41,5 +42,4 @@ PSČ (nepovinné) Provincie nebo teritorium Město (nepovinné) - 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 71afb4a1ef..05b1af5f84 100644 --- a/ui-core/src/main/res/values-da-rDK/strings.xml +++ b/ui-core/src/main/res/values-da-rDK/strings.xml @@ -16,6 +16,7 @@ Annuller Ugyldig indtastning + Ugyldig indtastning Faktureringsadresse Gade @@ -41,5 +42,4 @@ Postnummer (valgfrit) Provins eller territorium (valgfrit) By (valgfrit) - 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 b1e61d38ad..5301321678 100644 --- a/ui-core/src/main/res/values-de-rDE/strings.xml +++ b/ui-core/src/main/res/values-de-rDE/strings.xml @@ -16,6 +16,7 @@ Abbrechen Ungültige Eingabe + Ungültige Eingabe Rechnungsadresse Straße @@ -41,5 +42,4 @@ PLZ (optional) Provinz oder Territorium (optional) Ort (optional) - 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 8dc62e2e7b..52458d7287 100644 --- a/ui-core/src/main/res/values-el-rGR/strings.xml +++ b/ui-core/src/main/res/values-el-rGR/strings.xml @@ -16,6 +16,7 @@ Άκυρο Μη έγκυρη είσοδος + Μη έγκυρη είσοδος Διεύθυνση τιμολόγησης Οδός @@ -41,5 +42,4 @@ Ταχυδρομικός κωδικός (προαιρετικό) Επαρχία ή περιοχή (προαιρετικό) Πόλη / Δήμος (προαιρετικό) - 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 05dfcc5325..6e81195c7c 100644 --- a/ui-core/src/main/res/values-es-rES/strings.xml +++ b/ui-core/src/main/res/values-es-rES/strings.xml @@ -16,6 +16,7 @@ Cancelar Entrada no válida + Entrada no válida Dirección de facturación Calle @@ -41,5 +42,4 @@ Código postal (opcional) Provincia o territorio (opcional) Ciudad/población (opcional) - 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 6ac4431b2e..f5f5631d2c 100644 --- a/ui-core/src/main/res/values-fi-rFI/strings.xml +++ b/ui-core/src/main/res/values-fi-rFI/strings.xml @@ -16,6 +16,7 @@ Peruuta Ei-kelvollinen syöte + Ei-kelvollinen syöte Laskutusosoite Katu @@ -41,5 +42,4 @@ Postinumero (valinnainen) Maakunta tai alue (valinnainen) Kaupunki / taajama (valinnainen) - 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 1b2bbb9d9e..d73479c09a 100644 --- a/ui-core/src/main/res/values-fr-rFR/strings.xml +++ b/ui-core/src/main/res/values-fr-rFR/strings.xml @@ -16,6 +16,7 @@ Annuler Saisie incorrecte + Saisie incorrecte Adresse de facturation Rue @@ -41,5 +42,4 @@ Code postal (facultatif) Province ou territoire (facultatif) Ville (facultatif) - 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 15376e3275..1836d32082 100644 --- a/ui-core/src/main/res/values-hr-rHR/strings.xml +++ b/ui-core/src/main/res/values-hr-rHR/strings.xml @@ -16,6 +16,7 @@ Otkaži Pogrešan unos + Pogrešan unos Adresa za račun Ulica @@ -41,5 +42,4 @@ Poštanski broj (neobavezno) Pokrajina ili teritorij (neobavezno) Grad (neobavezno) - 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 970528e5eb..5c5d0c51ee 100644 --- a/ui-core/src/main/res/values-hu-rHU/strings.xml +++ b/ui-core/src/main/res/values-hu-rHU/strings.xml @@ -16,6 +16,7 @@ Mégse Érvénytelen bevitel + Érvénytelen bevitel Számlázási cím Utca @@ -41,5 +42,4 @@ Irányítószám (nem kötelező) Tartomány vagy terület (nem kötelező) Város (nem kötelező) - 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 55d284e493..57d82831b4 100644 --- a/ui-core/src/main/res/values-it-rIT/strings.xml +++ b/ui-core/src/main/res/values-it-rIT/strings.xml @@ -16,6 +16,7 @@ Cancella Dati inseriti non validi + Dati inseriti non validi Indirizzo di fatturazione Via @@ -41,5 +42,4 @@ CAP (facoltativo) Provincia o territorio (facoltativo) Città (facoltativo) - 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 09318871ca..1f3bedc33a 100644 --- a/ui-core/src/main/res/values-ja-rJP/strings.xml +++ b/ui-core/src/main/res/values-ja-rJP/strings.xml @@ -16,6 +16,7 @@ キャンセル 無効な入力 + 無効な入力 ご請求住所 番地 @@ -41,5 +42,4 @@ 郵便番号 (任意) 州または準州 (任意) 市区町村 (任意) - 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 8e8435f8c2..77f0147750 100644 --- a/ui-core/src/main/res/values-ko-rKR/strings.xml +++ b/ui-core/src/main/res/values-ko-rKR/strings.xml @@ -16,6 +16,7 @@ 취소 유효하지 않은 입력 + 유효하지 않은 입력 청구지 주소 도로명 @@ -41,5 +42,4 @@ 우편 번호(선택 사항) 도(선택 사항) 시/구(선택 사항) - 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 eb89de89e9..8de3a1c2e9 100644 --- a/ui-core/src/main/res/values-nb-rNO/strings.xml +++ b/ui-core/src/main/res/values-nb-rNO/strings.xml @@ -16,6 +16,7 @@ Avbryt Ugyldige inndata + Ugyldige inndata Faktureringsadresse Gate @@ -41,5 +42,4 @@ Postnummer (valgfritt) Provins eller territorium (valgfritt) By (valgfritt) - 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 069edde09f..25545be1ac 100644 --- a/ui-core/src/main/res/values-nl-rNL/strings.xml +++ b/ui-core/src/main/res/values-nl-rNL/strings.xml @@ -16,6 +16,7 @@ Annuleren Ongeldige invoer + Ongeldige invoer Factuuradres Straatnaam @@ -41,5 +42,4 @@ Postcode (optioneel) Provincie of territorium (optioneel) Stad (optioneel) - 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 1f0dcf60ec..023bf43643 100644 --- a/ui-core/src/main/res/values-pl-rPL/strings.xml +++ b/ui-core/src/main/res/values-pl-rPL/strings.xml @@ -16,6 +16,7 @@ Anuluj Nieprawidłowe dane wejściowe + Nieprawidłowe dane wejściowe Adres rozliczeniowy Ulica @@ -41,5 +42,4 @@ Kod pocztowy (opcjonalnie) Region lub terytorium (opcjonalnie) Miejscowość (opcjonalnie) - 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 cdac30267a..b75b5b5fb9 100644 --- a/ui-core/src/main/res/values-pt-rBR/strings.xml +++ b/ui-core/src/main/res/values-pt-rBR/strings.xml @@ -16,6 +16,7 @@ Cancelar Entrada inválida + Entrada inválida Endereço de cobrança Rua @@ -41,5 +42,4 @@ Código postal (opcional) Província ou território (opcional) Cidade (opcional) - 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 601db5bd1c..3a6cd14f2f 100644 --- a/ui-core/src/main/res/values-pt-rPT/strings.xml +++ b/ui-core/src/main/res/values-pt-rPT/strings.xml @@ -16,6 +16,7 @@ Cancelar Entrada inválida + Entrada inválida Morada de cobrança Rua @@ -41,5 +42,4 @@ Código postal (opcional) Província ou Território (opcional) Cidade/cidade (opcional) - 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 e3f932a2a6..5446b2cc55 100644 --- a/ui-core/src/main/res/values-ro-rRO/strings.xml +++ b/ui-core/src/main/res/values-ro-rRO/strings.xml @@ -16,6 +16,7 @@ Anulare Informațiile sunt incorecte + Informațiile sunt incorecte Adresa de facturare Strada @@ -41,5 +42,4 @@ Cod poștal (opțional) Provincie sau teritoriu (opțional) Oraș/localitate (opțional) - 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 d714921d9f..9f7cebc1e0 100644 --- a/ui-core/src/main/res/values-ru-rRU/strings.xml +++ b/ui-core/src/main/res/values-ru-rRU/strings.xml @@ -16,6 +16,7 @@ Отменить Неверные данные + Неверные данные Платежный адрес Улица @@ -41,5 +42,4 @@ Почтовый индекс (необязательно) Область или территория (необязательно) Город (необязательно) - 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 675be23b3d..d9fe85bb2a 100644 --- a/ui-core/src/main/res/values-sk-rSK/strings.xml +++ b/ui-core/src/main/res/values-sk-rSK/strings.xml @@ -16,6 +16,7 @@ Zrušiť Neplatný vstup + Neplatný vstup Fakturačná adresa Ulica @@ -41,5 +42,4 @@ PSČ (nepovinné) Okres alebo územie (voliteľné) Mesto/obec (nepovinné) - 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 45ff35c9ed..3f5654744c 100644 --- a/ui-core/src/main/res/values-sl-rSI/strings.xml +++ b/ui-core/src/main/res/values-sl-rSI/strings.xml @@ -16,6 +16,7 @@ Prekliči Neveljaven vnos + Neveljaven vnos Naslov za račun Ulica @@ -41,5 +42,4 @@ Poštna številka (neobvezno) Območje ali ozemlje (neobvezno) Mesto (neobvezno) - 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 c71f3c946e..69caa3d0d8 100644 --- a/ui-core/src/main/res/values-sv-rSE/strings.xml +++ b/ui-core/src/main/res/values-sv-rSE/strings.xml @@ -16,6 +16,7 @@ Avbryt Ogiltig inmatning + Ogiltig inmatning Faktureringsadress Gatuadress @@ -41,5 +42,4 @@ Postnummer (valfritt) Provins eller territorium (valfritt) Ort (valfritt) - 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 322f9383b3..f35ca1d9cf 100644 --- a/ui-core/src/main/res/values-zh-rCN/strings.xml +++ b/ui-core/src/main/res/values-zh-rCN/strings.xml @@ -16,6 +16,7 @@ 取消 无效的输入 + 无效的输入 账单地址 街道 @@ -41,5 +42,4 @@ 邮政编码(可选) 省或地区(可选) 市 / 镇(可选) - 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 19e149f492..57c14d5adf 100644 --- a/ui-core/src/main/res/values-zh-rTW/strings.xml +++ b/ui-core/src/main/res/values-zh-rTW/strings.xml @@ -16,6 +16,7 @@ 取消 輸入無效 + 輸入無效 帳單地址 街道 @@ -41,5 +42,4 @@ 郵遞區號(選用) 省或地區(選用) 市/鎮(選用) - diff --git a/ui-core/src/main/res/values/dimens.xml b/ui-core/src/main/res/values/dimens.xml index 3a2ed40c89..cb09a9d45f 100644 --- a/ui-core/src/main/res/values/dimens.xml +++ b/ui-core/src/main/res/values/dimens.xml @@ -26,4 +26,4 @@ 60dp - \ No newline at end of file + diff --git a/ui-core/src/main/res/values/strings.xml b/ui-core/src/main/res/values/strings.xml index 0bdeb781c1..547183ddb5 100644 --- a/ui-core/src/main/res/values/strings.xml +++ b/ui-core/src/main/res/values/strings.xml @@ -16,6 +16,7 @@ Cancel Invalid Input + Invalid Input Billing address Street From b47d4a680a099399ab3c92d8c4b2538ebc14d9a4 Mon Sep 17 00:00:00 2001 From: Atef Etman Date: Fri, 31 Mar 2023 15:15:53 +0200 Subject: [PATCH 024/161] Add Boleto delegate interface COAND-366 --- .../boleto/internal/ui/BoletoDelegate.kt | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/BoletoDelegate.kt diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/BoletoDelegate.kt b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/BoletoDelegate.kt new file mode 100644 index 0000000000..84b1401008 --- /dev/null +++ b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/BoletoDelegate.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by atef on 31/3/2023. + */ + +package com.adyen.checkout.boleto.internal.ui + +import com.adyen.checkout.boleto.BoletoComponentState +import com.adyen.checkout.boleto.internal.ui.model.BoletoInputData +import com.adyen.checkout.boleto.internal.ui.model.BoletoOutputData +import com.adyen.checkout.components.core.internal.ui.PaymentComponentDelegate +import com.adyen.checkout.ui.core.internal.ui.AddressDelegate +import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate +import com.adyen.checkout.ui.core.internal.ui.UIStateDelegate +import com.adyen.checkout.ui.core.internal.ui.ViewProvidingDelegate +import kotlinx.coroutines.flow.Flow + +internal interface BoletoDelegate : + PaymentComponentDelegate, + ViewProvidingDelegate, + ButtonDelegate, + UIStateDelegate, + AddressDelegate { + + val outputData: BoletoOutputData + + val outputDataFlow: Flow + + val componentStateFlow: Flow + + fun updateInputData(update: BoletoInputData.() -> Unit) + + fun setInteractionBlocked(isInteractionBlocked: Boolean) +} From da689c7047b019cecfa2bb9a35fd9e58c455dc24 Mon Sep 17 00:00:00 2001 From: Atef Etman Date: Fri, 31 Mar 2023 15:20:59 +0200 Subject: [PATCH 025/161] Add Boleto strings COAND-366 --- .../main/res/template/values/strings.xml.tt | 21 +++++++++++++++++++ boleto/src/main/res/values-ar/strings.xml | 20 ++++++++++++++++++ boleto/src/main/res/values-cs-rCZ/strings.xml | 20 ++++++++++++++++++ boleto/src/main/res/values-da-rDK/strings.xml | 20 ++++++++++++++++++ boleto/src/main/res/values-de-rDE/strings.xml | 20 ++++++++++++++++++ boleto/src/main/res/values-el-rGR/strings.xml | 20 ++++++++++++++++++ boleto/src/main/res/values-es-rES/strings.xml | 20 ++++++++++++++++++ boleto/src/main/res/values-fi-rFI/strings.xml | 20 ++++++++++++++++++ boleto/src/main/res/values-fr-rFR/strings.xml | 20 ++++++++++++++++++ boleto/src/main/res/values-hr-rHR/strings.xml | 20 ++++++++++++++++++ boleto/src/main/res/values-hu-rHU/strings.xml | 20 ++++++++++++++++++ boleto/src/main/res/values-it-rIT/strings.xml | 20 ++++++++++++++++++ boleto/src/main/res/values-ja-rJP/strings.xml | 20 ++++++++++++++++++ boleto/src/main/res/values-ko-rKR/strings.xml | 20 ++++++++++++++++++ boleto/src/main/res/values-nb-rNO/strings.xml | 20 ++++++++++++++++++ boleto/src/main/res/values-nl-rNL/strings.xml | 20 ++++++++++++++++++ boleto/src/main/res/values-pl-rPL/strings.xml | 20 ++++++++++++++++++ boleto/src/main/res/values-pt-rBR/strings.xml | 20 ++++++++++++++++++ boleto/src/main/res/values-pt-rPT/strings.xml | 20 ++++++++++++++++++ boleto/src/main/res/values-ro-rRO/strings.xml | 20 ++++++++++++++++++ boleto/src/main/res/values-ru-rRU/strings.xml | 20 ++++++++++++++++++ boleto/src/main/res/values-sk-rSK/strings.xml | 20 ++++++++++++++++++ boleto/src/main/res/values-sl-rSI/strings.xml | 20 ++++++++++++++++++ boleto/src/main/res/values-sv-rSE/strings.xml | 20 ++++++++++++++++++ boleto/src/main/res/values-zh-rCN/strings.xml | 20 ++++++++++++++++++ boleto/src/main/res/values-zh-rTW/strings.xml | 20 ++++++++++++++++++ boleto/src/main/res/values/strings.xml | 21 +++++++++++++++++++ 27 files changed, 542 insertions(+) create mode 100644 boleto/src/main/res/template/values/strings.xml.tt create mode 100644 boleto/src/main/res/values-ar/strings.xml create mode 100644 boleto/src/main/res/values-cs-rCZ/strings.xml create mode 100644 boleto/src/main/res/values-da-rDK/strings.xml create mode 100644 boleto/src/main/res/values-de-rDE/strings.xml create mode 100644 boleto/src/main/res/values-el-rGR/strings.xml create mode 100644 boleto/src/main/res/values-es-rES/strings.xml create mode 100644 boleto/src/main/res/values-fi-rFI/strings.xml create mode 100644 boleto/src/main/res/values-fr-rFR/strings.xml create mode 100644 boleto/src/main/res/values-hr-rHR/strings.xml create mode 100644 boleto/src/main/res/values-hu-rHU/strings.xml create mode 100644 boleto/src/main/res/values-it-rIT/strings.xml create mode 100644 boleto/src/main/res/values-ja-rJP/strings.xml create mode 100644 boleto/src/main/res/values-ko-rKR/strings.xml create mode 100644 boleto/src/main/res/values-nb-rNO/strings.xml create mode 100644 boleto/src/main/res/values-nl-rNL/strings.xml create mode 100644 boleto/src/main/res/values-pl-rPL/strings.xml create mode 100644 boleto/src/main/res/values-pt-rBR/strings.xml create mode 100644 boleto/src/main/res/values-pt-rPT/strings.xml create mode 100644 boleto/src/main/res/values-ro-rRO/strings.xml create mode 100644 boleto/src/main/res/values-ru-rRU/strings.xml create mode 100644 boleto/src/main/res/values-sk-rSK/strings.xml create mode 100644 boleto/src/main/res/values-sl-rSI/strings.xml create mode 100644 boleto/src/main/res/values-sv-rSE/strings.xml create mode 100644 boleto/src/main/res/values-zh-rCN/strings.xml create mode 100644 boleto/src/main/res/values-zh-rTW/strings.xml create mode 100644 boleto/src/main/res/values/strings.xml diff --git a/boleto/src/main/res/template/values/strings.xml.tt b/boleto/src/main/res/template/values/strings.xml.tt new file mode 100644 index 0000000000..95fbe06ebc --- /dev/null +++ b/boleto/src/main/res/template/values/strings.xml.tt @@ -0,0 +1,21 @@ + + + + %%personalDetails%% + %%firstName%% + %%lastName%% + %%boleto.sendCopyToEmail%% + %%shopperEmail%% + %%boletobancario.btnLabel%% + CPF/CNPJ + + %%firstName.invalid%% + %%lastName.invalid%% + %%shopperEmail.invalid%% + diff --git a/boleto/src/main/res/values-ar/strings.xml b/boleto/src/main/res/values-ar/strings.xml new file mode 100644 index 0000000000..6335368497 --- /dev/null +++ b/boleto/src/main/res/values-ar/strings.xml @@ -0,0 +1,20 @@ + + + + البيانات الشخصية + الاسم الأول + الاسم الأخير + إرسال نسخة إلى بريدي الإلكتروني + عنوان البريد الإلكتروني + إنشاء طريقة دفع Boleto + + الاسم الأول غير صحيح + الاسم الأخير غير صحيح + عنوان بريد إلكتروني غير صحيح + diff --git a/boleto/src/main/res/values-cs-rCZ/strings.xml b/boleto/src/main/res/values-cs-rCZ/strings.xml new file mode 100644 index 0000000000..d3d44aef2f --- /dev/null +++ b/boleto/src/main/res/values-cs-rCZ/strings.xml @@ -0,0 +1,20 @@ + + + + Osobní údaje + Jméno + Příjmení + Poslat mi kopii na e-mail + E-mailová adresa + Vygenerovat Boleto + + Křestní jméno není platné + Příjmení není platné + Neplatná e-mailová adresa + diff --git a/boleto/src/main/res/values-da-rDK/strings.xml b/boleto/src/main/res/values-da-rDK/strings.xml new file mode 100644 index 0000000000..48e5562277 --- /dev/null +++ b/boleto/src/main/res/values-da-rDK/strings.xml @@ -0,0 +1,20 @@ + + + + Personlige oplysninger + Fornavn + Efternavn + Send en kopi til min e-mail + E-mailadresse + Generér Boleto + + Fornavnet er ikke gyldigt + Efternavnet er ikke gyldigt + Ugyldig e-mailadresse + diff --git a/boleto/src/main/res/values-de-rDE/strings.xml b/boleto/src/main/res/values-de-rDE/strings.xml new file mode 100644 index 0000000000..5d0bc626a6 --- /dev/null +++ b/boleto/src/main/res/values-de-rDE/strings.xml @@ -0,0 +1,20 @@ + + + + Persönliche Angaben + Vorname + Nachname + Eine Kopie an meine E-Mail-Adresse senden + E-Mail-Adresse + Boleto generieren + + Vorname ist ungültig + Nachname ist ungültig + Ungültige E-Mail-Adresse + diff --git a/boleto/src/main/res/values-el-rGR/strings.xml b/boleto/src/main/res/values-el-rGR/strings.xml new file mode 100644 index 0000000000..b906857ddc --- /dev/null +++ b/boleto/src/main/res/values-el-rGR/strings.xml @@ -0,0 +1,20 @@ + + + + Προσωπικά στοιχεία + Όνομα + Επώνυμο + Αποστολή αντιγράφου στη διεύθυνση email μου + Διεύθυνση email + Δημιουργία Boleto + + Το όνομα δεν είναι έγκυρο + Το επώνυμο δεν είναι έγκυρο + Μη έγκυρη διεύθυνση email + diff --git a/boleto/src/main/res/values-es-rES/strings.xml b/boleto/src/main/res/values-es-rES/strings.xml new file mode 100644 index 0000000000..c6c7d88a5b --- /dev/null +++ b/boleto/src/main/res/values-es-rES/strings.xml @@ -0,0 +1,20 @@ + + + + Datos personales + Nombre + Apellidos + Enviar copia a mi correo electrónico + Dirección de correo electrónico + Generar Boleto + + El nombre no es válido + El apellido no es válido + La dirección de correo electrónico no es válida + diff --git a/boleto/src/main/res/values-fi-rFI/strings.xml b/boleto/src/main/res/values-fi-rFI/strings.xml new file mode 100644 index 0000000000..de33f3014a --- /dev/null +++ b/boleto/src/main/res/values-fi-rFI/strings.xml @@ -0,0 +1,20 @@ + + + + Henkilötiedot + Etunimi + Sukunimi + Lähetä kopio sähköpostiini + Sähköpostiosoite + Luo Boleto + + Etunimi ei ole kelvollinen + Sukunimi ei ole kelvollinen + Ei-kelvollinen sähköpostiosoite + diff --git a/boleto/src/main/res/values-fr-rFR/strings.xml b/boleto/src/main/res/values-fr-rFR/strings.xml new file mode 100644 index 0000000000..a345e42146 --- /dev/null +++ b/boleto/src/main/res/values-fr-rFR/strings.xml @@ -0,0 +1,20 @@ + + + + Informations personnelles + Prénom + Nom de famille + Envoyer une copie à mon adresse e-mail + Adresse e-mail + Générer un Boleto + + Le prénom n\'est pas valide + Le nom n\'est pas valide + Adresse e-mail incorrecte + diff --git a/boleto/src/main/res/values-hr-rHR/strings.xml b/boleto/src/main/res/values-hr-rHR/strings.xml new file mode 100644 index 0000000000..1bcc43556b --- /dev/null +++ b/boleto/src/main/res/values-hr-rHR/strings.xml @@ -0,0 +1,20 @@ + + + + Osobni podatci + Ime + Prezime + Pošalji kopiju na moju e-poštu + Adresa e-pošte + Generiraj Boleto + + Ime nije valjano + Prezime nije valjano + Nevažeća adresa e-pošte + diff --git a/boleto/src/main/res/values-hu-rHU/strings.xml b/boleto/src/main/res/values-hu-rHU/strings.xml new file mode 100644 index 0000000000..2db33dc600 --- /dev/null +++ b/boleto/src/main/res/values-hu-rHU/strings.xml @@ -0,0 +1,20 @@ + + + + Személyes adatok + Keresztnév + Vezetéknév + Másolat küldése az e-mail-címemre + E-mail-cím + Boleto létrehozása + + A keresztnév nem érvényes + A vezetéknév nem érvényes + Érvénytelen e-mail-cím + diff --git a/boleto/src/main/res/values-it-rIT/strings.xml b/boleto/src/main/res/values-it-rIT/strings.xml new file mode 100644 index 0000000000..ecd39107e9 --- /dev/null +++ b/boleto/src/main/res/values-it-rIT/strings.xml @@ -0,0 +1,20 @@ + + + + Dati personali + Nome + Cognome + Invia una copia alla mia e-mail + Indirizzo e-mail + Genera Boleto + + Nome non valido + Cognome non valido + Indirizzo e-mail non valido + diff --git a/boleto/src/main/res/values-ja-rJP/strings.xml b/boleto/src/main/res/values-ja-rJP/strings.xml new file mode 100644 index 0000000000..a42176f3b1 --- /dev/null +++ b/boleto/src/main/res/values-ja-rJP/strings.xml @@ -0,0 +1,20 @@ + + + + 個人情報 + + + 自分のメールアドレスにコピーを送信する + Eメールアドレス + Boletoを生成する + + 名が無効です + 姓が無効です + Eメールアドレスが無効です + diff --git a/boleto/src/main/res/values-ko-rKR/strings.xml b/boleto/src/main/res/values-ko-rKR/strings.xml new file mode 100644 index 0000000000..f60a0aa9f6 --- /dev/null +++ b/boleto/src/main/res/values-ko-rKR/strings.xml @@ -0,0 +1,20 @@ + + + + 개인 정보 + 이름 + + 내 이메일로 사본 보내기 + 이메일 주소 + Boleto 생성 + + 이름이 올바르지 않습니다 + 성이 올바르지 않습니다 + 유효하지 않은 이메일 주소 + diff --git a/boleto/src/main/res/values-nb-rNO/strings.xml b/boleto/src/main/res/values-nb-rNO/strings.xml new file mode 100644 index 0000000000..c5ac58499c --- /dev/null +++ b/boleto/src/main/res/values-nb-rNO/strings.xml @@ -0,0 +1,20 @@ + + + + Personopplysninger + Fornavn + Etternavn + Send meg en kopi på e-post + E-postadresse + Generer Boleto + + Fornavnet er ikke gyldig + Etternavnet er ikke gyldig + Ugyldig e-postadresse + diff --git a/boleto/src/main/res/values-nl-rNL/strings.xml b/boleto/src/main/res/values-nl-rNL/strings.xml new file mode 100644 index 0000000000..3063edcee3 --- /dev/null +++ b/boleto/src/main/res/values-nl-rNL/strings.xml @@ -0,0 +1,20 @@ + + + + Persoonlijke gegevens + Voornaam + Achternaam + Stuur een kopie naar mijn e-mailadres + E-mailadres + Boleto genereren + + Voornaam is niet geldig + Achternaam is niet geldig + Ongeldig e-mailadres + diff --git a/boleto/src/main/res/values-pl-rPL/strings.xml b/boleto/src/main/res/values-pl-rPL/strings.xml new file mode 100644 index 0000000000..eb991958b6 --- /dev/null +++ b/boleto/src/main/res/values-pl-rPL/strings.xml @@ -0,0 +1,20 @@ + + + + Dane osobowe + Imię + Nazwisko + Wyślij kopię na mój e-mail + Adres e-mail + Wygeneruj płatność Boleto + + Imię jest nieprawidłowe + Nazwisko jest nieprawidłowe + Niepoprawny adres email + diff --git a/boleto/src/main/res/values-pt-rBR/strings.xml b/boleto/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 0000000000..b1965158c5 --- /dev/null +++ b/boleto/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,20 @@ + + + + Informações pessoais + Nome + Sobrenome + Enviar uma cópia por e-mail + Endereço de e-mail + Gerar Boleto + + Este não é um nome válido + Este não é um sobrenome válido + Endereço de e-mail inválido + diff --git a/boleto/src/main/res/values-pt-rPT/strings.xml b/boleto/src/main/res/values-pt-rPT/strings.xml new file mode 100644 index 0000000000..f654d0ae45 --- /dev/null +++ b/boleto/src/main/res/values-pt-rPT/strings.xml @@ -0,0 +1,20 @@ + + + + Detalhes pessoais + Nome próprio + Apelido + Enviar uma cópia para o meu e-mail + Endereço de correio eletrónico + Gerar comprovativo + + O nome próprio não é válido + O apelido não é válido + Endereço de e-mail inválido + diff --git a/boleto/src/main/res/values-ro-rRO/strings.xml b/boleto/src/main/res/values-ro-rRO/strings.xml new file mode 100644 index 0000000000..3900d6bb25 --- /dev/null +++ b/boleto/src/main/res/values-ro-rRO/strings.xml @@ -0,0 +1,20 @@ + + + + Informații personale + Prenume + Nume de familie + Trimite o copie la adresa mea de e-mail + Adresă de e-mail + Generare Boleto + + Prenumele nu este valabil + Numele de familie nu este valabil + Adresă de e-mail incorectă + diff --git a/boleto/src/main/res/values-ru-rRU/strings.xml b/boleto/src/main/res/values-ru-rRU/strings.xml new file mode 100644 index 0000000000..220508c8af --- /dev/null +++ b/boleto/src/main/res/values-ru-rRU/strings.xml @@ -0,0 +1,20 @@ + + + + Личные данные + Имя + Фамилия + Отправить мне копию на эл. почту + Адрес эл. почты + Создать Boleto + + Неверное имя + Неверная фамилия + Недействительный адрес эл. почты + diff --git a/boleto/src/main/res/values-sk-rSK/strings.xml b/boleto/src/main/res/values-sk-rSK/strings.xml new file mode 100644 index 0000000000..e78d170e9b --- /dev/null +++ b/boleto/src/main/res/values-sk-rSK/strings.xml @@ -0,0 +1,20 @@ + + + + Osobné údaje + Krstné meno + Priezvisko + Zaslať kópiu na môj e-mail + E-mailová adresa + Generovať Boleto + + Meno nie je platné + Priezvisko nie je platné + Neplatná emailová adresa + diff --git a/boleto/src/main/res/values-sl-rSI/strings.xml b/boleto/src/main/res/values-sl-rSI/strings.xml new file mode 100644 index 0000000000..560c324737 --- /dev/null +++ b/boleto/src/main/res/values-sl-rSI/strings.xml @@ -0,0 +1,20 @@ + + + + Osebni podatki + Ime + Priimek + Pošlji kopijo na moj elektronski naslov + Elektronski naslov + Ustvari Boleto + + Ime ni veljavno + Priimek ni veljaven + Neveljaven elektronski naslov + diff --git a/boleto/src/main/res/values-sv-rSE/strings.xml b/boleto/src/main/res/values-sv-rSE/strings.xml new file mode 100644 index 0000000000..aea6d591b8 --- /dev/null +++ b/boleto/src/main/res/values-sv-rSE/strings.xml @@ -0,0 +1,20 @@ + + + + Personuppgifter + Förnamn + Efternamn + Skicka en kopia till min e-post + E-postadress + Generera Boleto + + Förnamnet är inte giltigt + Efternamnet är inte giltigt + Ogiltig e-postadress + diff --git a/boleto/src/main/res/values-zh-rCN/strings.xml b/boleto/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000000..71a382aba5 --- /dev/null +++ b/boleto/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,20 @@ + + + + 个人详细信息 + 名字 + 姓氏 + 将副本发送到我的电子邮箱 + 电子邮件地址 + 生成 Boleto + + 名字无效 + 姓氏无效 + 无效的邮件地址 + diff --git a/boleto/src/main/res/values-zh-rTW/strings.xml b/boleto/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 0000000000..8027a1cd53 --- /dev/null +++ b/boleto/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,20 @@ + + + + 個人詳細資料 + 名字 + 姓氏 + 將複本傳送至我的電子郵件 + 電子郵件地址 + 產生 Boleto + + 名字無效 + 姓氏無效 + 電子郵件地址無效 + diff --git a/boleto/src/main/res/values/strings.xml b/boleto/src/main/res/values/strings.xml new file mode 100644 index 0000000000..acdcc50334 --- /dev/null +++ b/boleto/src/main/res/values/strings.xml @@ -0,0 +1,21 @@ + + + + Personal details + First name + Last name + Send a copy to my email + Email address + Generate Boleto + CPF/CNPJ + + First name is not valid + Last name is not valid + Invalid email address + From ebe69ff438dbb99c46766e2f05f10bd1346a40ee Mon Sep 17 00:00:00 2001 From: Atef Etman Date: Fri, 31 Mar 2023 15:24:06 +0200 Subject: [PATCH 026/161] Add Boleto delegate default implementation COAND-366 --- .../internal/ui/DefaultBoletoDelegate.kt | 304 ++++++++++++++++++ .../internal/util/BoletoValidationUtils.kt | 41 +++ 2 files changed, 345 insertions(+) create mode 100644 boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegate.kt create mode 100644 boleto/src/main/java/com/adyen/checkout/boleto/internal/util/BoletoValidationUtils.kt diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegate.kt b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegate.kt new file mode 100644 index 0000000000..c68ea283e8 --- /dev/null +++ b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegate.kt @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2023 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by atef on 31/3/2023. + */ + +package com.adyen.checkout.boleto.internal.ui + +import androidx.annotation.VisibleForTesting +import androidx.lifecycle.LifecycleOwner +import com.adyen.checkout.boleto.BoletoComponentState +import com.adyen.checkout.boleto.internal.ui.model.BoletoComponentParams +import com.adyen.checkout.boleto.internal.ui.model.BoletoInputData +import com.adyen.checkout.boleto.internal.ui.model.BoletoOutputData +import com.adyen.checkout.boleto.internal.util.BoletoValidationUtils +import com.adyen.checkout.components.core.OrderRequest +import com.adyen.checkout.components.core.PaymentComponentData +import com.adyen.checkout.components.core.PaymentMethod +import com.adyen.checkout.components.core.PaymentMethodTypes +import com.adyen.checkout.components.core.ShopperName +import com.adyen.checkout.components.core.internal.PaymentComponentEvent +import com.adyen.checkout.components.core.internal.PaymentObserverRepository +import com.adyen.checkout.components.core.internal.data.api.AnalyticsRepository +import com.adyen.checkout.components.core.paymentmethod.GenericPaymentMethod +import com.adyen.checkout.core.internal.util.LogUtil +import com.adyen.checkout.core.internal.util.Logger +import com.adyen.checkout.ui.core.internal.data.api.AddressRepository +import com.adyen.checkout.ui.core.internal.ui.AddressFormUIState +import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType +import com.adyen.checkout.ui.core.internal.ui.ComponentViewType +import com.adyen.checkout.ui.core.internal.ui.PaymentComponentUIEvent +import com.adyen.checkout.ui.core.internal.ui.PaymentComponentUIState +import com.adyen.checkout.ui.core.internal.ui.SubmitHandler +import com.adyen.checkout.ui.core.internal.ui.model.AddressInputModel +import com.adyen.checkout.ui.core.internal.ui.model.AddressListItem +import com.adyen.checkout.ui.core.internal.ui.model.AddressOutputData +import com.adyen.checkout.ui.core.internal.ui.model.AddressParams +import com.adyen.checkout.ui.core.internal.util.AddressFormUtils +import com.adyen.checkout.ui.core.internal.util.AddressValidationUtils +import com.adyen.checkout.ui.core.internal.util.SocialSecurityNumberUtils +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +@Suppress("TooManyFunctions", "LongParameterList") +internal class DefaultBoletoDelegate( + private val submitHandler: SubmitHandler, + private val analyticsRepository: AnalyticsRepository, + private val observerRepository: PaymentObserverRepository, + private val paymentMethod: PaymentMethod, + private val order: OrderRequest?, + override val componentParams: BoletoComponentParams, + private val addressRepository: AddressRepository, +) : BoletoDelegate { + private val inputData = BoletoInputData() + + override val outputData: BoletoOutputData + get() = _outputDataFlow.value + + private val _outputDataFlow = MutableStateFlow(createOutputData()) + override val outputDataFlow: Flow = _outputDataFlow + + override val addressOutputData: AddressOutputData + get() = outputData.addressState + + override val addressOutputDataFlow: Flow by lazy { + outputDataFlow.map { + it.addressState + }.stateIn(coroutineScope, SharingStarted.Lazily, outputData.addressState) + } + + private val _componentStateFlow = MutableStateFlow(createComponentState()) + override val componentStateFlow: Flow = _componentStateFlow + + private val _viewFlow: MutableStateFlow = MutableStateFlow(null) + override val viewFlow: Flow = _viewFlow + + override val submitFlow: Flow = submitHandler.submitFlow + override val uiStateFlow: Flow = submitHandler.uiStateFlow + override val uiEventFlow: Flow = submitHandler.uiEventFlow + + private var _coroutineScope: CoroutineScope? = null + private val coroutineScope: CoroutineScope get() = requireNotNull(_coroutineScope) + + override fun initialize(coroutineScope: CoroutineScope) { + _coroutineScope = coroutineScope + submitHandler.initialize(coroutineScope, componentStateFlow) + + sendAnalyticsEvent(coroutineScope) + + if (componentParams.addressParams is AddressParams.FullAddress) { + subscribeToStatesList() + subscribeToCountryList() + requestCountryList() + } + } + + private fun sendAnalyticsEvent(coroutineScope: CoroutineScope) { + Logger.v(TAG, "sendAnalyticsEvent") + coroutineScope.launch { + analyticsRepository.sendAnalyticsEvent() + } + } + + private fun subscribeToStatesList() { + addressRepository.statesFlow + .distinctUntilChanged() + .onEach { states -> + Logger.d(TAG, "New states emitted - states: ${states.size}") + updateOutputData(stateOptions = AddressFormUtils.initializeStateOptions(states)) + } + .launchIn(coroutineScope) + } + + private fun subscribeToCountryList() { + addressRepository.countriesFlow + .distinctUntilChanged() + .onEach { countries -> + val countryOptions = AddressFormUtils.initializeCountryOptions( + shopperLocale = componentParams.shopperLocale, + addressParams = componentParams.addressParams, + countryList = countries + ) + countryOptions.firstOrNull { it.selected }?.let { + inputData.address.country = it.code + requestStateList(it.code) + } + updateOutputData(countryOptions = countryOptions) + } + .launchIn(coroutineScope) + } + + private fun requestCountryList() { + addressRepository.getCountryList( + shopperLocale = componentParams.shopperLocale, + coroutineScope = coroutineScope + ) + } + + private fun requestStateList(countryCode: String?) { + addressRepository.getStateList( + shopperLocale = componentParams.shopperLocale, + countryCode = countryCode, + coroutineScope = coroutineScope + ) + } + + override fun updateAddressInputData(update: AddressInputModel.() -> Unit) { + updateInputData { + this.address.update() + } + } + + override fun updateInputData(update: BoletoInputData.() -> Unit) { + inputData.update() + onInputDataChanged() + } + + private fun onInputDataChanged() { + val outputData = createOutputData( + countryOptions = outputData.addressState.countryOptions, + stateOptions = outputData.addressState.stateOptions + ) + _outputDataFlow.tryEmit(outputData) + updateComponentState(outputData) + requestStateList(inputData.address.country) + } + + private fun updateOutputData( + countryOptions: List = outputData.addressState.countryOptions, + stateOptions: List = outputData.addressState.stateOptions, + ) { + val newOutputData = createOutputData(countryOptions, stateOptions) + _outputDataFlow.tryEmit(newOutputData) + updateComponentState(newOutputData) + } + + private fun createOutputData( + countryOptions: List = emptyList(), + stateOptions: List = emptyList(), + ): BoletoOutputData { + val updatedCountryOptions = AddressFormUtils.markAddressListItemSelected( + countryOptions, + inputData.address.country + ) + val updatedStateOptions = AddressFormUtils.markAddressListItemSelected( + stateOptions, + inputData.address.stateOrProvince + ) + + val addressFormUIState = AddressFormUIState.fromAddressParams(componentParams.addressParams) + + return BoletoOutputData( + firstNameState = BoletoValidationUtils.validateFirstName(inputData.firstName), + lastNameState = BoletoValidationUtils.validateLastName(inputData.lastName), + socialSecurityNumberState = SocialSecurityNumberUtils.validateSocialSecurityNumber( + inputData.socialSecurityNumber + ), + addressState = AddressValidationUtils.validateAddressInput( + inputData.address, + addressFormUIState, + updatedCountryOptions, + updatedStateOptions, + false + ), + addressUIState = addressFormUIState, + isSendEmailSelected = inputData.isSendEmailSelected, + shopperEmailState = BoletoValidationUtils.validateShopperEmail( + inputData.isSendEmailSelected, + inputData.shopperEmail + ) + ) + } + + @VisibleForTesting + internal fun updateComponentState(outputData: BoletoOutputData) { + Logger.v(TAG, "updateComponentState") + val componentState = createComponentState(outputData) + _componentStateFlow.tryEmit(componentState) + } + + private fun createComponentState( + outputData: BoletoOutputData = this.outputData + ): BoletoComponentState { + val paymentComponentData = PaymentComponentData( + paymentMethod = GenericPaymentMethod(paymentMethod.type), + order = order, + socialSecurityNumber = outputData.socialSecurityNumberState.value, + shopperName = ShopperName( + firstName = outputData.firstNameState.value, + lastName = outputData.lastNameState.value + ) + ) + if (outputData.isSendEmailSelected) { + paymentComponentData.shopperEmail = outputData.shopperEmailState.value + } + if (AddressFormUtils.isAddressRequired(outputData.addressUIState)) { + paymentComponentData.billingAddress = AddressFormUtils.makeAddressData( + addressOutputData = outputData.addressState, + addressFormUIState = outputData.addressUIState + ) + } + val countriesList: List = outputData.addressState.countryOptions + val statesList: List = outputData.addressState.stateOptions + + return BoletoComponentState( + data = paymentComponentData, + isInputValid = outputData.isValid, + isReady = countriesList.isNotEmpty() && statesList.isNotEmpty() + ) + } + + override fun setInteractionBlocked(isInteractionBlocked: Boolean) { + submitHandler.setInteractionBlocked(isInteractionBlocked) + } + + override fun getPaymentMethodType(): String { + return paymentMethod.type ?: PaymentMethodTypes.UNKNOWN + } + + override fun observe( + lifecycleOwner: LifecycleOwner, + coroutineScope: CoroutineScope, + callback: (PaymentComponentEvent) -> Unit + ) { + observerRepository.addObservers( + stateFlow = componentStateFlow, + exceptionFlow = null, + submitFlow = submitFlow, + lifecycleOwner = lifecycleOwner, + coroutineScope = coroutineScope, + callback = callback + ) + } + + override fun removeObserver() { + observerRepository.removeObservers() + } + + override fun onSubmit() { + submitHandler.onSubmit(_componentStateFlow.value) + } + + override fun isConfirmationRequired(): Boolean = _viewFlow.value is ButtonComponentViewType + + override fun shouldShowSubmitButton(): Boolean = isConfirmationRequired() && componentParams.isSubmitButtonVisible + + override fun onCleared() { + removeObserver() + } + + companion object { + private val TAG = LogUtil.getTag() + } +} diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/internal/util/BoletoValidationUtils.kt b/boleto/src/main/java/com/adyen/checkout/boleto/internal/util/BoletoValidationUtils.kt new file mode 100644 index 0000000000..496dfac927 --- /dev/null +++ b/boleto/src/main/java/com/adyen/checkout/boleto/internal/util/BoletoValidationUtils.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by atef on 14/3/2023. + */ + +package com.adyen.checkout.boleto.internal.util + +import com.adyen.checkout.boleto.R +import com.adyen.checkout.components.core.internal.ui.model.FieldState +import com.adyen.checkout.components.core.internal.ui.model.Validation +import com.adyen.checkout.components.core.internal.util.ValidationUtils + +internal object BoletoValidationUtils { + + fun validateFirstName(firstName: String): FieldState { + return if (firstName.isNotBlank()) { + FieldState(firstName, Validation.Valid) + } else { + FieldState(firstName, Validation.Invalid(R.string.checkout_boleto_first_name_invalid)) + } + } + + fun validateLastName(lastName: String): FieldState { + return if (lastName.isNotBlank()) { + FieldState(lastName, Validation.Valid) + } else { + FieldState(lastName, Validation.Invalid(R.string.checkout_boleto_last_name_invalid)) + } + } + + fun validateShopperEmail(isEmailEnabled: Boolean, shopperEmail: String): FieldState { + return if (!isEmailEnabled || ValidationUtils.isEmailValid(shopperEmail)) { + FieldState(shopperEmail, Validation.Valid) + } else { + FieldState(shopperEmail, Validation.Invalid(R.string.checkout_boleto_email_invalid)) + } + } +} From ea3c1a12690ab8fd5d1316d9cf28d0af468dc954 Mon Sep 17 00:00:00 2001 From: Atef Etman Date: Fri, 31 Mar 2023 15:23:04 +0200 Subject: [PATCH 027/161] Add Boleto xml layout COAND-366 --- boleto/src/main/res/layout/boleto_view.xml | 80 ++++++++++++++++++++++ boleto/src/main/res/values/styles.xml | 45 ++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 boleto/src/main/res/layout/boleto_view.xml create mode 100644 boleto/src/main/res/values/styles.xml diff --git a/boleto/src/main/res/layout/boleto_view.xml b/boleto/src/main/res/layout/boleto_view.xml new file mode 100644 index 0000000000..f46d8f7efb --- /dev/null +++ b/boleto/src/main/res/layout/boleto_view.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/boleto/src/main/res/values/styles.xml b/boleto/src/main/res/values/styles.xml new file mode 100644 index 0000000000..e4c346af2b --- /dev/null +++ b/boleto/src/main/res/values/styles.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + From 5ba9d9807febaee83f1ce931563bf3424b5badfa Mon Sep 17 00:00:00 2001 From: Atef Etman Date: Fri, 31 Mar 2023 15:24:06 +0200 Subject: [PATCH 028/161] Add Boleto view COAND-366 --- .../boleto/internal/ui/BoletoViewProvider.kt | 37 +++ .../internal/ui/DefaultBoletoDelegate.kt | 2 +- .../boleto/internal/ui/view/BoletoView.kt | 210 ++++++++++++++++++ .../ui/core/internal/util/ViewExtensions.kt | 1 - 4 files changed, 248 insertions(+), 2 deletions(-) create mode 100644 boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/BoletoViewProvider.kt create mode 100644 boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/view/BoletoView.kt diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/BoletoViewProvider.kt b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/BoletoViewProvider.kt new file mode 100644 index 0000000000..5bad258611 --- /dev/null +++ b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/BoletoViewProvider.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by atef on 31/3/2023. + */ + +package com.adyen.checkout.boleto.internal.ui + +import android.content.Context +import android.util.AttributeSet +import com.adyen.checkout.boleto.R +import com.adyen.checkout.boleto.internal.ui.view.BoletoView +import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType +import com.adyen.checkout.ui.core.internal.ui.ComponentView +import com.adyen.checkout.ui.core.internal.ui.ComponentViewType +import com.adyen.checkout.ui.core.internal.ui.ViewProvider + +internal object BoletoViewProvider : ViewProvider { + override fun getView( + viewType: ComponentViewType, + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int + ): ComponentView = when (viewType) { + BoletoComponentViewType -> BoletoView(context, attrs, defStyleAttr) + else -> throw IllegalArgumentException("Unsupported view type") + } +} + +internal object BoletoComponentViewType : ButtonComponentViewType { + + override val viewProvider: ViewProvider = BoletoViewProvider + + override val buttonTextResId: Int = R.string.checkout_boleto_generate_btn_label +} diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegate.kt b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegate.kt index c68ea283e8..0435e994c9 100644 --- a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegate.kt +++ b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegate.kt @@ -81,7 +81,7 @@ internal class DefaultBoletoDelegate( private val _componentStateFlow = MutableStateFlow(createComponentState()) override val componentStateFlow: Flow = _componentStateFlow - private val _viewFlow: MutableStateFlow = MutableStateFlow(null) + private val _viewFlow: MutableStateFlow = MutableStateFlow(BoletoComponentViewType) override val viewFlow: Flow = _viewFlow override val submitFlow: Flow = submitHandler.submitFlow diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/view/BoletoView.kt b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/view/BoletoView.kt new file mode 100644 index 0000000000..af85a86d28 --- /dev/null +++ b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/view/BoletoView.kt @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2023 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by atef on 31/3/2023. + */ + +package com.adyen.checkout.boleto.internal.ui.view + +import android.content.Context +import android.text.Editable +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.view.View.OnFocusChangeListener +import android.widget.LinearLayout +import androidx.core.view.isVisible +import com.adyen.checkout.boleto.R +import com.adyen.checkout.boleto.databinding.BoletoViewBinding +import com.adyen.checkout.boleto.internal.ui.BoletoDelegate +import com.adyen.checkout.components.core.internal.ui.ComponentDelegate +import com.adyen.checkout.components.core.internal.ui.model.Validation +import com.adyen.checkout.ui.core.internal.ui.ComponentView +import com.adyen.checkout.ui.core.internal.util.hideError +import com.adyen.checkout.ui.core.internal.util.isVisible +import com.adyen.checkout.ui.core.internal.util.setLocalizedHintFromStyle +import com.adyen.checkout.ui.core.internal.util.setLocalizedTextFromStyle +import com.adyen.checkout.ui.core.internal.util.showError +import kotlinx.coroutines.CoroutineScope + +@Suppress("TooManyFunctions") +internal class BoletoView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : LinearLayout(context, attrs, defStyleAttr), ComponentView { + + private val binding = BoletoViewBinding.inflate(LayoutInflater.from(context), this) + + private lateinit var localizedContext: Context + + private lateinit var boletoDelegate: BoletoDelegate + + init { + orientation = VERTICAL + + val padding = resources.getDimension(R.dimen.standard_margin).toInt() + setPadding(padding, padding, padding, 0) + } + + override fun initView(delegate: ComponentDelegate, coroutineScope: CoroutineScope, localizedContext: Context) { + if (delegate !is BoletoDelegate) throw IllegalArgumentException("Unsupported delegate type") + boletoDelegate = delegate + + this.localizedContext = localizedContext + + initLocalizedStrings(localizedContext) + initFirstNameInput() + initLastNameInput() + initSocialSecurityNumberInput() + initAddressFormInput(coroutineScope) + initShopperEmailInput() + binding.switchSendEmailCopy.setOnCheckedChangeListener { _, isChecked -> + delegate.updateInputData { isSendEmailSelected = isChecked } + binding.textInputLayoutShopperEmail.isVisible = isChecked + } + } + + private fun initLocalizedStrings(localizedContext: Context) { + binding.textViewPersonalInformationHeader.setLocalizedTextFromStyle( + R.style.AdyenCheckout_Boleto_PersonalDetailsHeader, + localizedContext + ) + binding.textInputLayoutFirstName.setLocalizedHintFromStyle( + R.style.AdyenCheckout_Boleto_FirstNameInput, + localizedContext + ) + binding.textInputLayoutLastName.setLocalizedHintFromStyle( + R.style.AdyenCheckout_Boleto_LastNameInput, + localizedContext + ) + binding.textInputLayoutSocialSecurityNumber.setLocalizedHintFromStyle( + R.style.AdyenCheckout_Boleto_SocialNumberInput, + localizedContext + ) + binding.addressFormInput.initLocalizedContext(localizedContext) + binding.switchSendEmailCopy.setLocalizedTextFromStyle( + R.style.AdyenCheckout_Boleto_EmailCopySwitch, + localizedContext + ) + binding.textInputLayoutShopperEmail.setLocalizedHintFromStyle( + R.style.AdyenCheckout_Boleto_ShopperEmailInput, + localizedContext + ) + } + + private fun initFirstNameInput() { + binding.editTextFirstName.setOnChangeListener { editable: Editable -> + boletoDelegate.updateInputData { firstName = editable.toString() } + binding.textInputLayoutFirstName.hideError() + } + binding.editTextFirstName.onFocusChangeListener = OnFocusChangeListener { _, hasFocus -> + val firstNameValidation = boletoDelegate.outputData.firstNameState.validation + if (hasFocus) { + binding.textInputLayoutFirstName.hideError() + } else if (firstNameValidation is Validation.Invalid) { + binding.textInputLayoutFirstName.showError(localizedContext.getString(firstNameValidation.reason)) + } + } + } + + private fun initLastNameInput() { + binding.editTextLastName.setOnChangeListener { editable: Editable -> + boletoDelegate.updateInputData { lastName = editable.toString() } + binding.textInputLayoutLastName.hideError() + } + binding.editTextLastName.onFocusChangeListener = OnFocusChangeListener { _, hasFocus -> + val lastNameValidation = boletoDelegate.outputData.lastNameState.validation + if (hasFocus) { + binding.textInputLayoutLastName.hideError() + } else if (lastNameValidation is Validation.Invalid) { + binding.textInputLayoutLastName.showError(localizedContext.getString(lastNameValidation.reason)) + } + } + } + + private fun initSocialSecurityNumberInput() { + binding.editTextSocialSecurityNumber.setOnChangeListener { editable -> + boletoDelegate.updateInputData { socialSecurityNumber = editable.toString() } + binding.textInputLayoutSocialSecurityNumber.hideError() + } + binding.editTextSocialSecurityNumber.onFocusChangeListener = OnFocusChangeListener { _, hasFocus -> + val socialSecurityNumberValidation = boletoDelegate.outputData.socialSecurityNumberState.validation + if (hasFocus) { + binding.textInputLayoutSocialSecurityNumber.hideError() + } else if (socialSecurityNumberValidation is Validation.Invalid) { + binding.textInputLayoutSocialSecurityNumber.showError( + localizedContext.getString(socialSecurityNumberValidation.reason) + ) + } + } + } + + private fun initAddressFormInput(coroutineScope: CoroutineScope) { + binding.addressFormInput.attachDelegate(boletoDelegate, coroutineScope) + } + + private fun initShopperEmailInput() { + binding.editTextShopperEmail.setOnChangeListener { + boletoDelegate.updateInputData { shopperEmail = it.toString().trim() } + binding.textInputLayoutShopperEmail.hideError() + } + binding.editTextShopperEmail.onFocusChangeListener = OnFocusChangeListener { _, hasFocus -> + val shopperEmailValidation = boletoDelegate.outputData.shopperEmailState.validation + if (hasFocus) { + binding.textInputLayoutShopperEmail.hideError() + } else if (shopperEmailValidation is Validation.Invalid) { + binding.textInputLayoutShopperEmail.showError(localizedContext.getString(shopperEmailValidation.reason)) + } + } + } + + override fun highlightValidationErrors() { + boletoDelegate.outputData.let { + var isErrorFocused = false + val firstNameValidation = it.firstNameState.validation + if (binding.textInputLayoutFirstName.isVisible && firstNameValidation is Validation.Invalid) { + isErrorFocused = true + binding.textInputLayoutFirstName.requestFocus() + binding.textInputLayoutFirstName.showError(localizedContext.getString(firstNameValidation.reason)) + } + val lastNameValidation = it.lastNameState.validation + if (binding.textInputLayoutLastName.isVisible && lastNameValidation is Validation.Invalid) { + if (!isErrorFocused) { + isErrorFocused = true + binding.textInputLayoutLastName.requestFocus() + } + binding.textInputLayoutLastName.showError(localizedContext.getString(lastNameValidation.reason)) + } + val socialSecurityNumberValidation = it.socialSecurityNumberState.validation + if (binding.textInputLayoutSocialSecurityNumber.isVisible && + socialSecurityNumberValidation is Validation.Invalid + ) { + if (!isErrorFocused) { + isErrorFocused = true + binding.textInputLayoutSocialSecurityNumber.requestFocus() + } + binding.textInputLayoutSocialSecurityNumber.showError( + localizedContext.getString(socialSecurityNumberValidation.reason) + ) + } + if (binding.addressFormInput.isVisible && !it.addressState.isValid) { + binding.addressFormInput.highlightValidationErrors(isErrorFocused) + } + val shopperEmailValidation = it.shopperEmailState.validation + if (shopperEmailValidation is Validation.Invalid && binding.textInputLayoutShopperEmail.isVisible) { + if (!isErrorFocused) { + isErrorFocused = true + binding.textInputLayoutShopperEmail.requestFocus() + } + binding.textInputLayoutShopperEmail.showError( + localizedContext.getString(shopperEmailValidation.reason) + ) + } + } + } + + override fun getView(): View = this +} diff --git a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/util/ViewExtensions.kt b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/util/ViewExtensions.kt index 9fd61eaa6a..c3d2d9604d 100644 --- a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/util/ViewExtensions.kt +++ b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/util/ViewExtensions.kt @@ -21,7 +21,6 @@ inline var TextInputLayout.isVisible: Boolean this.visibility = visibility editText?.apply { this.visibility = visibility - isFocusable = value } } From f7cd7a92ee523e24b7a925c549609e83cf5098b6 Mon Sep 17 00:00:00 2001 From: Atef Etman Date: Fri, 31 Mar 2023 15:25:14 +0200 Subject: [PATCH 029/161] Add Boleto component and component provider COAND-366 --- .../adyen/checkout/boleto/BoletoComponent.kt | 115 ++++++++++ .../provider/BoletoComponentProvider.kt | 214 ++++++++++++++++++ .../components/core/PaymentMethodTypes.kt | 2 + 3 files changed, 331 insertions(+) create mode 100644 boleto/src/main/java/com/adyen/checkout/boleto/BoletoComponent.kt create mode 100644 boleto/src/main/java/com/adyen/checkout/boleto/internal/provider/BoletoComponentProvider.kt diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/BoletoComponent.kt b/boleto/src/main/java/com/adyen/checkout/boleto/BoletoComponent.kt new file mode 100644 index 0000000000..28d67df59f --- /dev/null +++ b/boleto/src/main/java/com/adyen/checkout/boleto/BoletoComponent.kt @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2023 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by atef on 31/3/2023. + */ + +package com.adyen.checkout.boleto + +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.adyen.checkout.action.internal.ActionHandlingComponent +import com.adyen.checkout.action.internal.DefaultActionHandlingComponent +import com.adyen.checkout.action.internal.ui.GenericActionDelegate +import com.adyen.checkout.boleto.internal.provider.BoletoComponentProvider +import com.adyen.checkout.boleto.internal.ui.BoletoDelegate +import com.adyen.checkout.components.core.PaymentMethodTypes +import com.adyen.checkout.components.core.internal.ButtonComponent +import com.adyen.checkout.components.core.internal.ComponentEventHandler +import com.adyen.checkout.components.core.internal.PaymentComponent +import com.adyen.checkout.components.core.internal.PaymentComponentEvent +import com.adyen.checkout.components.core.internal.toActionCallback +import com.adyen.checkout.components.core.internal.ui.ComponentDelegate +import com.adyen.checkout.core.internal.util.LogUtil +import com.adyen.checkout.core.internal.util.Logger +import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate +import com.adyen.checkout.ui.core.internal.ui.ComponentViewType +import com.adyen.checkout.ui.core.internal.ui.ViewableComponent +import com.adyen.checkout.ui.core.internal.util.mergeViewFlows +import kotlinx.coroutines.flow.Flow + +/** + * A [PaymentComponent] that supports the [PaymentMethodTypes.BOLETOBANCARIO], + * [PaymentMethodTypes.BOLETOBANCARIO_BANCODOBRASIL], [PaymentMethodTypes.BOLETOBANCARIO_BRADESCO], + * [PaymentMethodTypes.BOLETOBANCARIO_HSBC], [PaymentMethodTypes.BOLETOBANCARIO_ITAU], + * [PaymentMethodTypes.BOLETOBANCARIO_SANTANDER] and [PaymentMethodTypes.BOLETO_PRIMEIRO_PAY] payment methods. + */ +class BoletoComponent internal constructor( + private val boletoDelegate: BoletoDelegate, + private val genericActionDelegate: GenericActionDelegate, + private val actionHandlingComponent: DefaultActionHandlingComponent, + internal val componentEventHandler: ComponentEventHandler, +) : ViewModel(), + PaymentComponent, + ViewableComponent, + ButtonComponent, + ActionHandlingComponent by actionHandlingComponent { + + override val delegate: ComponentDelegate = actionHandlingComponent.activeDelegate + + override val viewFlow: Flow = mergeViewFlows( + viewModelScope, + boletoDelegate.viewFlow, + genericActionDelegate.viewFlow, + ) + + init { + boletoDelegate.initialize(viewModelScope) + genericActionDelegate.initialize(viewModelScope) + componentEventHandler.initialize(viewModelScope) + } + + internal fun observe( + lifecycleOwner: LifecycleOwner, + callback: (PaymentComponentEvent) -> Unit + ) { + boletoDelegate.observe(lifecycleOwner, viewModelScope, callback) + genericActionDelegate.observe(lifecycleOwner, viewModelScope, callback.toActionCallback()) + } + + internal fun removeObserver() { + boletoDelegate.removeObserver() + genericActionDelegate.removeObserver() + } + + override fun isConfirmationRequired(): Boolean = boletoDelegate.isConfirmationRequired() + + override fun submit() { + (boletoDelegate as? ButtonDelegate)?.onSubmit() + ?: Logger.e(TAG, "Component is currently not submittable, ignoring.") + } + + override fun setInteractionBlocked(isInteractionBlocked: Boolean) { + (boletoDelegate as? BoletoDelegate)?.setInteractionBlocked(isInteractionBlocked) + ?: Logger.e(TAG, "Payment component is not interactable, ignoring.") + } + + override fun onCleared() { + super.onCleared() + Logger.d(TAG, "onCleared") + boletoDelegate.onCleared() + genericActionDelegate.onCleared() + componentEventHandler.onCleared() + } + + companion object { + private val TAG = LogUtil.getTag() + + @JvmField + val PROVIDER = BoletoComponentProvider() + + @JvmField + val PAYMENT_METHOD_TYPES = listOf( + PaymentMethodTypes.BOLETOBANCARIO, + PaymentMethodTypes.BOLETOBANCARIO_BANCODOBRASIL, + PaymentMethodTypes.BOLETOBANCARIO_BRADESCO, + PaymentMethodTypes.BOLETOBANCARIO_HSBC, + PaymentMethodTypes.BOLETOBANCARIO_ITAU, + PaymentMethodTypes.BOLETOBANCARIO_SANTANDER, + PaymentMethodTypes.BOLETO_PRIMEIRO_PAY, + ) + } +} diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/internal/provider/BoletoComponentProvider.kt b/boleto/src/main/java/com/adyen/checkout/boleto/internal/provider/BoletoComponentProvider.kt new file mode 100644 index 0000000000..a26143478f --- /dev/null +++ b/boleto/src/main/java/com/adyen/checkout/boleto/internal/provider/BoletoComponentProvider.kt @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2023 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by atef on 31/3/2023. + */ + +package com.adyen.checkout.boleto.internal.provider + +import android.app.Application +import androidx.annotation.RestrictTo +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelStoreOwner +import androidx.savedstate.SavedStateRegistryOwner +import com.adyen.checkout.action.internal.DefaultActionHandlingComponent +import com.adyen.checkout.action.internal.provider.GenericActionComponentProvider +import com.adyen.checkout.boleto.BoletoComponent +import com.adyen.checkout.boleto.BoletoComponentState +import com.adyen.checkout.boleto.BoletoConfiguration +import com.adyen.checkout.boleto.internal.ui.DefaultBoletoDelegate +import com.adyen.checkout.boleto.internal.ui.model.BoletoComponentParamsMapper +import com.adyen.checkout.components.core.ComponentCallback +import com.adyen.checkout.components.core.Order +import com.adyen.checkout.components.core.PaymentMethod +import com.adyen.checkout.components.core.internal.DefaultComponentEventHandler +import com.adyen.checkout.components.core.internal.PaymentObserverRepository +import com.adyen.checkout.components.core.internal.data.api.AnalyticsMapper +import com.adyen.checkout.components.core.internal.data.api.AnalyticsService +import com.adyen.checkout.components.core.internal.data.api.DefaultAnalyticsRepository +import com.adyen.checkout.components.core.internal.data.model.AnalyticsSource +import com.adyen.checkout.components.core.internal.provider.PaymentComponentProvider +import com.adyen.checkout.components.core.internal.ui.model.ComponentParams +import com.adyen.checkout.components.core.internal.ui.model.SessionParams +import com.adyen.checkout.components.core.internal.util.get +import com.adyen.checkout.components.core.internal.util.viewModelFactory +import com.adyen.checkout.core.exception.ComponentException +import com.adyen.checkout.core.internal.data.api.HttpClientFactory +import com.adyen.checkout.sessions.core.CheckoutSession +import com.adyen.checkout.sessions.core.SessionComponentCallback +import com.adyen.checkout.sessions.core.internal.SessionComponentEventHandler +import com.adyen.checkout.sessions.core.internal.SessionInteractor +import com.adyen.checkout.sessions.core.internal.SessionSavedStateHandleContainer +import com.adyen.checkout.sessions.core.internal.data.api.SessionRepository +import com.adyen.checkout.sessions.core.internal.data.api.SessionService +import com.adyen.checkout.sessions.core.internal.provider.SessionPaymentComponentProvider +import com.adyen.checkout.sessions.core.internal.ui.model.SessionParamsFactory +import com.adyen.checkout.ui.core.internal.data.api.AddressService +import com.adyen.checkout.ui.core.internal.data.api.DefaultAddressRepository +import com.adyen.checkout.ui.core.internal.ui.SubmitHandler + +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +class BoletoComponentProvider( + overrideComponentParams: ComponentParams? = null, + overrideSessionParams: SessionParams? = null, +) : PaymentComponentProvider, + SessionPaymentComponentProvider { + + private val componentParamsMapper = BoletoComponentParamsMapper(overrideComponentParams, overrideSessionParams) + + override fun get( + savedStateRegistryOwner: SavedStateRegistryOwner, + viewModelStoreOwner: ViewModelStoreOwner, + lifecycleOwner: LifecycleOwner, + paymentMethod: PaymentMethod, + configuration: BoletoConfiguration, + application: Application, + componentCallback: ComponentCallback, + order: Order?, + key: String? + ): BoletoComponent { + assertSupported(paymentMethod) + + val boletoFactory = viewModelFactory(savedStateRegistryOwner, null) { savedStateHandle -> + val componentParams = componentParamsMapper.mapToParams(configuration, null) + val httpClient = HttpClientFactory.getHttpClient(componentParams.environment) + val analyticsService = AnalyticsService(httpClient) + val analyticsRepository = DefaultAnalyticsRepository( + packageName = application.packageName, + locale = componentParams.shopperLocale, + source = AnalyticsSource.PaymentComponent(componentParams.isCreatedByDropIn, paymentMethod), + analyticsService = analyticsService, + analyticsMapper = AnalyticsMapper(), + ) + + val addressService = AddressService(httpClient) + val addressRepository = DefaultAddressRepository(addressService) + + val boletoDelegate = DefaultBoletoDelegate( + submitHandler = SubmitHandler(savedStateHandle), + analyticsRepository = analyticsRepository, + observerRepository = PaymentObserverRepository(), + paymentMethod = paymentMethod, + order = order, + componentParams = componentParams, + addressRepository = addressRepository + ) + + val genericActionDelegate = GenericActionComponentProvider(componentParams).getDelegate( + configuration = configuration.genericActionConfiguration, + savedStateHandle = savedStateHandle, + application = application, + ) + + BoletoComponent( + boletoDelegate = boletoDelegate, + genericActionDelegate = genericActionDelegate, + actionHandlingComponent = DefaultActionHandlingComponent(genericActionDelegate, boletoDelegate), + componentEventHandler = DefaultComponentEventHandler(), + ) + } + + return ViewModelProvider(viewModelStoreOwner, boletoFactory)[key, BoletoComponent::class.java] + .also { component -> + component.observe(lifecycleOwner) { + component.componentEventHandler.onPaymentComponentEvent(it, componentCallback) + } + } + } + + @Suppress("LongMethod") + override fun get( + savedStateRegistryOwner: SavedStateRegistryOwner, + viewModelStoreOwner: ViewModelStoreOwner, + lifecycleOwner: LifecycleOwner, + checkoutSession: CheckoutSession, + paymentMethod: PaymentMethod, + configuration: BoletoConfiguration, + application: Application, + componentCallback: SessionComponentCallback, + key: String? + ): BoletoComponent { + assertSupported(paymentMethod) + + val genericFactory = viewModelFactory(savedStateRegistryOwner, null) { savedStateHandle -> + val componentParams = componentParamsMapper.mapToParams( + configuration = configuration, + sessionParams = SessionParamsFactory.create(checkoutSession), + ) + val httpClient = HttpClientFactory.getHttpClient(componentParams.environment) + val analyticsService = AnalyticsService(httpClient) + val analyticsRepository = DefaultAnalyticsRepository( + packageName = application.packageName, + locale = componentParams.shopperLocale, + source = AnalyticsSource.PaymentComponent(componentParams.isCreatedByDropIn, paymentMethod), + analyticsService = analyticsService, + analyticsMapper = AnalyticsMapper(), + ) + val addressService = AddressService(httpClient) + val addressRepository = DefaultAddressRepository(addressService) + + val boletoDelegate = DefaultBoletoDelegate( + submitHandler = SubmitHandler(savedStateHandle), + analyticsRepository = analyticsRepository, + observerRepository = PaymentObserverRepository(), + paymentMethod = paymentMethod, + order = checkoutSession.order, + componentParams = componentParams, + addressRepository = addressRepository + ) + + val genericActionDelegate = GenericActionComponentProvider(componentParams).getDelegate( + configuration = configuration.genericActionConfiguration, + savedStateHandle = savedStateHandle, + application = application, + ) + + val sessionSavedStateHandleContainer = SessionSavedStateHandleContainer( + savedStateHandle = savedStateHandle, + checkoutSession = checkoutSession, + ) + + val sessionInteractor = SessionInteractor( + sessionRepository = SessionRepository( + sessionService = SessionService(httpClient), + clientKey = componentParams.clientKey, + ), + sessionModel = sessionSavedStateHandleContainer.getSessionModel(), + isFlowTakenOver = sessionSavedStateHandleContainer.isFlowTakenOver ?: false + ) + + val sessionComponentEventHandler = + SessionComponentEventHandler( + sessionInteractor = sessionInteractor, + sessionSavedStateHandleContainer = sessionSavedStateHandleContainer, + ) + + BoletoComponent( + boletoDelegate = boletoDelegate, + genericActionDelegate = genericActionDelegate, + actionHandlingComponent = DefaultActionHandlingComponent(genericActionDelegate, boletoDelegate), + componentEventHandler = sessionComponentEventHandler, + ) + } + + return ViewModelProvider(viewModelStoreOwner, genericFactory)[key, BoletoComponent::class.java] + .also { component -> + component.observe(lifecycleOwner) { + component.componentEventHandler.onPaymentComponentEvent(it, componentCallback) + } + } + } + + private fun assertSupported(paymentMethod: PaymentMethod) { + if (!isPaymentMethodSupported(paymentMethod)) { + throw ComponentException("Unsupported payment method ${paymentMethod.type}") + } + } + + override fun isPaymentMethodSupported(paymentMethod: PaymentMethod): Boolean { + return BoletoComponent.PAYMENT_METHOD_TYPES.contains(paymentMethod.type) + } +} diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/PaymentMethodTypes.kt b/components-core/src/main/java/com/adyen/checkout/components/core/PaymentMethodTypes.kt index c5779105fd..cd6144b0e8 100644 --- a/components-core/src/main/java/com/adyen/checkout/components/core/PaymentMethodTypes.kt +++ b/components-core/src/main/java/com/adyen/checkout/components/core/PaymentMethodTypes.kt @@ -73,6 +73,7 @@ object PaymentMethodTypes { const val BOLETOBANCARIO_HSBC = "boletobancario_hsbc" const val BOLETOBANCARIO_ITAU = "boletobancario_itau" const val BOLETOBANCARIO_SANTANDER = "boletobancario_santander" + const val BOLETO_PRIMEIRO_PAY = "primeiropay_boleto" const val DRAGONPAY_EBANKING = "dragonpay_ebanking" const val DRAGONPAY_OTC_BANKING = "dragonpay_otc_banking" const val DRAGONPAY_OTC_NON_BANKING = "dragonpay_otc_non_banking" @@ -164,6 +165,7 @@ object PaymentMethodTypes { BOLETOBANCARIO_HSBC, BOLETOBANCARIO_ITAU, BOLETOBANCARIO_SANTANDER, + BOLETO_PRIMEIRO_PAY, DRAGONPAY_EBANKING, DRAGONPAY_OTC_BANKING, DRAGONPAY_OTC_NON_BANKING, From ad9028314dc75b3f0d4b530e0d1700921324a0eb Mon Sep 17 00:00:00 2001 From: Atef Etman Date: Fri, 31 Mar 2023 15:37:52 +0200 Subject: [PATCH 030/161] Add unit tests for Boleto COAND-366 --- .../checkout/boleto/BoletoComponentTest.kt | 152 ++++++ .../internal/ui/DefaultBoletoDelegateTest.kt | 509 ++++++++++++++++++ .../model/BoletoComponentParamsMapperTest.kt | 190 +++++++ .../util/BoletoValidationUtilsTest.kt | 73 +++ 4 files changed, 924 insertions(+) create mode 100644 boleto/src/test/java/com/adyen/checkout/boleto/BoletoComponentTest.kt create mode 100644 boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegateTest.kt create mode 100644 boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParamsMapperTest.kt create mode 100644 boleto/src/test/java/com/adyen/checkout/boleto/internal/util/BoletoValidationUtilsTest.kt diff --git a/boleto/src/test/java/com/adyen/checkout/boleto/BoletoComponentTest.kt b/boleto/src/test/java/com/adyen/checkout/boleto/BoletoComponentTest.kt new file mode 100644 index 0000000000..95552320b1 --- /dev/null +++ b/boleto/src/test/java/com/adyen/checkout/boleto/BoletoComponentTest.kt @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2023 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by atef on 31/3/2023. + */ + +package com.adyen.checkout.boleto + +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.viewModelScope +import com.adyen.checkout.action.internal.DefaultActionHandlingComponent +import com.adyen.checkout.action.internal.ui.GenericActionDelegate +import com.adyen.checkout.boleto.internal.ui.BoletoComponentViewType +import com.adyen.checkout.boleto.internal.ui.BoletoDelegate +import com.adyen.checkout.components.core.internal.ComponentEventHandler +import com.adyen.checkout.components.core.internal.PaymentComponentEvent +import com.adyen.checkout.core.AdyenLogger +import com.adyen.checkout.core.internal.util.Logger +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.test.TestComponentViewType +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.Mock +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@OptIn(ExperimentalCoroutinesApi::class) +@ExtendWith(MockitoExtension::class, TestDispatcherExtension::class) +internal class BoletoComponentTest( + @Mock private val boletoDelegate: BoletoDelegate, + @Mock private val genericActionDelegate: GenericActionDelegate, + @Mock private val actionHandlingComponent: DefaultActionHandlingComponent, + @Mock private val componentEventHandler: ComponentEventHandler, +) { + private lateinit var component: BoletoComponent + + @BeforeEach + fun beforeEach() { + whenever(boletoDelegate.viewFlow) doReturn MutableStateFlow(BoletoComponentViewType) + whenever(genericActionDelegate.viewFlow) doReturn MutableStateFlow(null) + + component = BoletoComponent( + boletoDelegate = boletoDelegate, + genericActionDelegate = genericActionDelegate, + actionHandlingComponent = actionHandlingComponent, + componentEventHandler = componentEventHandler, + ) + + AdyenLogger.setLogLevel(Logger.NONE) + } + + @Test + fun `when component is created then delegates are initialized`() { + verify(boletoDelegate).initialize(component.viewModelScope) + verify(genericActionDelegate).initialize(component.viewModelScope) + verify(componentEventHandler).initialize(component.viewModelScope) + } + + @Test + fun `when component is cleared then delegates are cleared`() { + component.invokeOnCleared() + + verify(boletoDelegate).onCleared() + verify(genericActionDelegate).onCleared() + verify(componentEventHandler).onCleared() + } + + @Test + fun `when observe is called then observe in delegates is called`() { + val lifecycleOwner = mock() + val callback: (PaymentComponentEvent) -> Unit = {} + + component.observe(lifecycleOwner, callback) + + verify(boletoDelegate).observe(lifecycleOwner, component.viewModelScope, callback) + verify(genericActionDelegate).observe(eq(lifecycleOwner), eq(component.viewModelScope), any()) + } + + @Test + fun `when removeObserver is called then removeObserver in delegates is called`() { + component.removeObserver() + + verify(boletoDelegate).removeObserver() + verify(genericActionDelegate).removeObserver() + } + + @Test + fun `when component is initialized then view flow should match boleto delegate view flow`() = runTest { + runCurrent() + Assertions.assertEquals(BoletoComponentViewType, component.viewFlow.first()) + } + + @Test + fun `when delegate view flow emits a value then component view flow should match that value`() = runTest { + val delegateViewFlow = MutableStateFlow(TestComponentViewType.VIEW_TYPE_1) + whenever(boletoDelegate.viewFlow) doReturn delegateViewFlow + component = BoletoComponent( + boletoDelegate, + genericActionDelegate, + actionHandlingComponent, + componentEventHandler + ) + + val viewTestFlow = component.viewFlow.test(testScheduler) + Assertions.assertEquals(TestComponentViewType.VIEW_TYPE_1, viewTestFlow.values.last()) + + delegateViewFlow.emit(TestComponentViewType.VIEW_TYPE_2) + Assertions.assertEquals(TestComponentViewType.VIEW_TYPE_2, viewTestFlow.values.last()) + + viewTestFlow.cancel() + } + + @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) + whenever(genericActionDelegate.viewFlow) doReturn actionDelegateViewFlow + component = BoletoComponent( + boletoDelegate, + genericActionDelegate, + actionHandlingComponent, + componentEventHandler + ) + + val viewTestFlow = component.viewFlow.test(testScheduler) + + // 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 + Assertions.assertEquals(BoletoComponentViewType, viewTestFlow.values.last()) + + actionDelegateViewFlow.emit(TestComponentViewType.VIEW_TYPE_2) + Assertions.assertEquals(TestComponentViewType.VIEW_TYPE_2, viewTestFlow.values.last()) + + viewTestFlow.cancel() + } +} diff --git a/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegateTest.kt b/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegateTest.kt new file mode 100644 index 0000000000..09408d986d --- /dev/null +++ b/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegateTest.kt @@ -0,0 +1,509 @@ +/* + * Copyright (c) 2023 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by atef on 31/3/2023. + */ + +package com.adyen.checkout.boleto.internal.ui + +import app.cash.turbine.test +import com.adyen.checkout.boleto.BoletoComponentState +import com.adyen.checkout.boleto.BoletoConfiguration +import com.adyen.checkout.boleto.internal.ui.model.BoletoComponentParamsMapper +import com.adyen.checkout.components.core.Order +import com.adyen.checkout.components.core.OrderRequest +import com.adyen.checkout.components.core.PaymentMethod +import com.adyen.checkout.components.core.internal.PaymentObserverRepository +import com.adyen.checkout.components.core.internal.data.api.AnalyticsRepository +import com.adyen.checkout.core.AdyenLogger +import com.adyen.checkout.core.Environment +import com.adyen.checkout.core.internal.util.Logger +import com.adyen.checkout.test.extensions.test +import com.adyen.checkout.ui.core.internal.test.TestAddressRepository +import com.adyen.checkout.ui.core.internal.ui.SubmitHandler +import com.adyen.checkout.ui.core.internal.ui.model.AddressInputModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.cancel +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.Mock +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.verify +import java.util.Locale + +@OptIn(ExperimentalCoroutinesApi::class) +@ExtendWith(MockitoExtension::class) +internal class DefaultBoletoDelegateTest( + @Mock private val submitHandler: SubmitHandler, + @Mock private val analyticsRepository: AnalyticsRepository, +) { + + private lateinit var delegate: DefaultBoletoDelegate + + private val configuration = BoletoConfiguration.Builder( + Locale.US, + Environment.TEST, + TEST_CLIENT_KEY + ).build() + + private lateinit var addressRepository: TestAddressRepository + + @BeforeEach + fun beforeEach() { + addressRepository = TestAddressRepository() + delegate = createBoletoDelegate() + AdyenLogger.setLogLevel(Logger.NONE) + } + + @Test + fun `when delegate is initialized then analytics event is sent`() = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + verify(analyticsRepository).sendAnalyticsEvent() + } + + @Nested + @DisplayName("when input data changes and") + inner class InputDataChangedTest { + + @Test + fun `input data is valid, then output must be valid`() = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + val outputTestFlow = delegate.outputDataFlow.test(testScheduler) + + delegate.updateInputData { + firstName = "Atef" + lastName = "Etman" + socialSecurityNumber = "568.617.525-09" + address = createAddressInputModel() + isSendEmailSelected = true + shopperEmail = "atef@test.com" + } + + assertTrue(outputTestFlow.latestValue.isValid) + outputTestFlow.cancel() + } + + @Test + fun `all inputs are empty, then output should be invalid`() = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + val outputTestFlow = delegate.outputDataFlow.test(testScheduler) + + delegate.updateInputData {} + + assertFalse(outputTestFlow.latestValue.isValid) + outputTestFlow.cancel() + } + + @Test + fun `first name is empty and other inputs are valid, then output should be invalid`() = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + val outputTestFlow = delegate.outputDataFlow.test(testScheduler) + + delegate.updateInputData { + firstName = " " + lastName = "Etman" + socialSecurityNumber = "568.617.525-09" + address = createAddressInputModel() + isSendEmailSelected = true + shopperEmail = "atef@test.com" + } + + assertFalse(outputTestFlow.latestValue.isValid) + outputTestFlow.cancel() + } + + @Test + fun `last name is empty and other inputs are valid, then output should be invalid`() = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + val outputTestFlow = delegate.outputDataFlow.test(testScheduler) + + delegate.updateInputData { + firstName = "Atef" + lastName = " " + socialSecurityNumber = "568.617.525-09" + address = createAddressInputModel() + isSendEmailSelected = true + shopperEmail = "atef@test.com" + } + + assertFalse(outputTestFlow.latestValue.isValid) + outputTestFlow.cancel() + } + + @Test + fun `social security number is empty and other inputs are valid, then output should be invalid`() = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + val outputTestFlow = delegate.outputDataFlow.test(testScheduler) + + delegate.updateInputData { + firstName = "Atef" + lastName = "Etman" + socialSecurityNumber = " " + address = createAddressInputModel() + isSendEmailSelected = true + shopperEmail = "atef@test.com" + } + + assertFalse(outputTestFlow.latestValue.isValid) + outputTestFlow.cancel() + } + + @Test + fun `social security number is invalid and other inputs are valid, then output should be invalid`() = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + val outputTestFlow = delegate.outputDataFlow.test(testScheduler) + + delegate.updateInputData { + firstName = "Atef" + lastName = "Etman" + socialSecurityNumber = "123.456.789-0" + address = createAddressInputModel() + isSendEmailSelected = true + shopperEmail = "atef@test.com" + } + + assertFalse(outputTestFlow.latestValue.isValid) + outputTestFlow.cancel() + } + + @Test + fun `address is empty and other inputs are valid, then output should be invalid`() = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + val outputTestFlow = delegate.outputDataFlow.test(testScheduler) + + delegate.updateInputData { + firstName = "Atef" + lastName = "Etman" + socialSecurityNumber = "568.617.525-09" + address = AddressInputModel() + isSendEmailSelected = true + shopperEmail = "atef@test.com" + } + + assertFalse(outputTestFlow.latestValue.isValid) + outputTestFlow.cancel() + } + + @Test + fun `email is empty and isEmailCopySelected equals true, then output should be invalid`() = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + val outputTestFlow = delegate.outputDataFlow.test(testScheduler) + + delegate.updateInputData { + firstName = "Atef" + lastName = "Etman" + socialSecurityNumber = "568.617.525-09" + address = AddressInputModel() + isSendEmailSelected = true + shopperEmail = " " + } + + assertFalse(outputTestFlow.latestValue.isValid) + outputTestFlow.cancel() + } + + @Test + fun `email is invalid and isEmailCopySelected equals true, then output should be invalid`() = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + + val outputTestFlow = delegate.outputDataFlow.test(testScheduler) + + delegate.updateInputData { + firstName = "Atef" + lastName = "Etman" + socialSecurityNumber = "568.617.525-09" + address = AddressInputModel() + isSendEmailSelected = true + shopperEmail = "atef@test" + } + + assertFalse(outputTestFlow.latestValue.isValid) + outputTestFlow.cancel() + } + } + + @Nested + @DisplayName("when creating component state and") + inner class CreateComponentStateTest { + + @Test + fun `output is valid, then component state should be valid`() = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + val componentStateTestFlow = delegate.componentStateFlow.test(testScheduler) + + delegate.updateInputData { + firstName = "Atef" + lastName = "Etman" + socialSecurityNumber = "568.617.525-09" + address = createAddressInputModel() + isSendEmailSelected = true + shopperEmail = "atef@test.com" + } + + with(componentStateTestFlow.latestValue) { + assertTrue(isInputValid) + assertTrue(isValid) + } + } + + @Test + fun `output is invalid, then component state should be invalid`() = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + val componentStateTestFlow = delegate.componentStateFlow.test(testScheduler) + + delegate.updateInputData {} + + with(componentStateTestFlow.latestValue) { + assertFalse(isInputValid) + assertFalse(isValid) + } + } + + @Test + fun `first name is empty and other inputs are valid, then component state should be invalid`() = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + val componentStateTestFlow = delegate.componentStateFlow.test(testScheduler) + + delegate.updateInputData { + firstName = " " + lastName = "Etman" + socialSecurityNumber = "568.617.525-09" + address = createAddressInputModel() + isSendEmailSelected = true + shopperEmail = "atef@test.com" + } + + with(componentStateTestFlow.latestValue) { + assertFalse(isInputValid) + assertFalse(isValid) + } + } + + @Test + fun `last name is empty and other inputs are valid, then component state should be invalid`() = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + val componentStateTestFlow = delegate.componentStateFlow.test(testScheduler) + + delegate.updateInputData { + firstName = "Atef" + lastName = " " + socialSecurityNumber = "568.617.525-09" + address = createAddressInputModel() + isSendEmailSelected = true + shopperEmail = "atef@test.com" + } + + with(componentStateTestFlow.latestValue) { + assertFalse(isInputValid) + assertFalse(isValid) + } + } + + @Test + fun `social security number is empty and other inputs are valid, then component state should be invalid`() = + runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + val componentStateTestFlow = delegate.componentStateFlow.test(testScheduler) + + delegate.updateInputData { + firstName = "Atef" + lastName = "Etman" + socialSecurityNumber = " " + address = createAddressInputModel() + isSendEmailSelected = true + shopperEmail = "atef@test.com" + } + + with(componentStateTestFlow.latestValue) { + assertFalse(isInputValid) + assertFalse(isValid) + } + } + + @Test + fun `social security number is invalid input and other inputs are valid, then component state should be invalid`() = + runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + val componentStateTestFlow = delegate.componentStateFlow.test(testScheduler) + + delegate.updateInputData { + firstName = "Atef" + lastName = "Etman" + socialSecurityNumber = "123.456.789-0" + address = createAddressInputModel() + isSendEmailSelected = true + shopperEmail = "atef@test.com" + } + + with(componentStateTestFlow.latestValue) { + assertFalse(isInputValid) + assertFalse(isValid) + } + } + + @Test + fun `social security number is invalid pattern and other inputs are valid, then component state should be invalid`() = + runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + val componentStateTestFlow = delegate.componentStateFlow.test(testScheduler) + + delegate.updateInputData { + firstName = "Atef" + lastName = "Etman" + socialSecurityNumber = "56861752509" + address = createAddressInputModel() + isSendEmailSelected = true + shopperEmail = "atef@test.com" + } + + with(componentStateTestFlow.latestValue) { + assertFalse(isInputValid) + assertFalse(isValid) + } + } + + @Test + fun `address is empty and other inputs are valid, then component state should be invalid`() = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + val componentStateTestFlow = delegate.componentStateFlow.test(testScheduler) + + delegate.updateInputData { + firstName = "Atef" + lastName = "Etman" + socialSecurityNumber = "568.617.525-09" + address = AddressInputModel() + isSendEmailSelected = true + shopperEmail = "atef@test.com" + } + + with(componentStateTestFlow.latestValue) { + assertFalse(isInputValid) + assertFalse(isValid) + } + } + + @Test + fun `email is empty and isEmailCopySelected equals true, then component state should be invalid`() = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + val componentStateTestFlow = delegate.componentStateFlow.test(testScheduler) + + delegate.updateInputData { + firstName = "Atef" + lastName = "Etman" + socialSecurityNumber = "568.617.525-09" + address = AddressInputModel() + isSendEmailSelected = true + shopperEmail = " " + } + + with(componentStateTestFlow.latestValue) { + assertFalse(isInputValid) + assertFalse(isValid) + } + } + + @Test + fun `email is invalid and isEmailCopySelected equals true, then component state should be invalid`() = runTest { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + val componentStateTestFlow = delegate.componentStateFlow.test(testScheduler) + + delegate.updateInputData { + firstName = "Atef" + lastName = "Etman" + socialSecurityNumber = "568.617.525-09" + address = AddressInputModel() + isSendEmailSelected = true + shopperEmail = "atef@test" + } + + with(componentStateTestFlow.latestValue) { + assertFalse(isInputValid) + assertFalse(isValid) + } + } + } + + @Nested + inner class SubmitHandlerTest { + @Test + fun `when delegate is initialized then submit handler event is initialized`() = runTest { + val coroutineScope = CoroutineScope(UnconfinedTestDispatcher()) + delegate.initialize(coroutineScope) + verify(submitHandler).initialize(coroutineScope, delegate.componentStateFlow) + } + + @Test + fun `when delegate setInteractionBlocked is called then submit handler setInteractionBlocked is called`() = + runTest { + delegate.setInteractionBlocked(true) + verify(submitHandler).setInteractionBlocked(true) + } + + @Test + fun `when delegate onSubmit is called then submit handler onSubmit is called`() = runTest { + delegate.componentStateFlow.test { + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + delegate.onSubmit() + verify(submitHandler).onSubmit(expectMostRecentItem()) + } + } + } + + private fun createBoletoDelegate( + submitHandler: SubmitHandler = this.submitHandler, + analyticsRepository: AnalyticsRepository = this.analyticsRepository, + paymentMethod: PaymentMethod = PaymentMethod(), + addressRepository: TestAddressRepository = this.addressRepository, + order: Order? = TEST_ORDER + ) = DefaultBoletoDelegate( + submitHandler = submitHandler, + analyticsRepository = analyticsRepository, + observerRepository = PaymentObserverRepository(), + paymentMethod = paymentMethod, + order = order, + componentParams = BoletoComponentParamsMapper(null, null).mapToParams(configuration, null), + addressRepository = addressRepository + ) + + private fun createAddressInputModel( + postalCode: String = "12345678", + street: String = "Rua Funcionarios", + stateOrProvince: String = "SP", + houseNumberOrName: String = "952", + apartmentSuite: String = "", + city: String = "São Paulo", + country: String = BRAZIL_COUNTRY_CODE + ) = AddressInputModel( + postalCode = postalCode, + street = street, + stateOrProvince = stateOrProvince, + houseNumberOrName = houseNumberOrName, + apartmentSuite = apartmentSuite, + city = city, + country = country + ) + + companion object { + private const val TEST_CLIENT_KEY = "test_qwertyuiopasdfghjklzxcvbnmqwerty" + private val TEST_ORDER = OrderRequest("PSP", "ORDER_DATA") + private const val BRAZIL_COUNTRY_CODE = "BR" + } +} diff --git a/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParamsMapperTest.kt b/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParamsMapperTest.kt new file mode 100644 index 0000000000..59c704258a --- /dev/null +++ b/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParamsMapperTest.kt @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2023 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by atef on 31/3/2023. + */ + +package com.adyen.checkout.boleto.internal.ui.model + +import com.adyen.checkout.boleto.BoletoConfiguration +import com.adyen.checkout.components.core.Amount +import com.adyen.checkout.components.core.internal.ui.model.ComponentParams +import com.adyen.checkout.components.core.internal.ui.model.GenericComponentParams +import com.adyen.checkout.components.core.internal.ui.model.SessionParams +import com.adyen.checkout.core.Environment +import com.adyen.checkout.ui.core.internal.ui.model.AddressParams +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import java.util.Locale + +internal class BoletoComponentParamsMapperTest { + + @Test + fun `when parent configuration is null and custom boleto configuration fields are null, them all fields should match`() { + val boletoConfiguration = getBoletoConfigurationBuilder().build() + + val params = getBoletoComponentParamsMapper().mapToParams(boletoConfiguration, null) + val expected = getBoletoComponentParams() + + assertEquals(expected, params) + } + + @Test + fun `when parent configuration is null and custom fields are set then all fields should match`() { + val boletoConfiguration = getBoletoConfigurationBuilder() + .setIsSendEmailFieldVisibility(true) + .build() + + val params = getBoletoComponentParamsMapper().mapToParams(boletoConfiguration, null) + val expectedAddressParams = AddressParams.FullAddress( + defaultCountryCode = BRAZIL_COUNTRY_CODE, + supportedCountryCodes = SUPPORTED_COUNTRY_LIST_1, + addressFieldPolicy = AddressFieldPolicyParams.Required + ) + val expected = getBoletoComponentParams( + addressParams = expectedAddressParams, + isSendEmailVisible = true + ) + + assertEquals(expected, params) + } + + @Test + fun `when parent configuration is set then parent configuration should override Boleto configuration fields`() { + val boletoConfiguration = getBoletoConfigurationBuilder().build() + + val overrideComponentParams = GenericComponentParams( + shopperLocale = Locale.GERMAN, + environment = Environment.EUROPE, + clientKey = TEST_CLIENT_KEY_2, + isAnalyticsEnabled = false, + isCreatedByDropIn = true, + amount = Amount( + currency = "CAD", + value = 123_00L + ) + ) + + val params = getBoletoComponentParamsMapper(overrideComponentParams = overrideComponentParams).mapToParams( + boletoConfiguration, + null + ) + + val expected = getBoletoComponentParams( + shopperLocale = Locale.GERMAN, + environment = Environment.EUROPE, + clientKey = TEST_CLIENT_KEY_2, + isAnalyticsEnabled = false, + isCreatedByDropIn = true, + amount = Amount( + currency = "CAD", + value = 123_00L + ) + ) + + assertEquals(expected, params) + } + + @Test + fun `when send email is set, them params should match`() { + val boletoConfiguration = getBoletoConfigurationBuilder() + .setIsSendEmailFieldVisibility(true) + .build() + + val params = getBoletoComponentParamsMapper().mapToParams(boletoConfiguration, null) + val expected = getBoletoComponentParams( + isSendEmailVisible = true + ) + + assertEquals(expected, params) + } + + @ParameterizedTest + @MethodSource("amountSource") + fun `amount should match value set in sessions if it exists, then should match drop in value, then configuration`( + configurationValue: Amount, + dropInValue: Amount?, + sessionsValue: Amount?, + expectedValue: Amount + ) { + val boletoConfiguration = getBoletoConfigurationBuilder() + .setAmount(configurationValue) + .build() + + // this is in practice DropInComponentParams, but we don't have access to it in this module and any + // ComponentParams class can work + val overrideParams = dropInValue?.let { getBoletoComponentParams(amount = it) } + + val params = getBoletoComponentParamsMapper(overrideComponentParams = overrideParams).mapToParams( + boletoConfiguration, + sessionParams = SessionParams( + enableStoreDetails = null, + installmentOptions = null, + amount = sessionsValue + ) + ) + + val expected = getBoletoComponentParams( + amount = expectedValue + ) + + assertEquals(expected, params) + } + + private fun getBoletoConfigurationBuilder() = BoletoConfiguration.Builder( + shopperLocale = Locale.US, + environment = Environment.TEST, + clientKey = TEST_CLIENT_KEY_1, + ) + + private fun getBoletoComponentParams( + isSubmitButtonVisible: Boolean = true, + shopperLocale: Locale = Locale.US, + environment: Environment = Environment.TEST, + clientKey: String = TEST_CLIENT_KEY_1, + isAnalyticsEnabled: Boolean = true, + isCreatedByDropIn: Boolean = false, + amount: Amount = Amount.EMPTY, + addressParams: AddressParams = AddressParams.FullAddress( + defaultCountryCode = BRAZIL_COUNTRY_CODE, + supportedCountryCodes = SUPPORTED_COUNTRY_LIST_1, + addressFieldPolicy = AddressFieldPolicyParams.Required + ), + isSendEmailVisible: Boolean = false + ) = BoletoComponentParams( + isSubmitButtonVisible = isSubmitButtonVisible, + shopperLocale = shopperLocale, + environment = environment, + clientKey = clientKey, + isAnalyticsEnabled = isAnalyticsEnabled, + isCreatedByDropIn = isCreatedByDropIn, + amount = amount, + addressParams = addressParams, + isSendEmailVisible = isSendEmailVisible + ) + + private fun getBoletoComponentParamsMapper( + overrideComponentParams: ComponentParams? = null, + overrideSessionParams: SessionParams? = null, + ) = BoletoComponentParamsMapper(overrideComponentParams, overrideSessionParams) + + companion object { + private const val TEST_CLIENT_KEY_1 = "test_qwertyuiopasdfghjklzxcvbnmqwerty" + private const val TEST_CLIENT_KEY_2 = "live_qwertyui34566776787zxcvbnmqwerty" + private const val BRAZIL_COUNTRY_CODE = "BR" + private val SUPPORTED_COUNTRY_LIST_1 = listOf(BRAZIL_COUNTRY_CODE) + + @JvmStatic + fun amountSource() = listOf( + // configurationValue, dropInValue, sessionsValue, expectedValue + Arguments.arguments(Amount("EUR", 100), Amount("USD", 200), Amount("CAD", 300), Amount("CAD", 300)), + Arguments.arguments(Amount("EUR", 100), Amount("USD", 200), null, Amount("USD", 200)), + Arguments.arguments(Amount("EUR", 100), null, null, Amount("EUR", 100)), + ) + } +} diff --git a/boleto/src/test/java/com/adyen/checkout/boleto/internal/util/BoletoValidationUtilsTest.kt b/boleto/src/test/java/com/adyen/checkout/boleto/internal/util/BoletoValidationUtilsTest.kt new file mode 100644 index 0000000000..2ebd20584a --- /dev/null +++ b/boleto/src/test/java/com/adyen/checkout/boleto/internal/util/BoletoValidationUtilsTest.kt @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2023 Adyen N.V. + * + * This file is open source and available under the MIT license. See the LICENSE file for more info. + * + * Created by atef on 4/4/2023. + */ + +package com.adyen.checkout.boleto.internal.util + +import com.adyen.checkout.boleto.R +import com.adyen.checkout.components.core.internal.ui.model.Validation +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource + +internal class BoletoValidationUtilsTest { + + @ParameterizedTest + @MethodSource("firstNameSource") + fun `first name value is set, then validation should match expected validation`( + firstName: String, + expectedValidation: Validation + ) { + assertEquals(expectedValidation, BoletoValidationUtils.validateFirstName(firstName).validation) + } + + @ParameterizedTest + @MethodSource("lastNameSource") + fun `last name value is set, then validation should match expected validation`( + lastName: String, + expectedValidation: Validation + ) { + assertEquals(expectedValidation, BoletoValidationUtils.validateLastName(lastName).validation) + } + + @ParameterizedTest + @MethodSource("emailSource") + fun `email and isEmailEnabled value is set, then actual validation should match expected validation`( + isEmailEnabled: Boolean, + email: String, + expectedValidation: Validation + ) { + assertEquals(expectedValidation, BoletoValidationUtils.validateShopperEmail(isEmailEnabled, email).validation) + } + + companion object { + @JvmStatic + fun firstNameSource() = listOf( + // firstName, expected validation + Arguments.arguments("firstname", Validation.Valid), + Arguments.arguments("", Validation.Invalid(reason = R.string.checkout_boleto_first_name_invalid)), + ) + + @JvmStatic + fun lastNameSource() = listOf( + // lastName, expected validation + Arguments.arguments("firstname", Validation.Valid), + Arguments.arguments("", Validation.Invalid(reason = R.string.checkout_boleto_last_name_invalid)), + ) + + @JvmStatic + fun emailSource() = listOf( + // isEmailEnabled, email, expected validation + Arguments.arguments(false, "email", Validation.Valid), + Arguments.arguments(false, "email@tezt.com", Validation.Valid), + Arguments.arguments(true, "", Validation.Invalid(reason = R.string.checkout_boleto_email_invalid)), + Arguments.arguments(true, "email", Validation.Invalid(reason = R.string.checkout_boleto_email_invalid)), + Arguments.arguments(true, "email@test.com", Validation.Valid) + ) + } +} From aa0972fe6becde78a6c8de28df54d98f2770f748 Mon Sep 17 00:00:00 2001 From: Atef Etman Date: Fri, 31 Mar 2023 15:46:55 +0200 Subject: [PATCH 031/161] Add boleto to drop in COAND-366 --- drop-in/build.gradle | 1 + .../checkout/dropin/DropInConfiguration.kt | 15 +++++++++++++++ .../provider/ComponentParsingProvider.kt | 19 +++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/drop-in/build.gradle b/drop-in/build.gradle index 85668e1935..e80974ae14 100644 --- a/drop-in/build.gradle +++ b/drop-in/build.gradle @@ -51,6 +51,7 @@ dependencies { api project(':action') api project(':bacs') api project(':bcmc') + api project(':boleto') api project(':blik') api project(':card') api project(':convenience-stores-jp') diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/DropInConfiguration.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/DropInConfiguration.kt index 167a79f3b7..7f0b18419f 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/DropInConfiguration.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/DropInConfiguration.kt @@ -16,6 +16,7 @@ import com.adyen.checkout.action.internal.ActionHandlingPaymentMethodConfigurati import com.adyen.checkout.bacs.BacsDirectDebitConfiguration import com.adyen.checkout.bcmc.BcmcConfiguration import com.adyen.checkout.blik.BlikConfiguration +import com.adyen.checkout.boleto.BoletoConfiguration import com.adyen.checkout.card.CardConfiguration import com.adyen.checkout.components.core.Amount import com.adyen.checkout.components.core.PaymentMethodTypes @@ -351,6 +352,20 @@ class DropInConfiguration private constructor( return this } + /** + * Add configuration for Boleto payment method. + */ + fun addBoletoDirectDebitConfiguration(boletoConfiguration: BoletoConfiguration): Builder { + availablePaymentConfigs[PaymentMethodTypes.BOLETOBANCARIO] = boletoConfiguration + availablePaymentConfigs[PaymentMethodTypes.BOLETOBANCARIO_BANCODOBRASIL] = boletoConfiguration + availablePaymentConfigs[PaymentMethodTypes.BOLETOBANCARIO_BRADESCO] = boletoConfiguration + availablePaymentConfigs[PaymentMethodTypes.BOLETOBANCARIO_HSBC] = boletoConfiguration + availablePaymentConfigs[PaymentMethodTypes.BOLETOBANCARIO_ITAU] = boletoConfiguration + availablePaymentConfigs[PaymentMethodTypes.BOLETOBANCARIO_SANTANDER] = boletoConfiguration + availablePaymentConfigs[PaymentMethodTypes.BOLETO_PRIMEIRO_PAY] = boletoConfiguration + return this + } + override fun buildInternal(): DropInConfiguration { return DropInConfiguration( shopperLocale = shopperLocale, diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/provider/ComponentParsingProvider.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/provider/ComponentParsingProvider.kt index cb052f04f8..86bafac781 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/internal/provider/ComponentParsingProvider.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/internal/provider/ComponentParsingProvider.kt @@ -28,6 +28,10 @@ import com.adyen.checkout.blik.BlikComponent import com.adyen.checkout.blik.BlikComponentState import com.adyen.checkout.blik.BlikConfiguration import com.adyen.checkout.blik.internal.provider.BlikComponentProvider +import com.adyen.checkout.boleto.BoletoComponent +import com.adyen.checkout.boleto.BoletoComponentState +import com.adyen.checkout.boleto.BoletoConfiguration +import com.adyen.checkout.boleto.internal.provider.BoletoComponentProvider import com.adyen.checkout.card.CardComponent import com.adyen.checkout.card.CardComponentState import com.adyen.checkout.card.CardConfiguration @@ -223,6 +227,11 @@ internal fun getDefaultConfigForPaymentMethod( environment = environment, clientKey = clientKey ) + BoletoComponent.PROVIDER.isPaymentMethodSupported(paymentMethod) -> BoletoConfiguration.Builder( + shopperLocale = shopperLocale, + environment = environment, + clientKey = clientKey + ) CardComponent.PROVIDER.isPaymentMethodSupported(paymentMethod) -> CardConfiguration.Builder( shopperLocale = shopperLocale, environment = environment, @@ -515,6 +524,16 @@ internal fun getComponentFor( callback = componentCallback as ComponentCallback, ) } + BoletoComponent.PROVIDER.isPaymentMethodSupported(paymentMethod) -> { + val boletoConfiguration: BoletoConfiguration = + getConfigurationForPaymentMethod(paymentMethod, dropInConfiguration) + BoletoComponentProvider(dropInParams, sessionParams).get( + fragment = fragment, + paymentMethod = paymentMethod, + configuration = boletoConfiguration, + callback = componentCallback as ComponentCallback, + ) + } CardComponent.PROVIDER.isPaymentMethodSupported(paymentMethod) -> { val cardConfig: CardConfiguration = getConfigurationForPaymentMethod(paymentMethod, dropInConfiguration) From 7bb4ad0fbdc373e73dce23be99ac71946692bd14 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Tue, 18 Apr 2023 16:50:24 +0200 Subject: [PATCH 032/161] Use namespace instead of manifest package declaration COAND-366 --- boleto/build.gradle | 1 + boleto/src/main/AndroidManifest.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/boleto/build.gradle b/boleto/build.gradle index 201811abba..fb06ae0363 100644 --- a/boleto/build.gradle +++ b/boleto/build.gradle @@ -19,6 +19,7 @@ ext.mavenArtifactDescription = "Adyen checkout Boleto component client for Adyen apply from: "${rootDir}/config/gradle/sharedTasks.gradle" android { + namespace 'com.adyen.checkout.boleto' compileSdkVersion compile_sdk_version defaultConfig { diff --git a/boleto/src/main/AndroidManifest.xml b/boleto/src/main/AndroidManifest.xml index 902aff78ec..722102298a 100644 --- a/boleto/src/main/AndroidManifest.xml +++ b/boleto/src/main/AndroidManifest.xml @@ -6,4 +6,4 @@ ~ Created by atef on 31/3/2023. --> - + From 64b556881f13d905b759dd3e1347590bc915db99 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Tue, 18 Apr 2023 17:12:11 +0200 Subject: [PATCH 033/161] Pass amount to component data COAND-366 --- .../adyen/checkout/boleto/internal/ui/DefaultBoletoDelegate.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegate.kt b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegate.kt index 0435e994c9..183c2ed252 100644 --- a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegate.kt +++ b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegate.kt @@ -23,6 +23,7 @@ import com.adyen.checkout.components.core.ShopperName import com.adyen.checkout.components.core.internal.PaymentComponentEvent import com.adyen.checkout.components.core.internal.PaymentObserverRepository import com.adyen.checkout.components.core.internal.data.api.AnalyticsRepository +import com.adyen.checkout.components.core.internal.util.isEmpty import com.adyen.checkout.components.core.paymentmethod.GenericPaymentMethod import com.adyen.checkout.core.internal.util.LogUtil import com.adyen.checkout.core.internal.util.Logger @@ -234,6 +235,7 @@ internal class DefaultBoletoDelegate( val paymentComponentData = PaymentComponentData( paymentMethod = GenericPaymentMethod(paymentMethod.type), order = order, + amount = componentParams.amount.takeUnless { it.isEmpty }, socialSecurityNumber = outputData.socialSecurityNumberState.value, shopperName = ShopperName( firstName = outputData.firstNameState.value, From d341d7bf471af3aa4fb3c3cb68bc99b97ce3d246 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 19 Apr 2023 15:16:24 +0200 Subject: [PATCH 034/161] Make email configuration work correctly COAND-366 --- .../checkout/boleto/BoletoConfiguration.kt | 14 ++++++------- .../internal/ui/DefaultBoletoDelegate.kt | 1 + .../ui/model/BoletoComponentParams.kt | 2 +- .../ui/model/BoletoComponentParamsMapper.kt | 2 +- .../internal/ui/model/BoletoOutputData.kt | 1 + .../boleto/internal/ui/view/BoletoView.kt | 20 +++++++++++++------ .../model/BoletoComponentParamsMapperTest.kt | 6 +++--- .../ui/core/internal/util/ViewExtensions.kt | 1 + 8 files changed, 29 insertions(+), 18 deletions(-) diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/BoletoConfiguration.kt b/boleto/src/main/java/com/adyen/checkout/boleto/BoletoConfiguration.kt index 7a50e1b38d..8e58c2bd7e 100644 --- a/boleto/src/main/java/com/adyen/checkout/boleto/BoletoConfiguration.kt +++ b/boleto/src/main/java/com/adyen/checkout/boleto/BoletoConfiguration.kt @@ -32,7 +32,7 @@ class BoletoConfiguration private constructor( override val amount: Amount, override val isSubmitButtonVisible: Boolean?, val genericActionConfiguration: GenericActionConfiguration, - val isSendEmailVisible: Boolean? + val isEmailVisible: Boolean? ) : Configuration, ButtonConfiguration { /** @@ -42,7 +42,7 @@ class BoletoConfiguration private constructor( ActionHandlingPaymentMethodConfigurationBuilder, ButtonConfigurationBuilder { private var isSubmitButtonVisible: Boolean? = null - private var isSendEmailVisible: Boolean? = null + private var isEmailVisible: Boolean? = null /** * Constructor for Builder with default values. @@ -83,13 +83,13 @@ class BoletoConfiguration private constructor( } /** - * Sets the visibility of send email copy filed + * Sets the visibility of the "send email copy"-switch and email input field. * * Default value is false - * @param isSendEmailVisible + * @param isEmailVisible */ - fun setIsSendEmailFieldVisibility(isSendEmailVisible: Boolean): Builder { - this.isSendEmailVisible = isSendEmailVisible + fun setEmailVisibility(isEmailVisible: Boolean): Builder { + this.isEmailVisible = isEmailVisible return this } @@ -101,7 +101,7 @@ class BoletoConfiguration private constructor( amount = amount, isSubmitButtonVisible = isSubmitButtonVisible, genericActionConfiguration = genericActionConfigurationBuilder.build(), - isSendEmailVisible = isSendEmailVisible + isEmailVisible = isEmailVisible ) } } diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegate.kt b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegate.kt index 183c2ed252..8b87aa3558 100644 --- a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegate.kt +++ b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegate.kt @@ -214,6 +214,7 @@ internal class DefaultBoletoDelegate( false ), addressUIState = addressFormUIState, + isEmailVisible = componentParams.isEmailVisible, isSendEmailSelected = inputData.isSendEmailSelected, shopperEmailState = BoletoValidationUtils.validateShopperEmail( inputData.isSendEmailSelected, diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParams.kt b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParams.kt index 3eb2668923..166cfb2cea 100644 --- a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParams.kt +++ b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParams.kt @@ -26,5 +26,5 @@ internal data class BoletoComponentParams( override val isCreatedByDropIn: Boolean, override val amount: Amount, val addressParams: AddressParams, - val isSendEmailVisible: Boolean + val isEmailVisible: Boolean, ) : ComponentParams, ButtonParams diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParamsMapper.kt b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParamsMapper.kt index 891392032b..6c07cffd06 100644 --- a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParamsMapper.kt +++ b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParamsMapper.kt @@ -42,7 +42,7 @@ internal class BoletoComponentParamsMapper( supportedCountryCodes = DEFAULT_SUPPORTED_COUNTRY_LIST, addressFieldPolicy = AddressFieldPolicyParams.Required ), - isSendEmailVisible = isSendEmailVisible ?: false + isEmailVisible = isEmailVisible ?: false ) } diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoOutputData.kt b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoOutputData.kt index 46b9fe992c..f3ef7d9f75 100644 --- a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoOutputData.kt +++ b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/model/BoletoOutputData.kt @@ -19,6 +19,7 @@ internal data class BoletoOutputData( val socialSecurityNumberState: FieldState, val addressState: AddressOutputData, val addressUIState: AddressFormUIState, + val isEmailVisible: Boolean, val isSendEmailSelected: Boolean, val shopperEmailState: FieldState ) : OutputData { diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/view/BoletoView.kt b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/view/BoletoView.kt index af85a86d28..6f5c4680e3 100644 --- a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/view/BoletoView.kt +++ b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/view/BoletoView.kt @@ -60,11 +60,7 @@ internal class BoletoView @JvmOverloads constructor( initLastNameInput() initSocialSecurityNumberInput() initAddressFormInput(coroutineScope) - initShopperEmailInput() - binding.switchSendEmailCopy.setOnCheckedChangeListener { _, isChecked -> - delegate.updateInputData { isSendEmailSelected = isChecked } - binding.textInputLayoutShopperEmail.isVisible = isChecked - } + initEmail(delegate.outputData.isEmailVisible) } private fun initLocalizedStrings(localizedContext: Context) { @@ -146,7 +142,18 @@ internal class BoletoView @JvmOverloads constructor( binding.addressFormInput.attachDelegate(boletoDelegate, coroutineScope) } - private fun initShopperEmailInput() { + private fun initEmail(isEmailVisible: Boolean) { + binding.switchSendEmailCopy.isVisible = isEmailVisible + if (isEmailVisible) { + binding.switchSendEmailCopy.setOnCheckedChangeListener { _, isChecked -> + boletoDelegate.updateInputData { isSendEmailSelected = isChecked } + binding.textInputLayoutShopperEmail.isVisible = isChecked + } + initEmailInput() + } + } + + private fun initEmailInput() { binding.editTextShopperEmail.setOnChangeListener { boletoDelegate.updateInputData { shopperEmail = it.toString().trim() } binding.textInputLayoutShopperEmail.hideError() @@ -196,6 +203,7 @@ internal class BoletoView @JvmOverloads constructor( val shopperEmailValidation = it.shopperEmailState.validation if (shopperEmailValidation is Validation.Invalid && binding.textInputLayoutShopperEmail.isVisible) { if (!isErrorFocused) { + @Suppress("UNUSED_VALUE") isErrorFocused = true binding.textInputLayoutShopperEmail.requestFocus() } diff --git a/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParamsMapperTest.kt b/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParamsMapperTest.kt index 59c704258a..7e97734411 100644 --- a/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParamsMapperTest.kt +++ b/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParamsMapperTest.kt @@ -37,7 +37,7 @@ internal class BoletoComponentParamsMapperTest { @Test fun `when parent configuration is null and custom fields are set then all fields should match`() { val boletoConfiguration = getBoletoConfigurationBuilder() - .setIsSendEmailFieldVisibility(true) + .setEmailVisibility(true) .build() val params = getBoletoComponentParamsMapper().mapToParams(boletoConfiguration, null) @@ -93,7 +93,7 @@ internal class BoletoComponentParamsMapperTest { @Test fun `when send email is set, them params should match`() { val boletoConfiguration = getBoletoConfigurationBuilder() - .setIsSendEmailFieldVisibility(true) + .setEmailVisibility(true) .build() val params = getBoletoComponentParamsMapper().mapToParams(boletoConfiguration, null) @@ -165,7 +165,7 @@ internal class BoletoComponentParamsMapperTest { isCreatedByDropIn = isCreatedByDropIn, amount = amount, addressParams = addressParams, - isSendEmailVisible = isSendEmailVisible + isEmailVisible = isSendEmailVisible ) private fun getBoletoComponentParamsMapper( diff --git a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/util/ViewExtensions.kt b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/util/ViewExtensions.kt index c3d2d9604d..9fd61eaa6a 100644 --- a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/util/ViewExtensions.kt +++ b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/util/ViewExtensions.kt @@ -21,6 +21,7 @@ inline var TextInputLayout.isVisible: Boolean this.visibility = visibility editText?.apply { this.visibility = visibility + isFocusable = value } } From 7f66c57ef4dfbd67cded84d9ed8fa6c70ba188af Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 19 Apr 2023 15:20:25 +0200 Subject: [PATCH 035/161] Remove direct debit from boleto naming COAND-366 --- .../adyen/checkout/dropin/DropInConfiguration.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drop-in/src/main/java/com/adyen/checkout/dropin/DropInConfiguration.kt b/drop-in/src/main/java/com/adyen/checkout/dropin/DropInConfiguration.kt index 7f0b18419f..388948203d 100644 --- a/drop-in/src/main/java/com/adyen/checkout/dropin/DropInConfiguration.kt +++ b/drop-in/src/main/java/com/adyen/checkout/dropin/DropInConfiguration.kt @@ -91,12 +91,12 @@ class DropInConfiguration private constructor( /** * Create a [DropInConfiguration] * - * @param context A context + * @param shopperLocale The [Locale] of the shopper. * @param environment The [Environment] to be used for internal network calls from the SDK to Adyen. * @param clientKey Your Client Key used for internal network calls from the SDK to Adyen. */ - constructor(context: Context, environment: Environment, clientKey: String) : super( - context, + constructor(shopperLocale: Locale, environment: Environment, clientKey: String) : super( + shopperLocale, environment, clientKey ) @@ -104,12 +104,12 @@ class DropInConfiguration private constructor( /** * Alternative constructor that uses the [context] to fetch the user locale and use it as a shopper locale. * - * @param shopperLocale The [Locale] of the shopper. + * @param context A context * @param environment The [Environment] to be used for internal network calls from the SDK to Adyen. * @param clientKey Your Client Key used for internal network calls from the SDK to Adyen. */ - constructor(shopperLocale: Locale, environment: Environment, clientKey: String) : super( - shopperLocale, + constructor(context: Context, environment: Environment, clientKey: String) : super( + context, environment, clientKey ) @@ -355,7 +355,7 @@ class DropInConfiguration private constructor( /** * Add configuration for Boleto payment method. */ - fun addBoletoDirectDebitConfiguration(boletoConfiguration: BoletoConfiguration): Builder { + fun addBoletoConfiguration(boletoConfiguration: BoletoConfiguration): Builder { availablePaymentConfigs[PaymentMethodTypes.BOLETOBANCARIO] = boletoConfiguration availablePaymentConfigs[PaymentMethodTypes.BOLETOBANCARIO_BANCODOBRASIL] = boletoConfiguration availablePaymentConfigs[PaymentMethodTypes.BOLETOBANCARIO_BRADESCO] = boletoConfiguration From 8a014bf3788c3c4e1e83fa748df66abbeaa47993 Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 19 Apr 2023 15:39:17 +0200 Subject: [PATCH 036/161] Improve email input UX and fix focus issue COAND-366 --- .../checkout/boleto/internal/ui/view/BoletoView.kt | 11 ++++++++++- .../checkout/ui/core/internal/util/ViewExtensions.kt | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/view/BoletoView.kt b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/view/BoletoView.kt index 6f5c4680e3..5c6be9967a 100644 --- a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/view/BoletoView.kt +++ b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/view/BoletoView.kt @@ -23,10 +23,12 @@ import com.adyen.checkout.components.core.internal.ui.ComponentDelegate import com.adyen.checkout.components.core.internal.ui.model.Validation import com.adyen.checkout.ui.core.internal.ui.ComponentView import com.adyen.checkout.ui.core.internal.util.hideError +import com.adyen.checkout.ui.core.internal.util.hideKeyboard import com.adyen.checkout.ui.core.internal.util.isVisible import com.adyen.checkout.ui.core.internal.util.setLocalizedHintFromStyle import com.adyen.checkout.ui.core.internal.util.setLocalizedTextFromStyle import com.adyen.checkout.ui.core.internal.util.showError +import com.adyen.checkout.ui.core.internal.util.showKeyboard import kotlinx.coroutines.CoroutineScope @Suppress("TooManyFunctions") @@ -146,8 +148,15 @@ internal class BoletoView @JvmOverloads constructor( binding.switchSendEmailCopy.isVisible = isEmailVisible if (isEmailVisible) { binding.switchSendEmailCopy.setOnCheckedChangeListener { _, isChecked -> - boletoDelegate.updateInputData { isSendEmailSelected = isChecked } binding.textInputLayoutShopperEmail.isVisible = isChecked + if (isChecked) { + binding.editTextShopperEmail.requestFocus() + binding.editTextShopperEmail.showKeyboard() + } else { + binding.editTextShopperEmail.clearFocus() + hideKeyboard() + } + boletoDelegate.updateInputData { isSendEmailSelected = isChecked } } initEmailInput() } diff --git a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/util/ViewExtensions.kt b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/util/ViewExtensions.kt index 9fd61eaa6a..6693b902d3 100644 --- a/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/util/ViewExtensions.kt +++ b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/util/ViewExtensions.kt @@ -22,6 +22,7 @@ inline var TextInputLayout.isVisible: Boolean editText?.apply { this.visibility = visibility isFocusable = value + isFocusableInTouchMode = value } } From 25da4b20a6a1e24b32e24321b4326280cb1a586d Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Wed, 19 Apr 2023 15:48:33 +0200 Subject: [PATCH 037/161] Update BoletoConfiguration.Builder kdocs COAND-366 --- .../adyen/checkout/boleto/BoletoConfiguration.kt | 16 ++++++++-------- .../boleto/internal/ui/view/BoletoView.kt | 1 + .../internal/ui/DefaultBoletoDelegateTest.kt | 1 + .../ui/model/BoletoComponentParamsMapperTest.kt | 1 + 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/BoletoConfiguration.kt b/boleto/src/main/java/com/adyen/checkout/boleto/BoletoConfiguration.kt index 8e58c2bd7e..92ac3204a8 100644 --- a/boleto/src/main/java/com/adyen/checkout/boleto/BoletoConfiguration.kt +++ b/boleto/src/main/java/com/adyen/checkout/boleto/BoletoConfiguration.kt @@ -45,11 +45,11 @@ class BoletoConfiguration private constructor( private var isEmailVisible: Boolean? = null /** - * Constructor for Builder with default values. + * Alternative constructor that uses the [context] to fetch the user locale and use it as a shopper locale. * - * @param context A context - * @param environment The [Environment] to be used for network calls to Adyen. - * @param clientKey Your Client Key used for network calls from the SDK to Adyen. + * @param context A Context + * @param environment The [Environment] to be used for internal network calls from the SDK to Adyen. + * @param clientKey Your Client Key used for internal network calls from the SDK to Adyen. */ constructor(context: Context, environment: Environment, clientKey: String) : super( context, @@ -58,11 +58,11 @@ class BoletoConfiguration private constructor( ) /** - * Builder with required parameters. + * Builder with parameters for a [BoletoConfiguration]. * - * @param shopperLocale The Locale of the shopper. - * @param environment The [Environment] to be used for network calls to Adyen. - * @param clientKey Your Client Key used for network calls from the SDK to Adyen. + * @param shopperLocale The [Locale] of the shopper. + * @param environment The [Environment] to be used for internal network calls from the SDK to Adyen. + * @param clientKey Your Client Key used for internal network calls from the SDK to Adyen. */ constructor(shopperLocale: Locale, environment: Environment, clientKey: String) : super( shopperLocale, diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/view/BoletoView.kt b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/view/BoletoView.kt index 5c6be9967a..db2501d9a8 100644 --- a/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/view/BoletoView.kt +++ b/boleto/src/main/java/com/adyen/checkout/boleto/internal/ui/view/BoletoView.kt @@ -177,6 +177,7 @@ internal class BoletoView @JvmOverloads constructor( } } + @Suppress("CyclomaticComplexMethod") override fun highlightValidationErrors() { boletoDelegate.outputData.let { var isErrorFocused = false diff --git a/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegateTest.kt b/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegateTest.kt index 09408d986d..08bd8a9044 100644 --- a/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegateTest.kt +++ b/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegateTest.kt @@ -483,6 +483,7 @@ internal class DefaultBoletoDelegateTest( addressRepository = addressRepository ) + @Suppress("LongParameterList") private fun createAddressInputModel( postalCode: String = "12345678", street: String = "Rua Funcionarios", diff --git a/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParamsMapperTest.kt b/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParamsMapperTest.kt index 7e97734411..75059d8c84 100644 --- a/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParamsMapperTest.kt +++ b/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/model/BoletoComponentParamsMapperTest.kt @@ -142,6 +142,7 @@ internal class BoletoComponentParamsMapperTest { clientKey = TEST_CLIENT_KEY_1, ) + @Suppress("LongParameterList") private fun getBoletoComponentParams( isSubmitButtonVisible: Boolean = true, shopperLocale: Locale = Locale.US, From 88962f6a661bbf813cff353d21dee04af91ed4dc Mon Sep 17 00:00:00 2001 From: Oscar Spruit Date: Fri, 21 Apr 2023 10:34:27 +0200 Subject: [PATCH 038/161] Add test for amount propagation COAND-366 --- .../provider/BoletoComponentProvider.kt | 13 ++++- .../internal/ui/DefaultBoletoDelegateTest.kt | 49 ++++++++++++++++--- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/boleto/src/main/java/com/adyen/checkout/boleto/internal/provider/BoletoComponentProvider.kt b/boleto/src/main/java/com/adyen/checkout/boleto/internal/provider/BoletoComponentProvider.kt index a26143478f..cea27c88e7 100644 --- a/boleto/src/main/java/com/adyen/checkout/boleto/internal/provider/BoletoComponentProvider.kt +++ b/boleto/src/main/java/com/adyen/checkout/boleto/internal/provider/BoletoComponentProvider.kt @@ -54,8 +54,17 @@ import com.adyen.checkout.ui.core.internal.ui.SubmitHandler class BoletoComponentProvider( overrideComponentParams: ComponentParams? = null, overrideSessionParams: SessionParams? = null, -) : PaymentComponentProvider, - SessionPaymentComponentProvider { +) : + PaymentComponentProvider< + BoletoComponent, + BoletoConfiguration, + BoletoComponentState, + ComponentCallback>, + SessionPaymentComponentProvider< + BoletoComponent, + BoletoConfiguration, + BoletoComponentState, + SessionComponentCallback> { private val componentParamsMapper = BoletoComponentParamsMapper(overrideComponentParams, overrideSessionParams) diff --git a/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegateTest.kt b/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegateTest.kt index 08bd8a9044..b96b056c93 100644 --- a/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegateTest.kt +++ b/boleto/src/test/java/com/adyen/checkout/boleto/internal/ui/DefaultBoletoDelegateTest.kt @@ -12,6 +12,7 @@ import app.cash.turbine.test import com.adyen.checkout.boleto.BoletoComponentState import com.adyen.checkout.boleto.BoletoConfiguration import com.adyen.checkout.boleto.internal.ui.model.BoletoComponentParamsMapper +import com.adyen.checkout.components.core.Amount import com.adyen.checkout.components.core.Order import com.adyen.checkout.components.core.OrderRequest import com.adyen.checkout.components.core.PaymentMethod @@ -29,6 +30,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.cancel import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach @@ -36,6 +38,9 @@ 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 +import org.junit.jupiter.params.provider.MethodSource import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.kotlin.verify @@ -50,12 +55,6 @@ internal class DefaultBoletoDelegateTest( private lateinit var delegate: DefaultBoletoDelegate - private val configuration = BoletoConfiguration.Builder( - Locale.US, - Environment.TEST, - TEST_CLIENT_KEY - ).build() - private lateinit var addressRepository: TestAddressRepository @BeforeEach @@ -439,6 +438,27 @@ internal class DefaultBoletoDelegateTest( assertFalse(isValid) } } + + @ParameterizedTest + @MethodSource("com.adyen.checkout.boleto.internal.ui.DefaultBoletoDelegateTest#amountSource") + fun `when input data is valid then amount is propagated in component state if set`( + configurationValue: Amount?, + expectedComponentStateValue: Amount?, + ) = runTest { + if (configurationValue != null) { + val configuration = getDefaultBoletoConfigurationBuilder() + .setAmount(configurationValue) + .build() + delegate = createBoletoDelegate(configuration = configuration) + } + delegate.initialize(CoroutineScope(UnconfinedTestDispatcher())) + delegate.componentStateFlow.test { + delegate.updateInputData { + firstName = "Test" + } + assertEquals(expectedComponentStateValue, expectMostRecentItem().data.amount) + } + } } @Nested @@ -467,12 +487,14 @@ internal class DefaultBoletoDelegateTest( } } + @Suppress("LongParameterList") private fun createBoletoDelegate( submitHandler: SubmitHandler = this.submitHandler, analyticsRepository: AnalyticsRepository = this.analyticsRepository, paymentMethod: PaymentMethod = PaymentMethod(), addressRepository: TestAddressRepository = this.addressRepository, - order: Order? = TEST_ORDER + order: Order? = TEST_ORDER, + configuration: BoletoConfiguration = getDefaultBoletoConfigurationBuilder().build(), ) = DefaultBoletoDelegate( submitHandler = submitHandler, analyticsRepository = analyticsRepository, @@ -502,9 +524,22 @@ internal class DefaultBoletoDelegateTest( country = country ) + private fun getDefaultBoletoConfigurationBuilder(): BoletoConfiguration.Builder { + return BoletoConfiguration.Builder(Locale.US, Environment.TEST, TEST_CLIENT_KEY) + } + companion object { private const val TEST_CLIENT_KEY = "test_qwertyuiopasdfghjklzxcvbnmqwerty" private val TEST_ORDER = OrderRequest("PSP", "ORDER_DATA") private const val BRAZIL_COUNTRY_CODE = "BR" + + @JvmStatic + fun amountSource() = listOf( + // configurationValue, expectedComponentStateValue + Arguments.arguments(Amount("EUR", 100), Amount("EUR", 100)), + Arguments.arguments(Amount("USD", 0), Amount("USD", 0)), + Arguments.arguments(Amount.EMPTY, null), + Arguments.arguments(null, null), + ) } } From 7af1d3aa48cfb6d10b0097e33f71d1362fedc100 Mon Sep 17 00:00:00 2001 From: Atef Etman Date: Mon, 3 Apr 2023 18:30:10 +0200 Subject: [PATCH 039/161] Add support to boleto payment methods COAND-366 --- .../checkout/components/core/PaymentMethodTypes.kt | 14 +++++++------- .../internal/provider/VoucherComponentProvider.kt | 11 ++++++++++- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/PaymentMethodTypes.kt b/components-core/src/main/java/com/adyen/checkout/components/core/PaymentMethodTypes.kt index cd6144b0e8..deea6ee1c0 100644 --- a/components-core/src/main/java/com/adyen/checkout/components/core/PaymentMethodTypes.kt +++ b/components-core/src/main/java/com/adyen/checkout/components/core/PaymentMethodTypes.kt @@ -95,6 +95,13 @@ object PaymentMethodTypes { listOf( ACH, BCMC, + BOLETOBANCARIO, + BOLETOBANCARIO_BANCODOBRASIL, + BOLETOBANCARIO_BRADESCO, + BOLETOBANCARIO_HSBC, + BOLETOBANCARIO_ITAU, + BOLETOBANCARIO_SANTANDER, + BOLETO_PRIMEIRO_PAY, DUIT_NOW, DOTPAY, ENTERCASH, @@ -159,13 +166,6 @@ object PaymentMethodTypes { ECONTEXT_ONLINE, ECONTEXT_SEVEN_ELEVEN, ECONTEXT_STORES, - BOLETOBANCARIO, - BOLETOBANCARIO_BANCODOBRASIL, - BOLETOBANCARIO_BRADESCO, - BOLETOBANCARIO_HSBC, - BOLETOBANCARIO_ITAU, - BOLETOBANCARIO_SANTANDER, - BOLETO_PRIMEIRO_PAY, DRAGONPAY_EBANKING, DRAGONPAY_OTC_BANKING, DRAGONPAY_OTC_NON_BANKING, diff --git a/voucher/src/main/java/com/adyen/checkout/voucher/internal/provider/VoucherComponentProvider.kt b/voucher/src/main/java/com/adyen/checkout/voucher/internal/provider/VoucherComponentProvider.kt index 4d194fa285..c552011476 100644 --- a/voucher/src/main/java/com/adyen/checkout/voucher/internal/provider/VoucherComponentProvider.kt +++ b/voucher/src/main/java/com/adyen/checkout/voucher/internal/provider/VoucherComponentProvider.kt @@ -83,6 +83,15 @@ class VoucherComponentProvider( } companion object { - private val PAYMENT_METHODS = listOf(PaymentMethodTypes.BACS) + private val PAYMENT_METHODS = listOf( + PaymentMethodTypes.BACS, + PaymentMethodTypes.BOLETOBANCARIO, + PaymentMethodTypes.BOLETOBANCARIO_BANCODOBRASIL, + PaymentMethodTypes.BOLETOBANCARIO_BRADESCO, + PaymentMethodTypes.BOLETOBANCARIO_HSBC, + PaymentMethodTypes.BOLETOBANCARIO_ITAU, + PaymentMethodTypes.BOLETOBANCARIO_SANTANDER, + PaymentMethodTypes.BOLETO_PRIMEIRO_PAY + ) } } From 48f4697f27ba1be11f949bddde66e04f1f260d3b Mon Sep 17 00:00:00 2001 From: Atef Etman Date: Mon, 3 Apr 2023 18:30:36 +0200 Subject: [PATCH 040/161] Add downloadUrl property to VoucherAction COAND-366 --- .../adyen/checkout/components/core/action/VoucherAction.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/action/VoucherAction.kt b/components-core/src/main/java/com/adyen/checkout/components/core/action/VoucherAction.kt index 83dc0f414c..ae7dc811d9 100644 --- a/components-core/src/main/java/com/adyen/checkout/components/core/action/VoucherAction.kt +++ b/components-core/src/main/java/com/adyen/checkout/components/core/action/VoucherAction.kt @@ -30,7 +30,9 @@ data class VoucherAction( var reference: String? = null, var alternativeReference: String? = null, var merchantName: String? = null, + // TODO: remove url when it's fixed from backend side var url: String? = null, + var downloadUrl: String? = null ) : Action() { companion object { @@ -44,6 +46,7 @@ data class VoucherAction( private const val ALTERNATIVE_REFERENCE = "alternativeReference" private const val MERCHANT_NAME = "merchantName" private const val URL = "url" + private const val DOWNLOAD_URL = "downloadUrl" @JvmField val SERIALIZER: Serializer = object : Serializer { @@ -62,6 +65,7 @@ data class VoucherAction( putOpt(ALTERNATIVE_REFERENCE, modelObject.alternativeReference) putOpt(MERCHANT_NAME, modelObject.merchantName) putOpt(URL, modelObject.url) + putOpt(DOWNLOAD_URL, modelObject.downloadUrl) } } catch (e: JSONException) { throw ModelSerializationException(VoucherAction::class.java, e) @@ -82,6 +86,7 @@ data class VoucherAction( alternativeReference = jsonObject.getStringOrNull(ALTERNATIVE_REFERENCE), merchantName = jsonObject.getStringOrNull(MERCHANT_NAME), url = jsonObject.getStringOrNull(URL), + downloadUrl = jsonObject.getStringOrNull(DOWNLOAD_URL), ) } } From e375bcfea74dc48af6fbb6510e98fd8e8ea50a27 Mon Sep 17 00:00:00 2001 From: Atef Etman Date: Mon, 3 Apr 2023 18:31:13 +0200 Subject: [PATCH 041/161] Add expiration date format util method COAND-366 --- .../core/internal/util/DateUtils.kt | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/components-core/src/main/java/com/adyen/checkout/components/core/internal/util/DateUtils.kt b/components-core/src/main/java/com/adyen/checkout/components/core/internal/util/DateUtils.kt index b84f859dd1..fb02b04b3a 100644 --- a/components-core/src/main/java/com/adyen/checkout/components/core/internal/util/DateUtils.kt +++ b/components-core/src/main/java/com/adyen/checkout/components/core/internal/util/DateUtils.kt @@ -10,6 +10,7 @@ package com.adyen.checkout.components.core.internal.util import androidx.annotation.RestrictTo import com.adyen.checkout.core.internal.util.Logger +import java.text.DateFormat import java.text.ParseException import java.text.SimpleDateFormat import java.util.Calendar @@ -17,6 +18,7 @@ import java.util.Locale @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) object DateUtils { + private const val DEFAULT_INPUT_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss" @JvmStatic fun parseDateToView(month: String, year: String): String { @@ -50,4 +52,27 @@ object DateUtils { false } } + + /** + * Format server date pattern to regular date pattern (30/03/2023). + * + * @param date date value coming from server + * @param shopperLocale + * @param inputFormat server date pattern + */ + fun formatStringDate( + date: String, + shopperLocale: Locale, + inputFormat: String = DEFAULT_INPUT_DATE_FORMAT + ): String? { + return try { + val inputSimpleFormat = SimpleDateFormat(inputFormat, shopperLocale) + val outputSimpleFormat = DateFormat.getDateInstance(DateFormat.SHORT, shopperLocale) + val parsedDate = inputSimpleFormat.parse(date) + parsedDate?.let { outputSimpleFormat.format(it) } + } catch (e: ParseException) { + Logger.e("DateUtil", "Provided date $date does not match the given format $inputFormat") + null + } + } } From 7193361e3ae643a2cf1fa6e8a012be119de29f06 Mon Sep 17 00:00:00 2001 From: Atef Etman Date: Mon, 3 Apr 2023 18:32:09 +0200 Subject: [PATCH 042/161] Move PdfOpener to ui-core module COAND-366 --- .../internal/provider/OnlineBankingComponentProvider.kt | 2 +- .../internal/ui/DefaultOnlineBankingDelegate.kt | 2 +- .../internal/ui/DefaultOnlineBankingDelegateTest.kt | 2 +- .../com/adyen/checkout/ui/core}/internal/util/PdfOpener.kt | 3 +-- 4 files changed, 4 insertions(+), 5 deletions(-) rename {online-banking-core/src/main/java/com/adyen/checkout/onlinebankingcore => ui-core/src/main/java/com/adyen/checkout/ui/core}/internal/util/PdfOpener.kt (96%) diff --git a/online-banking-core/src/main/java/com/adyen/checkout/onlinebankingcore/internal/provider/OnlineBankingComponentProvider.kt b/online-banking-core/src/main/java/com/adyen/checkout/onlinebankingcore/internal/provider/OnlineBankingComponentProvider.kt index 4dc0f43d35..3e9bb685ad 100644 --- a/online-banking-core/src/main/java/com/adyen/checkout/onlinebankingcore/internal/provider/OnlineBankingComponentProvider.kt +++ b/online-banking-core/src/main/java/com/adyen/checkout/onlinebankingcore/internal/provider/OnlineBankingComponentProvider.kt @@ -42,7 +42,6 @@ import com.adyen.checkout.onlinebankingcore.internal.OnlineBankingComponent import com.adyen.checkout.onlinebankingcore.internal.OnlineBankingConfiguration import com.adyen.checkout.onlinebankingcore.internal.ui.DefaultOnlineBankingDelegate import com.adyen.checkout.onlinebankingcore.internal.ui.OnlineBankingDelegate -import com.adyen.checkout.onlinebankingcore.internal.util.PdfOpener import com.adyen.checkout.sessions.core.CheckoutSession import com.adyen.checkout.sessions.core.SessionComponentCallback import com.adyen.checkout.sessions.core.internal.SessionComponentEventHandler @@ -53,6 +52,7 @@ import com.adyen.checkout.sessions.core.internal.data.api.SessionService import com.adyen.checkout.sessions.core.internal.provider.SessionPaymentComponentProvider import com.adyen.checkout.sessions.core.internal.ui.model.SessionParamsFactory import com.adyen.checkout.ui.core.internal.ui.SubmitHandler +import com.adyen.checkout.ui.core.internal.util.PdfOpener @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) abstract class OnlineBankingComponentProvider< diff --git a/online-banking-core/src/main/java/com/adyen/checkout/onlinebankingcore/internal/ui/DefaultOnlineBankingDelegate.kt b/online-banking-core/src/main/java/com/adyen/checkout/onlinebankingcore/internal/ui/DefaultOnlineBankingDelegate.kt index 2c35ca82f0..8440e39154 100644 --- a/online-banking-core/src/main/java/com/adyen/checkout/onlinebankingcore/internal/ui/DefaultOnlineBankingDelegate.kt +++ b/online-banking-core/src/main/java/com/adyen/checkout/onlinebankingcore/internal/ui/DefaultOnlineBankingDelegate.kt @@ -29,7 +29,6 @@ import com.adyen.checkout.core.internal.util.Logger import com.adyen.checkout.onlinebankingcore.internal.ui.model.OnlineBankingInputData import com.adyen.checkout.onlinebankingcore.internal.ui.model.OnlineBankingModel import com.adyen.checkout.onlinebankingcore.internal.ui.model.OnlineBankingOutputData -import com.adyen.checkout.onlinebankingcore.internal.util.PdfOpener import com.adyen.checkout.onlinebankingcore.internal.util.getLegacyIssuers import com.adyen.checkout.onlinebankingcore.internal.util.mapToModel import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType @@ -37,6 +36,7 @@ import com.adyen.checkout.ui.core.internal.ui.ComponentViewType import com.adyen.checkout.ui.core.internal.ui.PaymentComponentUIEvent import com.adyen.checkout.ui.core.internal.ui.PaymentComponentUIState import com.adyen.checkout.ui.core.internal.ui.SubmitHandler +import com.adyen.checkout.ui.core.internal.util.PdfOpener import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow diff --git a/online-banking-core/src/test/java/com/adyen/checkout/onlinebankingcore/internal/ui/DefaultOnlineBankingDelegateTest.kt b/online-banking-core/src/test/java/com/adyen/checkout/onlinebankingcore/internal/ui/DefaultOnlineBankingDelegateTest.kt index 93cf5b4e2e..575298c058 100644 --- a/online-banking-core/src/test/java/com/adyen/checkout/onlinebankingcore/internal/ui/DefaultOnlineBankingDelegateTest.kt +++ b/online-banking-core/src/test/java/com/adyen/checkout/onlinebankingcore/internal/ui/DefaultOnlineBankingDelegateTest.kt @@ -19,11 +19,11 @@ import com.adyen.checkout.components.core.internal.ui.model.ButtonComponentParam import com.adyen.checkout.core.Environment import com.adyen.checkout.onlinebankingcore.internal.ui.model.OnlineBankingModel import com.adyen.checkout.onlinebankingcore.internal.ui.model.OnlineBankingOutputData -import com.adyen.checkout.onlinebankingcore.internal.util.PdfOpener import com.adyen.checkout.onlinebankingcore.utils.TestOnlineBankingComponentState import com.adyen.checkout.onlinebankingcore.utils.TestOnlineBankingConfiguration import com.adyen.checkout.onlinebankingcore.utils.TestOnlineBankingPaymentMethod import com.adyen.checkout.ui.core.internal.ui.SubmitHandler +import com.adyen.checkout.ui.core.internal.util.PdfOpener import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.UnconfinedTestDispatcher diff --git a/online-banking-core/src/main/java/com/adyen/checkout/onlinebankingcore/internal/util/PdfOpener.kt b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/util/PdfOpener.kt similarity index 96% rename from online-banking-core/src/main/java/com/adyen/checkout/onlinebankingcore/internal/util/PdfOpener.kt rename to ui-core/src/main/java/com/adyen/checkout/ui/core/internal/util/PdfOpener.kt index adda7ad9df..54e5632c1f 100644 --- a/online-banking-core/src/main/java/com/adyen/checkout/onlinebankingcore/internal/util/PdfOpener.kt +++ b/ui-core/src/main/java/com/adyen/checkout/ui/core/internal/util/PdfOpener.kt @@ -6,7 +6,7 @@ * Created by atef on 20/9/2022. */ -package com.adyen.checkout.onlinebankingcore.internal.util +package com.adyen.checkout.ui.core.internal.util import android.content.ActivityNotFoundException import android.content.Context @@ -18,7 +18,6 @@ import androidx.browser.customtabs.CustomTabColorSchemeParams import androidx.browser.customtabs.CustomTabsIntent import com.adyen.checkout.core.internal.util.LogUtil import com.adyen.checkout.core.internal.util.Logger -import com.adyen.checkout.ui.core.internal.util.ThemeUtil @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) class PdfOpener { From c4b427b831d89ca375868926f6bd6eb375979689 Mon Sep 17 00:00:00 2001 From: Atef Etman Date: Mon, 3 Apr 2023 18:33:29 +0200 Subject: [PATCH 043/161] Add downloadVoucher method to VoucherDelegate COAND-366 --- .../provider/VoucherComponentProvider.kt | 7 +++- .../internal/ui/DefaultVoucherDelegate.kt | 11 ++++++ .../voucher/internal/ui/VoucherDelegate.kt | 5 ++- .../voucher/internal/ui/view/VoucherView.kt | 13 +------ .../internal/ui/DefaultVoucherDelegateTest.kt | 35 ++++++++++++++++--- 5 files changed, 52 insertions(+), 19 deletions(-) diff --git a/voucher/src/main/java/com/adyen/checkout/voucher/internal/provider/VoucherComponentProvider.kt b/voucher/src/main/java/com/adyen/checkout/voucher/internal/provider/VoucherComponentProvider.kt index c552011476..349e45d593 100644 --- a/voucher/src/main/java/com/adyen/checkout/voucher/internal/provider/VoucherComponentProvider.kt +++ b/voucher/src/main/java/com/adyen/checkout/voucher/internal/provider/VoucherComponentProvider.kt @@ -27,6 +27,7 @@ import com.adyen.checkout.components.core.internal.ui.model.GenericComponentPara import com.adyen.checkout.components.core.internal.ui.model.SessionParams import com.adyen.checkout.components.core.internal.util.get import com.adyen.checkout.components.core.internal.util.viewModelFactory +import com.adyen.checkout.ui.core.internal.util.PdfOpener import com.adyen.checkout.voucher.VoucherComponent import com.adyen.checkout.voucher.VoucherConfiguration import com.adyen.checkout.voucher.internal.ui.DefaultVoucherDelegate @@ -68,7 +69,11 @@ class VoucherComponentProvider( application: Application, ): VoucherDelegate { val componentParams = componentParamsMapper.mapToParams(configuration, null) - return DefaultVoucherDelegate(ActionObserverRepository(), componentParams) + return DefaultVoucherDelegate( + observerRepository = ActionObserverRepository(), + componentParams = componentParams, + pdfOpener = PdfOpener() + ) } override val supportedActionTypes: List diff --git a/voucher/src/main/java/com/adyen/checkout/voucher/internal/ui/DefaultVoucherDelegate.kt b/voucher/src/main/java/com/adyen/checkout/voucher/internal/ui/DefaultVoucherDelegate.kt index 9cb100ba1e..cf4b9bd815 100644 --- a/voucher/src/main/java/com/adyen/checkout/voucher/internal/ui/DefaultVoucherDelegate.kt +++ b/voucher/src/main/java/com/adyen/checkout/voucher/internal/ui/DefaultVoucherDelegate.kt @@ -9,6 +9,7 @@ package com.adyen.checkout.voucher.internal.ui import android.app.Activity +import android.content.Context import androidx.lifecycle.LifecycleOwner import com.adyen.checkout.components.core.action.Action import com.adyen.checkout.components.core.action.VoucherAction @@ -19,6 +20,7 @@ import com.adyen.checkout.components.core.internal.util.bufferedChannel import com.adyen.checkout.core.exception.CheckoutException import com.adyen.checkout.core.exception.ComponentException import com.adyen.checkout.ui.core.internal.ui.ComponentViewType +import com.adyen.checkout.ui.core.internal.util.PdfOpener import com.adyen.checkout.voucher.internal.ui.model.VoucherOutputData import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel @@ -29,6 +31,7 @@ import kotlinx.coroutines.flow.receiveAsFlow internal class DefaultVoucherDelegate( private val observerRepository: ActionObserverRepository, override val componentParams: GenericComponentParams, + private val pdfOpener: PdfOpener, ) : VoucherDelegate { private val _outputDataFlow = MutableStateFlow(createOutputData()) @@ -84,6 +87,14 @@ internal class DefaultVoucherDelegate( downloadUrl = null ) + override fun downloadVoucher(context: Context) { + try { + pdfOpener.open(context, outputData.downloadUrl ?: "") + } catch (e: IllegalStateException) { + exceptionChannel.trySend(CheckoutException(e.message ?: "", e.cause)) + } + } + override fun onCleared() { removeObserver() } diff --git a/voucher/src/main/java/com/adyen/checkout/voucher/internal/ui/VoucherDelegate.kt b/voucher/src/main/java/com/adyen/checkout/voucher/internal/ui/VoucherDelegate.kt index 1a4ace2df1..f74362517c 100644 --- a/voucher/src/main/java/com/adyen/checkout/voucher/internal/ui/VoucherDelegate.kt +++ b/voucher/src/main/java/com/adyen/checkout/voucher/internal/ui/VoucherDelegate.kt @@ -8,6 +8,7 @@ package com.adyen.checkout.voucher.internal.ui +import android.content.Context import androidx.annotation.RestrictTo import com.adyen.checkout.components.core.internal.ui.ActionDelegate import com.adyen.checkout.components.core.internal.ui.ViewableDelegate @@ -18,4 +19,6 @@ import com.adyen.checkout.voucher.internal.ui.model.VoucherOutputData interface VoucherDelegate : ActionDelegate, ViewableDelegate, - ViewProvidingDelegate + ViewProvidingDelegate { + fun downloadVoucher(context: Context) +} diff --git a/voucher/src/main/java/com/adyen/checkout/voucher/internal/ui/view/VoucherView.kt b/voucher/src/main/java/com/adyen/checkout/voucher/internal/ui/view/VoucherView.kt index f4b96e4873..0ef886f0cf 100644 --- a/voucher/src/main/java/com/adyen/checkout/voucher/internal/ui/view/VoucherView.kt +++ b/voucher/src/main/java/com/adyen/checkout/voucher/internal/ui/view/VoucherView.kt @@ -9,19 +9,16 @@ package com.adyen.checkout.voucher.internal.ui.view import android.content.Context -import android.net.Uri import android.util.AttributeSet import android.view.LayoutInflater import android.view.View import android.widget.LinearLayout -import androidx.browser.customtabs.CustomTabsIntent import com.adyen.checkout.components.core.internal.ui.ComponentDelegate import com.adyen.checkout.core.internal.util.LogUtil import com.adyen.checkout.core.internal.util.Logger import com.adyen.checkout.ui.core.internal.ui.ComponentView import com.adyen.checkout.ui.core.internal.ui.LogoSize import com.adyen.checkout.ui.core.internal.ui.loadLogo -import com.adyen.checkout.ui.core.internal.util.ThemeUtil import com.adyen.checkout.ui.core.internal.util.setLocalizedTextFromStyle import com.adyen.checkout.voucher.R import com.adyen.checkout.voucher.databinding.VoucherViewBinding @@ -65,7 +62,7 @@ internal class VoucherView @JvmOverloads constructor( observeDelegate(delegate, coroutineScope) - binding.textViewDownload.setOnClickListener { launchDownloadIntent(delegate.outputData.downloadUrl) } + binding.textViewDownload.setOnClickListener { delegate.downloadVoucher(context) } } private fun initLocalizedStrings(localizedContext: Context) { @@ -100,14 +97,6 @@ internal class VoucherView @JvmOverloads constructor( } } - private fun launchDownloadIntent(url: String?) { - CustomTabsIntent.Builder() - .setShowTitle(true) - .setToolbarColor(ThemeUtil.getPrimaryThemeColor(context)) - .build() - .launchUrl(context, Uri.parse(url)) - } - override fun highlightValidationErrors() { // No validation required } diff --git a/voucher/src/test/java/com/adyen/checkout/voucher/internal/ui/DefaultVoucherDelegateTest.kt b/voucher/src/test/java/com/adyen/checkout/voucher/internal/ui/DefaultVoucherDelegateTest.kt index 810b43491b..71bc5e8931 100644 --- a/voucher/src/test/java/com/adyen/checkout/voucher/internal/ui/DefaultVoucherDelegateTest.kt +++ b/voucher/src/test/java/com/adyen/checkout/voucher/internal/ui/DefaultVoucherDelegateTest.kt @@ -9,21 +9,32 @@ package com.adyen.checkout.voucher.internal.ui import android.app.Activity +import android.content.Context import app.cash.turbine.test import com.adyen.checkout.components.core.action.VoucherAction import com.adyen.checkout.components.core.internal.ActionObserverRepository import com.adyen.checkout.components.core.internal.ui.model.GenericComponentParamsMapper import com.adyen.checkout.core.Environment +import com.adyen.checkout.ui.core.internal.util.PdfOpener import com.adyen.checkout.voucher.VoucherConfiguration import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.Mock +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.verify import java.util.Locale @OptIn(ExperimentalCoroutinesApi::class) -internal class DefaultVoucherDelegateTest { +@ExtendWith(MockitoExtension::class) +internal class DefaultVoucherDelegateTest( + @Mock private val pdfOpener: PdfOpener, + @Mock private val context: Context, + @Mock private val activity: Activity +) { private lateinit var delegate: DefaultVoucherDelegate @@ -33,6 +44,7 @@ internal class DefaultVoucherDelegateTest { delegate = DefaultVoucherDelegate( ActionObserverRepository(), GenericComponentParamsMapper(null, null).mapToParams(configuration, null), + pdfOpener ) } @@ -45,18 +57,31 @@ internal class DefaultVoucherDelegateTest { url = "download_url", paymentData = "paymentData", ), - Activity(), + activity, ) - skipItems(1) - - with(awaitItem()) { + with(expectMostRecentItem()) { assertEquals("payment_method_type", paymentMethodType) assertEquals("download_url", downloadUrl) } } } + @Test + fun `when download voucher is called, then pdf open should be called`() { + delegate.handleAction( + VoucherAction( + paymentMethodType = "payment_method_type", + url = "download_url", + paymentData = "paymentData", + ), + activity, + ) + delegate.downloadVoucher(context) + + verify(pdfOpener).open(context, "download_url") + } + companion object { private const val TEST_CLIENT_KEY = "test_qwertyuiopasdfghjklzxcvbnmqwerty" } From 43ef146299327e7d7d0c8bce9e3298128e843f8b Mon Sep 17 00:00:00 2001 From: Atef Etman Date: Mon, 3 Apr 2023 18:36:31 +0200 Subject: [PATCH 044/161] Add strings for Boleto voucher view COAND-366 --- voucher/src/main/res/template/values/strings.xml.tt | 7 ++++++- voucher/src/main/res/values-ar/strings.xml | 5 +++++ voucher/src/main/res/values-cs-rCZ/strings.xml | 5 +++++ voucher/src/main/res/values-da-rDK/strings.xml | 5 +++++ voucher/src/main/res/values-de-rDE/strings.xml | 5 +++++ voucher/src/main/res/values-el-rGR/strings.xml | 5 +++++ voucher/src/main/res/values-es-rES/strings.xml | 5 +++++ voucher/src/main/res/values-fi-rFI/strings.xml | 5 +++++ voucher/src/main/res/values-fr-rFR/strings.xml | 5 +++++ voucher/src/main/res/values-hr-rHR/strings.xml | 5 +++++ voucher/src/main/res/values-hu-rHU/strings.xml | 5 +++++ voucher/src/main/res/values-it-rIT/strings.xml | 5 +++++ voucher/src/main/res/values-ja-rJP/strings.xml | 5 +++++ voucher/src/main/res/values-ko-rKR/strings.xml | 5 +++++ voucher/src/main/res/values-nb-rNO/strings.xml | 5 +++++ voucher/src/main/res/values-nl-rNL/strings.xml | 5 +++++ voucher/src/main/res/values-pl-rPL/strings.xml | 5 +++++ voucher/src/main/res/values-pt-rBR/strings.xml | 5 +++++ voucher/src/main/res/values-pt-rPT/strings.xml | 5 +++++ voucher/src/main/res/values-ro-rRO/strings.xml | 5 +++++ voucher/src/main/res/values-ru-rRU/strings.xml | 5 +++++ voucher/src/main/res/values-sk-rSK/strings.xml | 5 +++++ voucher/src/main/res/values-sl-rSI/strings.xml | 5 +++++ voucher/src/main/res/values-sv-rSE/strings.xml | 5 +++++ voucher/src/main/res/values-zh-rCN/strings.xml | 5 +++++ voucher/src/main/res/values-zh-rTW/strings.xml | 5 +++++ voucher/src/main/res/values/strings.xml | 5 +++++ 27 files changed, 136 insertions(+), 1 deletion(-) diff --git a/voucher/src/main/res/template/values/strings.xml.tt b/voucher/src/main/res/template/values/strings.xml.tt index 945da45451..7e5bdd9343 100644 --- a/voucher/src/main/res/template/values/strings.xml.tt +++ b/voucher/src/main/res/template/values/strings.xml.tt @@ -9,6 +9,11 @@ %%bacs.result.introduction%% + %%voucher.introduction%% + %%voucher.paymentReferenceLabel%% + %%voucher.expirationDate%% + %%pix.instructions.copyButton%% + %%pix.instructions.copiedMessage%% %%download.pdf%% %%voucher.finish%% - \ No newline at end of file + diff --git a/voucher/src/main/res/values-ar/strings.xml b/voucher/src/main/res/values-ar/strings.xml index 5f4dd0fe9f..fd96db990f 100644 --- a/voucher/src/main/res/values-ar/strings.xml +++ b/voucher/src/main/res/values-ar/strings.xml @@ -9,6 +9,11 @@ تنزيل تعليمات الخصم المباشر (تعليمات / تفويض DDI) + شكرًا لك على شرائك، يرجى استخدام القسيمة التالية لإتمام عملية الدفع. + مرجع الدفع + تاريخ الانتهاء + نسخ الرمز + تم نسخ الرمز إلى الحافظة تنزيل ملف PDF إنهاء \ No newline at end of file diff --git a/voucher/src/main/res/values-cs-rCZ/strings.xml b/voucher/src/main/res/values-cs-rCZ/strings.xml index 3760438119..2a1683587d 100644 --- a/voucher/src/main/res/values-cs-rCZ/strings.xml +++ b/voucher/src/main/res/values-cs-rCZ/strings.xml @@ -9,6 +9,11 @@ Stáhněte si pokyny k přímému inkasu (DDI / podpisové právo) + Děkujeme za nákup. K dokončení platby použijte prosím následující kupón. + Číslo platby + Datum konce platnosti + Kopírovat kód + Kód zkopírován do schránky Stáhnout PDF Dokončit \ No newline at end of file diff --git a/voucher/src/main/res/values-da-rDK/strings.xml b/voucher/src/main/res/values-da-rDK/strings.xml index c0fb41d673..ae5532daef 100644 --- a/voucher/src/main/res/values-da-rDK/strings.xml +++ b/voucher/src/main/res/values-da-rDK/strings.xml @@ -9,6 +9,11 @@ Download vejledningen til direkte debitering (fuldmagt til direkte debitering) + Tak for dit køb. Brug følgende kupon til at gennemføre din betaling. + Betalingsreference + Udløbsdato + Kopiér koden + Koden er kopieret til udklipsholderen Download PDF Afslut \ No newline at end of file diff --git a/voucher/src/main/res/values-de-rDE/strings.xml b/voucher/src/main/res/values-de-rDE/strings.xml index 40befd8b1a..b61ae763ff 100644 --- a/voucher/src/main/res/values-de-rDE/strings.xml +++ b/voucher/src/main/res/values-de-rDE/strings.xml @@ -9,6 +9,11 @@ Laden Sie Ihre Lastschriftanweisung (DDI/Einzugsermächtigung) herunter + Vielen Dank für Ihren Kauf. Bitte schließen Sie Ihre Zahlung unter Verwendung des folgenden Gutscheins ab. + Zahlungsreferenz + Gültig bis + Code kopieren + Code wurde in die Zwischenablage kopiert PDF herunterladen Abschließen \ No newline at end of file diff --git a/voucher/src/main/res/values-el-rGR/strings.xml b/voucher/src/main/res/values-el-rGR/strings.xml index cb0ad1df50..100c2d154a 100644 --- a/voucher/src/main/res/values-el-rGR/strings.xml +++ b/voucher/src/main/res/values-el-rGR/strings.xml @@ -9,6 +9,11 @@ Κατεβάστε την Εντολή Άμεσης Χρέωσης (DDI/Εντολή) + Σας ευχαριστούμε για την αγορά. Χρησιμοποιήστε το παρακάτω κουπόνι για να ολοκληρώσετε την πληρωμή. + Αναφορά πληρωμής + Ημερομηνία λήξης + Αντιγραφή κωδικού + Ο κωδικός αντιγράφτηκε στο πρόχειρο Λήψη PDF Τέλος \ No newline at end of file diff --git a/voucher/src/main/res/values-es-rES/strings.xml b/voucher/src/main/res/values-es-rES/strings.xml index 33ba3f0d3a..e626a82277 100644 --- a/voucher/src/main/res/values-es-rES/strings.xml +++ b/voucher/src/main/res/values-es-rES/strings.xml @@ -9,6 +9,11 @@ Descargue su instrucción de débito directo (IDD/mandato) + Gracias por su compra. Use el siguiente cupón para completar su pago. + Referencia de pago + Fecha de caducidad + Copiar código + Código copiado al portapapeles Descargar PDF Finalizar \ No newline at end of file diff --git a/voucher/src/main/res/values-fi-rFI/strings.xml b/voucher/src/main/res/values-fi-rFI/strings.xml index 5741d47490..2b323b4176 100644 --- a/voucher/src/main/res/values-fi-rFI/strings.xml +++ b/voucher/src/main/res/values-fi-rFI/strings.xml @@ -9,6 +9,11 @@ Lataa suoraveloitusohjeet (DDI / Mandate) + Kiitos hankinnastasi, käytä seuraavaa kuponkia viedäksesi maksusi päätökseen. + Maksun viite + Vanhenemispäivämäärä + Kopioi koodi + Koodi kopioitu leikepöydälle Lataa PDF Lopeta \ No newline at end of file diff --git a/voucher/src/main/res/values-fr-rFR/strings.xml b/voucher/src/main/res/values-fr-rFR/strings.xml index 060730a6bb..66f08b0925 100644 --- a/voucher/src/main/res/values-fr-rFR/strings.xml +++ b/voucher/src/main/res/values-fr-rFR/strings.xml @@ -9,6 +9,11 @@ Téléchargez votre mandat de prélèvement (DDI) + Merci pour votre achat, veuillez utiliser le coupon suivant pour finaliser votre paiement. + Référence du paiement + Date d\'expiration + Copier le code + Code copié dans le presse-papiers Télécharger le PDF Terminer \ No newline at end of file diff --git a/voucher/src/main/res/values-hr-rHR/strings.xml b/voucher/src/main/res/values-hr-rHR/strings.xml index 2a00909371..ea14dd50c9 100644 --- a/voucher/src/main/res/values-hr-rHR/strings.xml +++ b/voucher/src/main/res/values-hr-rHR/strings.xml @@ -9,6 +9,11 @@ Preuzmite upute za izravno terećenje (DDI / mandat) + Zahvaljujemo na kupnji, upotrijebite sljedeći kupon za dovršetak plaćanja. + Referenca za plaćanje + Datum isteka + Kopiraj kôd + Kôd je kopiran u međuspremnik Preuzmite PDF Završi \ No newline at end of file diff --git a/voucher/src/main/res/values-hu-rHU/strings.xml b/voucher/src/main/res/values-hu-rHU/strings.xml index cc9492d15c..36f483f75e 100644 --- a/voucher/src/main/res/values-hu-rHU/strings.xml +++ b/voucher/src/main/res/values-hu-rHU/strings.xml @@ -9,6 +9,11 @@ Beszedési megbízási utasítás (meghatalmazás) letöltése + Köszönjük a vásárlást! Kérjük, a fizetéshez használja a következő kupont. + Fizetési referencia + Lejárati dátum + Kód másolása + Kód a vágólapra másolva PDF letöltése Befejezés \ No newline at end of file diff --git a/voucher/src/main/res/values-it-rIT/strings.xml b/voucher/src/main/res/values-it-rIT/strings.xml index ff01eeb23b..e7ecba8cb4 100644 --- a/voucher/src/main/res/values-it-rIT/strings.xml +++ b/voucher/src/main/res/values-it-rIT/strings.xml @@ -9,6 +9,11 @@ Scarica le Istruzioni per l\'addebito diretto (DDI / Mandato) + Grazie per il tuo acquisto, utilizza il seguente coupon per completare il pagamento. + Riferimento del pagamento + Data di scadenza + Copia codice + Codice copiato negli appunti Scarica PDF Completa \ No newline at end of file diff --git a/voucher/src/main/res/values-ja-rJP/strings.xml b/voucher/src/main/res/values-ja-rJP/strings.xml index 47c4835dde..e663229f03 100644 --- a/voucher/src/main/res/values-ja-rJP/strings.xml +++ b/voucher/src/main/res/values-ja-rJP/strings.xml @@ -9,6 +9,11 @@ 自動引き落としの説明 (DDI/委任状) をダウンロードする + お買い上げありがとうございます。以下のクーポンを使用して、お支払いを完了してください。 + 支払いの参照 + 有効期限 + コードをコピー + コードをクリップボードにコピーしました PDFをダウンロード 完了 \ No newline at end of file diff --git a/voucher/src/main/res/values-ko-rKR/strings.xml b/voucher/src/main/res/values-ko-rKR/strings.xml index 3be720d63b..e6ccd42b0c 100644 --- a/voucher/src/main/res/values-ko-rKR/strings.xml +++ b/voucher/src/main/res/values-ko-rKR/strings.xml @@ -9,6 +9,11 @@ 자동 이체 안내(DDI/필수) 다운로드 + 구매해 주셔서 감사합니다. 다음 쿠폰을 사용하여 결제를 완료하십시오. + 결제 참조번호 + 만료일 + 코드 복사하기 + 코드가 클립보드에 복사되었습니다 PDF 다운로드 완료 \ No newline at end of file diff --git a/voucher/src/main/res/values-nb-rNO/strings.xml b/voucher/src/main/res/values-nb-rNO/strings.xml index cdc2ae945c..a416da41c9 100644 --- a/voucher/src/main/res/values-nb-rNO/strings.xml +++ b/voucher/src/main/res/values-nb-rNO/strings.xml @@ -9,6 +9,11 @@ Last ned instruksjoner for direktebelastning (DDI/ mandat) + Takk for ditt kjøp. Vennligst bruk den følgende kupongen til å fullføre betalingen. + Betalingsreferanse + Utløpsdato + Kopier kode + Koden er kopiert til utklippstavlen Last ned PDF Fullfør \ No newline at end of file diff --git a/voucher/src/main/res/values-nl-rNL/strings.xml b/voucher/src/main/res/values-nl-rNL/strings.xml index 0455626d95..a3bb49e508 100644 --- a/voucher/src/main/res/values-nl-rNL/strings.xml +++ b/voucher/src/main/res/values-nl-rNL/strings.xml @@ -9,6 +9,11 @@ Download uw machtiging automatische incasso + Bedankt voor uw aankoop. Gebruik deze coupon om uw betaling te voltooien. + Betalingsreferentie + Vervaldatum + Code kopiëren + De code is naar het klembord gekopieerd PDF downloaden Voltooien \ No newline at end of file diff --git a/voucher/src/main/res/values-pl-rPL/strings.xml b/voucher/src/main/res/values-pl-rPL/strings.xml index 549a8d40e8..ca46547eb7 100644 --- a/voucher/src/main/res/values-pl-rPL/strings.xml +++ b/voucher/src/main/res/values-pl-rPL/strings.xml @@ -9,6 +9,11 @@ Pobierz dyspozycję polecenia zapłaty (DDI/upoważnienie) + Dziękujemy za zakup, dokończ płatność przy użyciu tego kuponu. + Nr referencyjny płatności + Data ważności + Skopiuj kod + Kod skopiowany do schowka Pobierz PDF Zakończ \ No newline at end of file diff --git a/voucher/src/main/res/values-pt-rBR/strings.xml b/voucher/src/main/res/values-pt-rBR/strings.xml index 8612569e3d..5492175b15 100644 --- a/voucher/src/main/res/values-pt-rBR/strings.xml +++ b/voucher/src/main/res/values-pt-rBR/strings.xml @@ -9,6 +9,11 @@ Baixar instrução de débito direto (DDI) + Obrigado pela sua compra, use o cupom a seguir para concluir o seu pagamento. + Referência de pagamento + Data de validade + Copiar código + Código copiado Baixar PDF Concluir \ No newline at end of file diff --git a/voucher/src/main/res/values-pt-rPT/strings.xml b/voucher/src/main/res/values-pt-rPT/strings.xml index 9199ee56ee..2aa1cdad24 100644 --- a/voucher/src/main/res/values-pt-rPT/strings.xml +++ b/voucher/src/main/res/values-pt-rPT/strings.xml @@ -9,6 +9,11 @@ Descarregue a sua Instrução de Débito Direto (DDI / Mandato) + Obrigado pela sua compra, utilize o seguinte cupão para completar o seu pagamento. + Referência de pagamento + Data de validade + Copiar código + Código copiado para a área de transferência Descarregar PDF Terminar \ No newline at end of file diff --git a/voucher/src/main/res/values-ro-rRO/strings.xml b/voucher/src/main/res/values-ro-rRO/strings.xml index 09fb165fa8..ce0d6da110 100644 --- a/voucher/src/main/res/values-ro-rRO/strings.xml +++ b/voucher/src/main/res/values-ro-rRO/strings.xml @@ -9,6 +9,11 @@ Descărcați instrucțiunile de debitare directă (DDI/mandat) + Vă mulțumim pentru cumpărături, vă rugăm să utilizați următorul cupon pentru a vă finaliza plata. + Referința plății + Data de expirare + Copiați codul + Cod copiat în clipboard Descărcați PDF Finalizați \ No newline at end of file diff --git a/voucher/src/main/res/values-ru-rRU/strings.xml b/voucher/src/main/res/values-ru-rRU/strings.xml index 038cb4cdde..09e168abcc 100644 --- a/voucher/src/main/res/values-ru-rRU/strings.xml +++ b/voucher/src/main/res/values-ru-rRU/strings.xml @@ -9,6 +9,11 @@ Загрузить распоряжение прямого дебетования (DDI / поручение) + Благодарим за покупку. Для завершения оплаты используйте следующий купон. + Код оплаты + Срок действия + Скопировать код + Код скопирован в буфер обмена Загрузить PDF Готово \ No newline at end of file diff --git a/voucher/src/main/res/values-sk-rSK/strings.xml b/voucher/src/main/res/values-sk-rSK/strings.xml index 93ceab4ee2..b904c77969 100644 --- a/voucher/src/main/res/values-sk-rSK/strings.xml +++ b/voucher/src/main/res/values-sk-rSK/strings.xml @@ -9,6 +9,11 @@ Stiahnite si pokyny k inkasu (DDI/Mandát) + Ďakujeme vám za nákup; na dokončenie platby použite nasledujúci kupón. + Platobná referencia + Dátum vypršania platnosti + Skopírovať kód + Kód bol skopírovaný do schránky Stiahnuť vo formáte PDF Dokončiť \ No newline at end of file diff --git a/voucher/src/main/res/values-sl-rSI/strings.xml b/voucher/src/main/res/values-sl-rSI/strings.xml index b9628cea4c..0d4b45416b 100644 --- a/voucher/src/main/res/values-sl-rSI/strings.xml +++ b/voucher/src/main/res/values-sl-rSI/strings.xml @@ -9,6 +9,11 @@ Prenesite navodila za neposredno bremenitev (DDI/mandat) + Zahvaljujemo se vam za nakup. Za dokončanje plačila uporabite naslednji kupon. + Referenčna številka plačila + Datum poteka veljavnosti + Kopiraj kodo + Koda je kopirana v odložišče Prenos datoteke PDF Zaključi \ No newline at end of file diff --git a/voucher/src/main/res/values-sv-rSE/strings.xml b/voucher/src/main/res/values-sv-rSE/strings.xml index 5b9759f445..5e09b49e2a 100644 --- a/voucher/src/main/res/values-sv-rSE/strings.xml +++ b/voucher/src/main/res/values-sv-rSE/strings.xml @@ -9,6 +9,11 @@ Ladda ner din instruktion för autogiro/direktdebitering (DDI / Mandate) + Tack för ditt köp, vänligen använd följande kupong för att slutföra din betalning. + Betalreferens + Utgångsdatum + Kopiera kod + Koden kopierades till Urklipp Ladda ner PDF Avsluta \ No newline at end of file diff --git a/voucher/src/main/res/values-zh-rCN/strings.xml b/voucher/src/main/res/values-zh-rCN/strings.xml index 08a4637d38..f9064b9f5b 100644 --- a/voucher/src/main/res/values-zh-rCN/strings.xml +++ b/voucher/src/main/res/values-zh-rCN/strings.xml @@ -9,6 +9,11 @@ 下载您的直接借记指示(DDI/委托) + 感谢您的购买,请使用以下优惠券完成支付。 + 交易号 + 有效期 + 复制代码 + 代码复制到剪贴板 下载 PDF 文件 完成 \ No newline at end of file diff --git a/voucher/src/main/res/values-zh-rTW/strings.xml b/voucher/src/main/res/values-zh-rTW/strings.xml index 7832791c00..9f176d3d0e 100644 --- a/voucher/src/main/res/values-zh-rTW/strings.xml +++ b/voucher/src/main/res/values-zh-rTW/strings.xml @@ -9,6 +9,11 @@ 下載您的直接扣款指示(DDI/授權) + 多謝惠顧,請使用以下優惠券完成付款。 + 付款參照號碼 + 到期日期 + 複製代碼 + 已將代碼複製到剪貼簿 下載 PDF 完成 \ No newline at end of file diff --git a/voucher/src/main/res/values/strings.xml b/voucher/src/main/res/values/strings.xml index 2182137e8f..840f53f17a 100644 --- a/voucher/src/main/res/values/strings.xml +++ b/voucher/src/main/res/values/strings.xml @@ -9,6 +9,11 @@ Download your Direct Debit Instruction (DDI / Mandate) + Thank you for your purchase, please use the following coupon to complete your payment. + Payment Reference + Expiration Date + Copy code + Code copied to clipboard Download PDF Finish \ No newline at end of file From eed9ef5954b1de2c5c47272a0cd25a2ad0656dc1 Mon Sep 17 00:00:00 2001 From: Atef Etman Date: Mon, 3 Apr 2023 18:36:57 +0200 Subject: [PATCH 045/161] Add styles for Boleto voucher view COAND-366 --- ui-core/src/main/res/values/dimens.xml | 1 + .../voucher/internal/ui/view/VoucherView.kt | 2 +- voucher/src/main/res/layout/voucher_view.xml | 2 +- voucher/src/main/res/values/styles.xml | 53 ++++++++++++++++++- 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/ui-core/src/main/res/values/dimens.xml b/ui-core/src/main/res/values/dimens.xml index cb09a9d45f..fcfa517936 100644 --- a/ui-core/src/main/res/values/dimens.xml +++ b/ui-core/src/main/res/values/dimens.xml @@ -13,6 +13,7 @@ 8dp 12dp 16dp + 24dp 32dp 40dp diff --git a/voucher/src/main/java/com/adyen/checkout/voucher/internal/ui/view/VoucherView.kt b/voucher/src/main/java/com/adyen/checkout/voucher/internal/ui/view/VoucherView.kt index 0ef886f0cf..ea4a87868b 100644 --- a/voucher/src/main/java/com/adyen/checkout/voucher/internal/ui/view/VoucherView.kt +++ b/voucher/src/main/java/com/adyen/checkout/voucher/internal/ui/view/VoucherView.kt @@ -67,7 +67,7 @@ internal class VoucherView @JvmOverloads constructor( private fun initLocalizedStrings(localizedContext: Context) { binding.textViewDescription.setLocalizedTextFromStyle( - R.style.AdyenCheckout_Voucher_DescriptionTextAppearance, + R.style.AdyenCheckout_Voucher_Description_Bacs, localizedContext ) binding.textViewDownload.setLocalizedTextFromStyle( diff --git a/voucher/src/main/res/layout/voucher_view.xml b/voucher/src/main/res/layout/voucher_view.xml index 3c983681db..09bbf0dafd 100644 --- a/voucher/src/main/res/layout/voucher_view.xml +++ b/voucher/src/main/res/layout/voucher_view.xml @@ -22,7 +22,7 @@ diff --git a/voucher/src/main/res/values/styles.xml b/voucher/src/main/res/values/styles.xml index 7d04ca4ade..c49c16551c 100644 --- a/voucher/src/main/res/values/styles.xml +++ b/voucher/src/main/res/values/styles.xml @@ -10,7 +10,7 @@ + + + + + + + + + + + + + + + + From 4a6aa50773a7cd6bf150aeee10a02f46da610387 Mon Sep 17 00:00:00 2001 From: Atef Etman Date: Mon, 3 Apr 2023 18:41:22 +0200 Subject: [PATCH 046/161] Add full voucher view xml layout COAND-366 --- .../main/res/drawable/voucher_background.xml | 15 ++ .../src/main/res/layout/full_voucher_view.xml | 145 ++++++++++++++++++ voucher/src/main/res/values/colors.xml | 11 ++ 3 files changed, 171 insertions(+) create mode 100644 voucher/src/main/res/drawable/voucher_background.xml create mode 100644 voucher/src/main/res/layout/full_voucher_view.xml create mode 100644 voucher/src/main/res/values/colors.xml diff --git a/voucher/src/main/res/drawable/voucher_background.xml b/voucher/src/main/res/drawable/voucher_background.xml new file mode 100644 index 0000000000..a6879a5f84 --- /dev/null +++ b/voucher/src/main/res/drawable/voucher_background.xml @@ -0,0 +1,15 @@ + + + + + + diff --git a/voucher/src/main/res/layout/full_voucher_view.xml b/voucher/src/main/res/layout/full_voucher_view.xml new file mode 100644 index 0000000000..f167423971 --- /dev/null +++ b/voucher/src/main/res/layout/full_voucher_view.xml @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + +