diff --git a/.github/workflows/gradle-task.yml b/.github/workflows/gradle-task.yml index cd1536d01..2a3014a66 100644 --- a/.github/workflows/gradle-task.yml +++ b/.github/workflows/gradle-task.yml @@ -43,6 +43,13 @@ jobs: ~/.gradle/caches ~/.gradle/wrapper key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} + + - name: Create local.properties + run: | + touch local.properties + echo "sandbox.components.public_key=${{ secrets.COMPONENTS_SANDBOX_PUBLIC_KEY }}" >> local.properties + echo "sandbox.components.secret_key=${{ secrets.COMPONENTS_SANDBOX_SECRET_KEY }}" >> local.properties + - name: Run ${{ inputs.task }} with Gradle run: ./gradlew :${{ inputs.module }}:${{ inputs.task }} diff --git a/app/build.gradle b/app/build.gradle index cc87b03fc..ba8641f97 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -45,6 +45,22 @@ android { lint { baseline = file('lint-baseline.xml') } + + def localProps = new Properties() + def localPropsFile = rootProject.file("local.properties") + if (localPropsFile.exists()) { + localProps.load(localPropsFile.newDataInputStream()) + } + + defaultConfig { + buildConfigField "String", "SANDBOX_PUBLIC_KEY", "\"${localProps.getProperty("sandbox.components.public_key") ?: ""}\"" + buildConfigField "String", "SANDBOX_SECRET_KEY", "\"${localProps.getProperty("sandbox.components.secret_key") ?: ""}\"" + buildConfigField "String", "SANDBOX_PROCESSING_CHANNEL_ID", "\"${localProps.getProperty("sandbox.components.processing_channel_id") ?: ""}\"" + + buildFeatures { + buildConfig true + } + } } dependencies { diff --git a/app/src/main/java/checkout/checkout_android/CVVTokenizationActivity.java b/app/src/main/java/checkout/checkout_android/CVVTokenizationActivity.java index ae0aa6bde..ba1279f23 100644 --- a/app/src/main/java/checkout/checkout_android/CVVTokenizationActivity.java +++ b/app/src/main/java/checkout/checkout_android/CVVTokenizationActivity.java @@ -1,6 +1,6 @@ package checkout.checkout_android; -import static checkout.checkout_android.Constants.PUBLIC_KEY_CVV_TOKENIZATION; +import static checkout.checkout_android.Constants.REGIONAL_SUBDOMAIN; import android.app.AlertDialog; import android.os.Bundle; @@ -51,7 +51,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { setupObservers(); // Create cvvComponentApi - CVVComponentApi cvvComponentApi = CVVComponentApiFactory.create(PUBLIC_KEY_CVV_TOKENIZATION, Environment.SANDBOX, this); + CVVComponentApi cvvComponentApi = CVVComponentApiFactory.create(BuildConfig.SANDBOX_PUBLIC_KEY, Environment.SANDBOX, this, REGIONAL_SUBDOMAIN); // initialise CVVTokenizationResultHandler for tokenization resultHandler = result -> { diff --git a/app/src/main/java/checkout/checkout_android/Constants.java b/app/src/main/java/checkout/checkout_android/Constants.java index ea94a64cc..7dfee3cb6 100644 --- a/app/src/main/java/checkout/checkout_android/Constants.java +++ b/app/src/main/java/checkout/checkout_android/Constants.java @@ -7,19 +7,11 @@ public class Constants { * Target platform environment */ public static final Environment ENVIRONMENT = Environment.SANDBOX; - /** - * Replace with public key from Hub in Sandbox Environment - */ - public static final String PUBLIC_KEY = "pk_test_b37b8b6b-fc9a-483f-a77e-3386b606f90e"; - /** - * Replace with public key from Hub in Sandbox Environment, testing key for CVV Tokenization - */ - public static final String PUBLIC_KEY_CVV_TOKENIZATION = "pk_6b30805a-1f3b-4c63-8b75-eb3030109173"; - /** - * Replace with Secret key from Hub in Sandbox Environment - */ - public static final String SECRET_KEY = "sk_test_568e6077-a08f-4692-9237-cc6c48dcf6aa"; + /** + * Replace with subdomain value, testing key for base url regional subdomain prefix + */ + public static final String REGIONAL_SUBDOMAIN = "global"; /** * Replace with Success/Failure Urls from Hub in Sandbox Environment */ diff --git a/app/src/main/java/checkout/checkout_android/DemoActivity.java b/app/src/main/java/checkout/checkout_android/DemoActivity.java index 5a6e156b8..29788955a 100644 --- a/app/src/main/java/checkout/checkout_android/DemoActivity.java +++ b/app/src/main/java/checkout/checkout_android/DemoActivity.java @@ -1,5 +1,7 @@ package checkout.checkout_android; +import static checkout.checkout_android.Constants.REGIONAL_SUBDOMAIN; + import android.app.AlertDialog; import android.app.ProgressDialog; import android.os.Bundle; @@ -18,6 +20,8 @@ import com.checkout.threedsecure.model.ThreeDSResult; import com.checkout.tokenization.model.TokenDetails; +import java.util.Collections; + import checkout.checkout_android.utils.PaymentUtil; import kotlin.Unit; import kotlin.jvm.functions.Function1; @@ -89,11 +93,15 @@ public void onBackPressed() { private PaymentFormConfig providePaymentFormConfig() { return new PaymentFormConfig( - Constants.PUBLIC_KEY, + BuildConfig.SANDBOX_PUBLIC_KEY, this, Constants.ENVIRONMENT, new PaymentFormStyle(), - mPaymentFlowHandler); + mPaymentFlowHandler, + Collections.emptyList(), + null, + REGIONAL_SUBDOMAIN + ); } private void displayMessage(String title, String message, boolean exitScreen) { diff --git a/app/src/main/java/checkout/checkout_android/utils/PaymentUtil.java b/app/src/main/java/checkout/checkout_android/utils/PaymentUtil.java index c722d033b..998a745ef 100644 --- a/app/src/main/java/checkout/checkout_android/utils/PaymentUtil.java +++ b/app/src/main/java/checkout/checkout_android/utils/PaymentUtil.java @@ -11,6 +11,7 @@ import java.io.IOException; +import checkout.checkout_android.BuildConfig; import checkout.checkout_android.Constants; import okhttp3.Call; import okhttp3.MediaType; @@ -47,7 +48,7 @@ public static void createPayment(String token, Callback callback) { RequestBody requestBody = buildPaymentRequestBody(token); Request paymentRequest = new Request.Builder() .url("https://api.sandbox.checkout.com/payments") - .addHeader("Authorization", "Bearer "+Constants.SECRET_KEY) + .addHeader("Authorization", "Bearer "+ BuildConfig.SANDBOX_SECRET_KEY) .post(requestBody) .build(); diff --git a/checkout/src/main/java/com/checkout/CheckoutApiServiceFactory.kt b/checkout/src/main/java/com/checkout/CheckoutApiServiceFactory.kt index f2ae1952d..3f6d05054 100644 --- a/checkout/src/main/java/com/checkout/CheckoutApiServiceFactory.kt +++ b/checkout/src/main/java/com/checkout/CheckoutApiServiceFactory.kt @@ -7,6 +7,7 @@ import com.checkout.base.model.Environment import com.checkout.logging.EventLoggerProvider import com.checkout.logging.Logger import com.checkout.logging.model.LoggingEvent +import com.checkout.logging.utils.toBaseUrl import com.checkout.network.OkHttpProvider import com.checkout.risk.FramesOptions import com.checkout.threedsecure.Executor @@ -40,6 +41,7 @@ public object CheckoutApiServiceFactory { publicKey: String, environment: Environment, context: Context, + baseUrlPrefix: String? = null, ): CheckoutApiService { val logger = EventLoggerProvider.provide() logger.setup(context, environment) @@ -51,7 +53,7 @@ public object CheckoutApiServiceFactory { ) return CheckoutApiClient( - provideTokenRepository(context, publicKey, environment), + provideTokenRepository(context, publicKey, environment, baseUrlPrefix), provideThreeDSExecutor(logger), ) } @@ -60,8 +62,9 @@ public object CheckoutApiServiceFactory { context: Context, publicKey: String, environment: Environment, + baseUrlPrefix: String?, ): TokenRepository = TokenRepositoryImpl( - networkApiClient = provideNetworkApiClient(publicKey, environment.url), + networkApiClient = provideNetworkApiClient(publicKey, environment.toBaseUrl(baseUrlPrefix)), cardToTokenRequestMapper = CardToTokenRequestMapper(), cvvToTokenNetworkRequestMapper = CVVToTokenNetworkRequestMapper(), cardTokenizationNetworkDataMapper = CardTokenizationNetworkDataMapper(), @@ -77,7 +80,13 @@ public object CheckoutApiServiceFactory { logger = TokenizationEventLogger(EventLoggerProvider.provide()), publicKey = publicKey, cvvTokenizationNetworkDataMapper = CVVTokenizationNetworkDataMapper(), - riskSdkUseCase = RiskSdkUseCase(environment, context, publicKey, riskSDKFramesOptions, RiskInstanceProvider), + riskSdkUseCase = RiskSdkUseCase( + environment, + context, + publicKey, + riskSDKFramesOptions, + RiskInstanceProvider, + ), ) private fun provideNetworkApiClient( diff --git a/checkout/src/main/java/com/checkout/base/model/Environment.kt b/checkout/src/main/java/com/checkout/base/model/Environment.kt index 99463fa1c..b4b20393e 100644 --- a/checkout/src/main/java/com/checkout/base/model/Environment.kt +++ b/checkout/src/main/java/com/checkout/base/model/Environment.kt @@ -1,8 +1,6 @@ package com.checkout.base.model -import com.checkout.base.util.EnvironmentConstants - -public enum class Environment(public val url: String) { - PRODUCTION(EnvironmentConstants.PRODUCTION_SERVER_URL), - SANDBOX(EnvironmentConstants.SANDBOX_SERVER_URL), +public enum class Environment { + PRODUCTION, + SANDBOX, } diff --git a/checkout/src/main/java/com/checkout/base/util/CommonConstants.kt b/checkout/src/main/java/com/checkout/base/util/CommonConstants.kt index c89976b94..96c4da8ab 100644 --- a/checkout/src/main/java/com/checkout/base/util/CommonConstants.kt +++ b/checkout/src/main/java/com/checkout/base/util/CommonConstants.kt @@ -14,3 +14,8 @@ internal const val ZIP_LENGTH = 50 */ internal const val PHONE_MIN_LENGTH = 6 internal const val PHONE_MAX_LENGTH = 25 + +/** + * Constants for environments + */ +internal const val HTTPS_PROTOCOL: String = "https://" diff --git a/checkout/src/main/java/com/checkout/base/util/EnvironmentConstants.kt b/checkout/src/main/java/com/checkout/base/util/EnvironmentConstants.kt index 43f39e7b2..ab6d375dc 100644 --- a/checkout/src/main/java/com/checkout/base/util/EnvironmentConstants.kt +++ b/checkout/src/main/java/com/checkout/base/util/EnvironmentConstants.kt @@ -3,4 +3,6 @@ package com.checkout.base.util internal object EnvironmentConstants { internal const val SANDBOX_SERVER_URL = "https://api.sandbox.checkout.com/tokens" internal const val PRODUCTION_SERVER_URL = "https://api.checkout.com/tokens" + internal const val PRODUCTION_LOGGING = "production" + internal const val SANDBOX_LOGGING = "sandbox" } diff --git a/checkout/src/main/java/com/checkout/logging/utils/EnvironmentExtension.kt b/checkout/src/main/java/com/checkout/logging/utils/EnvironmentExtension.kt index 7fc67c904..b523f5dc3 100644 --- a/checkout/src/main/java/com/checkout/logging/utils/EnvironmentExtension.kt +++ b/checkout/src/main/java/com/checkout/logging/utils/EnvironmentExtension.kt @@ -1,13 +1,32 @@ package com.checkout.logging.utils import com.checkout.base.model.Environment +import com.checkout.base.util.EnvironmentConstants.PRODUCTION_LOGGING +import com.checkout.base.util.EnvironmentConstants.PRODUCTION_SERVER_URL +import com.checkout.base.util.EnvironmentConstants.SANDBOX_LOGGING +import com.checkout.base.util.EnvironmentConstants.SANDBOX_SERVER_URL +import com.checkout.base.util.HTTPS_PROTOCOL +internal fun Environment.toBaseUrl(baseUrlPrefix: String? = null) = when (this) { + Environment.PRODUCTION -> PRODUCTION_SERVER_URL.applyPrefix(baseUrlPrefix) + Environment.SANDBOX -> SANDBOX_SERVER_URL.applyPrefix(baseUrlPrefix) +} internal fun Environment.toLoggingEnvironment() = when (this) { Environment.PRODUCTION -> com.checkout.eventlogger.Environment.PRODUCTION Environment.SANDBOX -> com.checkout.eventlogger.Environment.SANDBOX } internal fun Environment.toLoggingName() = when (this) { - Environment.PRODUCTION -> "production" - Environment.SANDBOX -> "sandbox" + Environment.PRODUCTION -> PRODUCTION_LOGGING + Environment.SANDBOX -> SANDBOX_LOGGING +} + +private fun String.applyPrefix(prefix: String?): String { + val validatedPrefix = prefix?.baseUrlPrefixValidator() ?: return this + return this.replace(HTTPS_PROTOCOL, "$HTTPS_PROTOCOL$validatedPrefix.") } + +private fun String?.baseUrlPrefixValidator() = this + ?.takeIf { prefix -> + prefix.all { char -> char in 'a'..'z' || char in 'A'..'Z' || char in '0'..'9' } && prefix.isNotEmpty() + } diff --git a/checkout/src/test/java/com/checkout/CheckoutApiServiceFactoryTest.kt b/checkout/src/test/java/com/checkout/CheckoutApiServiceFactoryTest.kt index 4eea27064..1db5f878b 100644 --- a/checkout/src/test/java/com/checkout/CheckoutApiServiceFactoryTest.kt +++ b/checkout/src/test/java/com/checkout/CheckoutApiServiceFactoryTest.kt @@ -34,7 +34,7 @@ internal class CheckoutApiServiceFactoryTest { val mockEnvironment = Environment.SANDBOX // When - CheckoutApiServiceFactory.create("", mockEnvironment, mockContext) + CheckoutApiServiceFactory.create("", mockEnvironment, mockContext, null) // Then verify { mockLogger.setup(eq(mockContext), eq(mockEnvironment)) } diff --git a/checkout/src/test/java/com/checkout/EnvironmentExtensionTest.kt b/checkout/src/test/java/com/checkout/EnvironmentExtensionTest.kt new file mode 100644 index 000000000..1c78f4625 --- /dev/null +++ b/checkout/src/test/java/com/checkout/EnvironmentExtensionTest.kt @@ -0,0 +1,93 @@ +package com.checkout + +import com.checkout.base.model.Environment +import com.checkout.base.util.EnvironmentConstants +import com.checkout.base.util.EnvironmentConstants.PRODUCTION_SERVER_URL +import com.checkout.logging.utils.toBaseUrl +import org.junit.Assert.assertEquals +import org.junit.Test + +internal class EnvironmentExtensionTest { + + @Test + fun `when toBaseUrl PRODUCTION with null subDomainPrefix returns production server URL`() { + val result = Environment.PRODUCTION.toBaseUrl(null) + assertEquals(EnvironmentConstants.PRODUCTION_SERVER_URL, result) + } + + @Test + fun `when toBaseUrl PRODUCTION with no subDomainPrefix argument returns production server URL`() { + val result = Environment.PRODUCTION.toBaseUrl() + assertEquals(EnvironmentConstants.PRODUCTION_SERVER_URL, result) + } + + @Test + fun `when toBaseUrl PRODUCTION with subDomainPrefix returns custom production URL`() { + val result = Environment.PRODUCTION.toBaseUrl("custom") + assertEquals("https://custom.api.checkout.com/tokens", result) + } + + @Test + fun `when toBaseUrl PRODUCTION with alphanumeric subDomainPrefix returns custom production URL`() { + val result = Environment.PRODUCTION.toBaseUrl("mySubdomain123") + assertEquals("https://mySubdomain123.api.checkout.com/tokens", result) + } + + @Test + fun `when toBaseUrl SANDBOX with null subDomainPrefix returns sandbox server URL`() { + val result = Environment.SANDBOX.toBaseUrl(null) + assertEquals(EnvironmentConstants.SANDBOX_SERVER_URL, result) + } + + @Test + fun `when toBaseUrl SANDBOX with no subDomainPrefix argument returns sandbox server URL`() { + val result = Environment.SANDBOX.toBaseUrl() + assertEquals(EnvironmentConstants.SANDBOX_SERVER_URL, result) + } + + @Test + fun `when toBaseUrl SANDBOX with subDomainPrefix returns custom sandbox URL`() { + val result = Environment.SANDBOX.toBaseUrl("custom") + assertEquals("https://custom.api.sandbox.checkout.com/tokens", result) + } + + @Test + fun `when toBaseUrl SANDBOX with alphanumeric subDomainPrefix returns custom sandbox URL`() { + val result = Environment.SANDBOX.toBaseUrl("test99") + assertEquals("https://test99.api.sandbox.checkout.com/tokens", result) + } + + @Test + fun `when toBaseUrl with empty string subDomainPrefix uses default URL for production`() { + val result = Environment.PRODUCTION.toBaseUrl("") + assertEquals("https://api.checkout.com/tokens", result) + } + + @Test + fun `toBaseUrl PRODUCTION returns default URL for any non-alphanumeric prefix`() { + val invalidPrefixList = listOf( + "invalid_prefix", + "invalid-prefix", + "invalid.prefix", + "invalid@prefix", + "invalid!prefix", + "invalid#prefix", + "invalid\$prefix", + "invalid%prefix", + "invalid^prefix", + "invalid&prefix", + "invalid*prefix", + "invalid(prefix", + "invalid)prefix", + "invalid prefix", + ) + + invalidPrefixList.forEach { invalidPrefix -> + val result = Environment.PRODUCTION.toBaseUrl(invalidPrefix) + assertEquals( + PRODUCTION_SERVER_URL, + result, + ) + } + } +} diff --git a/example_app_frames/build.gradle.kts b/example_app_frames/build.gradle.kts index aad77d19c..b3373fd32 100644 --- a/example_app_frames/build.gradle.kts +++ b/example_app_frames/build.gradle.kts @@ -1,3 +1,5 @@ +import java.util.Properties + plugins { id("precompiled-android-app") id("org.jetbrains.kotlin.plugin.compose") @@ -9,6 +11,26 @@ android { applicationId = ExampleAppFramesConfig.id versionCode = ExampleAppFramesConfig.versionCode versionName = ExampleAppFramesConfig.versionName + + Properties().apply { + load(rootProject.file("local.properties").inputStream()) + }.run { + buildConfigField( + "String", + "SANDBOX_PUBLIC_KEY", + "\"${this["sandbox.components.public_key"]}\"", + ) + buildConfigField( + "String", + "SANDBOX_SECRET_KEY", + "\"${this["sandbox.components.secret_key"]}\"", + ) + buildConfigField( + "String", + "SANDBOX_PROCESSING_CHANNEL_ID", + "\"${this["sandbox.components.processing_channel_id"]}\"", + ) + } } buildTypes { @@ -21,6 +43,10 @@ android { } } + buildFeatures { + buildConfig = true + } + packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" diff --git a/example_app_frames/src/main/java/com/checkout/example/frames/ui/screen/CVVTokenizationScreen.kt b/example_app_frames/src/main/java/com/checkout/example/frames/ui/screen/CVVTokenizationScreen.kt index 683e87495..4b8d8f8fe 100644 --- a/example_app_frames/src/main/java/com/checkout/example/frames/ui/screen/CVVTokenizationScreen.kt +++ b/example_app_frames/src/main/java/com/checkout/example/frames/ui/screen/CVVTokenizationScreen.kt @@ -8,9 +8,10 @@ import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavHostController import com.checkout.base.model.CardScheme import com.checkout.base.model.Environment +import com.checkout.example.frames.BuildConfig import com.checkout.example.frames.paymentformstyling.PaymentFormConstants import com.checkout.example.frames.styling.CustomCVVInputFieldStyle -import com.checkout.example.frames.ui.utils.PUBLIC_KEY_CVV_TOKENIZATION +import com.checkout.example.frames.ui.utils.REGIONAL_SUBDOMAIN import com.checkout.example.frames.ui.viewmodel.CVVTokenizationViewModel import com.checkout.frames.cvvinputfield.CVVComponentApiFactory import com.checkout.frames.cvvinputfield.api.CVVComponentApi @@ -32,9 +33,10 @@ fun CVVTokenizationScreen(navController: NavHostController) { val cvvTokenizationViewModel: CVVTokenizationViewModel = viewModel() val cvvComponentApi = CVVComponentApiFactory.create( - publicKey = PUBLIC_KEY_CVV_TOKENIZATION, + publicKey = BuildConfig.SANDBOX_PUBLIC_KEY, environment = Environment.SANDBOX, context = LocalContext.current, + baseUrlPrefix = REGIONAL_SUBDOMAIN, ) val visaMediator = createMediator( diff --git a/example_app_frames/src/main/java/com/checkout/example/frames/ui/screen/HomeScreen.kt b/example_app_frames/src/main/java/com/checkout/example/frames/ui/screen/HomeScreen.kt index 0d25473d7..7fdde09fb 100644 --- a/example_app_frames/src/main/java/com/checkout/example/frames/ui/screen/HomeScreen.kt +++ b/example_app_frames/src/main/java/com/checkout/example/frames/ui/screen/HomeScreen.kt @@ -36,6 +36,7 @@ import androidx.compose.ui.unit.dp import androidx.navigation.NavController import com.checkout.CheckoutApiServiceFactory import com.checkout.base.model.Environment +import com.checkout.example.frames.BuildConfig import com.checkout.example.frames.R import com.checkout.example.frames.navigation.Screen import com.checkout.example.frames.ui.component.ButtonComponent @@ -47,6 +48,7 @@ import com.checkout.example.frames.ui.theme.FramesTheme import com.checkout.example.frames.ui.theme.GrayColor import com.checkout.example.frames.ui.utils.PromptUtils import com.checkout.example.frames.ui.utils.PromptUtils.neutralButton +import com.checkout.example.frames.ui.utils.REGIONAL_SUBDOMAIN import com.checkout.tokenization.model.GooglePayTokenRequest @Suppress("MagicNumber", "LongMethod") @@ -220,9 +222,10 @@ fun invokeCheckoutSDKToGenerateTokenForGooglePay(context: Context) { * Creating instance of CheckoutApiClient */ val checkoutApiClient = CheckoutApiServiceFactory.create( - "pk_test_6e40a700-d563-43cd-89d0-f9bb17d35e73", + BuildConfig.SANDBOX_PUBLIC_KEY, Environment.SANDBOX, context, + REGIONAL_SUBDOMAIN, ) /** diff --git a/example_app_frames/src/main/java/com/checkout/example/frames/ui/screen/MainActivity.kt b/example_app_frames/src/main/java/com/checkout/example/frames/ui/screen/MainActivity.kt index d0e5a07a0..026ce54b4 100644 --- a/example_app_frames/src/main/java/com/checkout/example/frames/ui/screen/MainActivity.kt +++ b/example_app_frames/src/main/java/com/checkout/example/frames/ui/screen/MainActivity.kt @@ -9,14 +9,15 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.checkout.base.model.CardScheme +import com.checkout.example.frames.BuildConfig import com.checkout.example.frames.R import com.checkout.example.frames.navigation.Screen import com.checkout.example.frames.paymentformstyling.CustomBillingFormStyle import com.checkout.example.frames.paymentformstyling.CustomPaymentDetailsStyle import com.checkout.example.frames.paymentformstyling.CustomPaymentFormTheme import com.checkout.example.frames.ui.utils.ENVIRONMENT -import com.checkout.example.frames.ui.utils.PUBLIC_KEY import com.checkout.example.frames.ui.utils.PrefillDataHelper +import com.checkout.example.frames.ui.utils.REGIONAL_SUBDOMAIN import com.checkout.frames.api.PaymentFlowHandler import com.checkout.frames.api.PaymentFormMediator import com.checkout.frames.screen.paymentform.model.PaymentFormConfig @@ -47,9 +48,10 @@ fun Navigator( ) { val navController = rememberNavController() val defaultPaymentFormConfig = PaymentFormConfig( - publicKey = PUBLIC_KEY, + publicKey = BuildConfig.SANDBOX_PUBLIC_KEY, context = context, environment = ENVIRONMENT, + baseUrlPrefix = REGIONAL_SUBDOMAIN, paymentFlowHandler = object : PaymentFlowHandler { override fun onSubmit() { /*Intentionally left empty*/ diff --git a/example_app_frames/src/main/java/com/checkout/example/frames/ui/screen/ThreedSecureActivity.kt b/example_app_frames/src/main/java/com/checkout/example/frames/ui/screen/ThreedSecureActivity.kt index 6fd06cabe..e2e3e88a4 100644 --- a/example_app_frames/src/main/java/com/checkout/example/frames/ui/screen/ThreedSecureActivity.kt +++ b/example_app_frames/src/main/java/com/checkout/example/frames/ui/screen/ThreedSecureActivity.kt @@ -4,10 +4,10 @@ import android.os.Bundle import android.view.ViewGroup import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import com.checkout.example.frames.BuildConfig import com.checkout.example.frames.R import com.checkout.example.frames.ui.utils.ENVIRONMENT import com.checkout.example.frames.ui.utils.FAILURE_URL -import com.checkout.example.frames.ui.utils.PUBLIC_KEY import com.checkout.example.frames.ui.utils.PromptUtils import com.checkout.example.frames.ui.utils.PromptUtils.neutralButton import com.checkout.example.frames.ui.utils.SUCCESS_URL @@ -28,7 +28,7 @@ class ThreedSecureActivity : ComponentActivity() { val url = intent.getStringExtra(URL_IDENTIFIER) val paymentFormConfig = PaymentFormConfig( - publicKey = PUBLIC_KEY, + publicKey = BuildConfig.SANDBOX_PUBLIC_KEY, context = this, environment = ENVIRONMENT, paymentFlowHandler = object : PaymentFlowHandler { diff --git a/example_app_frames/src/main/java/com/checkout/example/frames/ui/utils/Constants.kt b/example_app_frames/src/main/java/com/checkout/example/frames/ui/utils/Constants.kt index f71355537..980dd1001 100644 --- a/example_app_frames/src/main/java/com/checkout/example/frames/ui/utils/Constants.kt +++ b/example_app_frames/src/main/java/com/checkout/example/frames/ui/utils/Constants.kt @@ -8,19 +8,9 @@ import com.checkout.base.model.Environment val ENVIRONMENT: Environment = Environment.SANDBOX /** - * Replace with public key from Hub in Sandbox Environment + * Replace with subdomain value, testing key for base url regional subdomain prefix */ -const val PUBLIC_KEY = "pk_test_b37b8b6b-fc9a-483f-a77e-3386b606f90e" - -/** - * Replace with public key from Hub in Sandbox Environment, testing key for CVV Tokenization - */ -const val PUBLIC_KEY_CVV_TOKENIZATION = "pk_6b30805a-1f3b-4c63-8b75-eb3030109173" - -/** - * Replace with Secret key from Hub in Sandbox Environment - */ -const val SECRET_KEY = "sk_test_568e6077-a08f-4692-9237-cc6c48dcf6aa" +const val REGIONAL_SUBDOMAIN = "global" /** * Replace with Success/Failure Urls from Hub in Sandbox Environment diff --git a/frames/src/main/java/com/checkout/frames/cvvinputfield/CVVComponentApiFactory.kt b/frames/src/main/java/com/checkout/frames/cvvinputfield/CVVComponentApiFactory.kt index bc99a8d1a..0a5154a6c 100644 --- a/frames/src/main/java/com/checkout/frames/cvvinputfield/CVVComponentApiFactory.kt +++ b/frames/src/main/java/com/checkout/frames/cvvinputfield/CVVComponentApiFactory.kt @@ -16,13 +16,15 @@ public object CVVComponentApiFactory { * @param publicKey - used for client-side authentication in the SDK * @param environment - [Environment] represent the environment for tokenization * @param context - represent the application context + * @param baseUrlPrefix - an optional alphanumeric prefix used to route requests to a specific regional or merchant-specific subdomain (e.g., "msdd"). Must be alphanumeric. */ @JvmStatic public fun create( publicKey: String, environment: Environment, context: Context, + baseUrlPrefix: String? = null, ): CVVComponentApi { - return InternalCVVComponentApi(publicKey, environment, context) + return InternalCVVComponentApi(publicKey, environment, context, baseUrlPrefix) } } diff --git a/frames/src/main/java/com/checkout/frames/cvvinputfield/api/InternalCVVComponentApi.kt b/frames/src/main/java/com/checkout/frames/cvvinputfield/api/InternalCVVComponentApi.kt index 850d7ec6d..37bd1c935 100644 --- a/frames/src/main/java/com/checkout/frames/cvvinputfield/api/InternalCVVComponentApi.kt +++ b/frames/src/main/java/com/checkout/frames/cvvinputfield/api/InternalCVVComponentApi.kt @@ -10,6 +10,7 @@ internal class InternalCVVComponentApi( private val publicKey: String, private val environment: Environment, private val context: Context, + private val baseUrlPrefix: String?, ) : CVVComponentApi { override fun createComponentMediator(cvvComponentConfig: CVVComponentConfig) = InternalCVVComponentMediator( @@ -19,6 +20,7 @@ internal class InternalCVVComponentApi( publicKey = publicKey, environment = environment, context = context, + baseUrlPrefix = baseUrlPrefix, ), ), ) diff --git a/frames/src/main/java/com/checkout/frames/di/component/FramesDIComponent.kt b/frames/src/main/java/com/checkout/frames/di/component/FramesDIComponent.kt index a87f2ae59..e8b37e668 100644 --- a/frames/src/main/java/com/checkout/frames/di/component/FramesDIComponent.kt +++ b/frames/src/main/java/com/checkout/frames/di/component/FramesDIComponent.kt @@ -68,6 +68,9 @@ internal abstract class FramesDIComponent { @BindsInstance fun prefillData(cardHolderName: PrefillData?): Builder + @BindsInstance + fun baseUrlPrefix(baseUrlPrefix: String?): Builder + fun build(): FramesDIComponent } } diff --git a/frames/src/main/java/com/checkout/frames/di/injector/FramesInjector.kt b/frames/src/main/java/com/checkout/frames/di/injector/FramesInjector.kt index 8e8be319c..30741e5ab 100644 --- a/frames/src/main/java/com/checkout/frames/di/injector/FramesInjector.kt +++ b/frames/src/main/java/com/checkout/frames/di/injector/FramesInjector.kt @@ -56,6 +56,7 @@ internal class FramesInjector(private val component: FramesDIComponent) : Inject environment: Environment, paymentFlowHandler: PaymentFlowHandler, supportedCardSchemeList: List = emptyList(), + baseUrlPrefix: String?, prefillData: PrefillData? = null, ): Injector { val logger = EventLoggerProvider.provide().apply { @@ -64,7 +65,7 @@ internal class FramesInjector(private val component: FramesDIComponent) : Inject } val closePaymentFlowUseCase = ClosePaymentFlowUseCase(paymentFlowHandler::onBackPressed) val cardTokenizationUseCase = CardTokenizationUseCase( - CheckoutApiServiceFactory.create(publicKey, environment, context), + CheckoutApiServiceFactory.create(publicKey, environment, context, baseUrlPrefix), paymentFlowHandler::onSubmit, paymentFlowHandler::onSuccess, paymentFlowHandler::onFailure, @@ -76,6 +77,7 @@ internal class FramesInjector(private val component: FramesDIComponent) : Inject .closePaymentFlowUseCase(closePaymentFlowUseCase) .supportedCardSchemes(supportedCardSchemeList) .prefillData(prefillData) + .baseUrlPrefix(baseUrlPrefix) .build(), ) } diff --git a/frames/src/main/java/com/checkout/frames/screen/paymentform/PaymentFormScreen.kt b/frames/src/main/java/com/checkout/frames/screen/paymentform/PaymentFormScreen.kt index 946a88d21..eaaa2fbd7 100644 --- a/frames/src/main/java/com/checkout/frames/screen/paymentform/PaymentFormScreen.kt +++ b/frames/src/main/java/com/checkout/frames/screen/paymentform/PaymentFormScreen.kt @@ -25,6 +25,7 @@ internal fun PaymentFormScreen(config: PaymentFormConfig) { paymentFlowHandler = config.paymentFlowHandler, supportedCardSchemes = config.supportedCardSchemeList, prefillData = config.prefillData, + baseUrlPrefix = config.baseUrlPrefix, ), ) diff --git a/frames/src/main/java/com/checkout/frames/screen/paymentform/PaymentFormViewModel.kt b/frames/src/main/java/com/checkout/frames/screen/paymentform/PaymentFormViewModel.kt index fad11ff82..bbe40b896 100644 --- a/frames/src/main/java/com/checkout/frames/screen/paymentform/PaymentFormViewModel.kt +++ b/frames/src/main/java/com/checkout/frames/screen/paymentform/PaymentFormViewModel.kt @@ -22,6 +22,7 @@ internal class PaymentFormViewModel @Inject internal constructor() : ViewModel() private val environment: Environment, private val paymentFlowHandler: PaymentFlowHandler, private val supportedCardSchemes: List = emptyList(), + private val baseUrlPrefix: String?, private val prefillData: PrefillData? = null, ) : ViewModelProvider.Factory, InjectionClient { @@ -33,7 +34,7 @@ internal class PaymentFormViewModel @Inject internal constructor() : ViewModel() @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { injector = FramesInjector.create( - publicKey, context, environment, paymentFlowHandler, supportedCardSchemes, prefillData, + publicKey, context, environment, paymentFlowHandler, supportedCardSchemes, baseUrlPrefix, prefillData, ) injector.inject(this) diff --git a/frames/src/main/java/com/checkout/frames/screen/paymentform/model/PaymentFormConfig.kt b/frames/src/main/java/com/checkout/frames/screen/paymentform/model/PaymentFormConfig.kt index 99c3a0e51..cec579d7c 100644 --- a/frames/src/main/java/com/checkout/frames/screen/paymentform/model/PaymentFormConfig.kt +++ b/frames/src/main/java/com/checkout/frames/screen/paymentform/model/PaymentFormConfig.kt @@ -14,6 +14,7 @@ import com.checkout.frames.style.screen.PaymentFormStyle * @param paymentFlowHandler - [PaymentFlowHandler] represent the handler for PaymentForm * @param supportedCardSchemeList - represent the supported card schemes [CardScheme] in PaymentForm * @param prefillData - [PrefillData] represent the data for prefill in the PaymentForm + * @param baseUrlPrefix - an optional alphanumeric prefix used to route requests to a specific regional or merchant-specific subdomain (e.g., "msdd"). Must be alphanumeric. */ public data class PaymentFormConfig @JvmOverloads constructor( public val publicKey: String, @@ -23,4 +24,5 @@ public data class PaymentFormConfig @JvmOverloads constructor( public val paymentFlowHandler: PaymentFlowHandler, public var supportedCardSchemeList: List = emptyList(), public val prefillData: PrefillData? = null, + public val baseUrlPrefix: String? = null, ) diff --git a/frames/src/test/java/com/checkout/frames/cvvinputfield/CVVComponentApiFactoryTest.kt b/frames/src/test/java/com/checkout/frames/cvvinputfield/CVVComponentApiFactoryTest.kt index 430ee589a..21e2417ac 100644 --- a/frames/src/test/java/com/checkout/frames/cvvinputfield/CVVComponentApiFactoryTest.kt +++ b/frames/src/test/java/com/checkout/frames/cvvinputfield/CVVComponentApiFactoryTest.kt @@ -24,7 +24,7 @@ internal class CVVComponentApiFactoryTest { @Test fun `when CVVComponentApiFactory is created then InternalCVVComponentApi is correctly created`() { // When - val cvvComponentApi = CVVComponentApiFactory.create(publicKey, environment, context) + val cvvComponentApi = CVVComponentApiFactory.create(publicKey, environment, context, null) // Then assertEquals(InternalCVVComponentApi::class.java, cvvComponentApi.javaClass) diff --git a/frames/src/test/java/com/checkout/frames/cvvinputfield/InternalCVVComponentApiTest.kt b/frames/src/test/java/com/checkout/frames/cvvinputfield/InternalCVVComponentApiTest.kt index d36940548..d3b5f614d 100644 --- a/frames/src/test/java/com/checkout/frames/cvvinputfield/InternalCVVComponentApiTest.kt +++ b/frames/src/test/java/com/checkout/frames/cvvinputfield/InternalCVVComponentApiTest.kt @@ -38,6 +38,7 @@ internal class InternalCVVComponentApiTest { publicKey = "your_public_key", environment = Environment.SANDBOX, context = mockContext, + baseUrlPrefix = null, ), ) diff --git a/frames/src/test/java/com/checkout/frames/mock/PaymentFormConfigTestData.kt b/frames/src/test/java/com/checkout/frames/mock/PaymentFormConfigTestData.kt index 298b1d321..af690eed6 100644 --- a/frames/src/test/java/com/checkout/frames/mock/PaymentFormConfigTestData.kt +++ b/frames/src/test/java/com/checkout/frames/mock/PaymentFormConfigTestData.kt @@ -36,6 +36,7 @@ internal object PaymentFormConfigTestData { val style = PaymentFormStyle() val supportedCardSchemes = listOf(CardScheme.VISA, CardScheme.MAESTRO) const val publicKey = "Test key" + val baseUrlPrefix = "prefix" @Suppress("EmptyFunctionBlock") val paymentFlowHandler = object : PaymentFlowHandler { diff --git a/frames/src/test/java/com/checkout/frames/screen/paymentform/PaymentFormConfigTest.kt b/frames/src/test/java/com/checkout/frames/screen/paymentform/PaymentFormConfigTest.kt index fc5ad0bee..b9a25d159 100644 --- a/frames/src/test/java/com/checkout/frames/screen/paymentform/PaymentFormConfigTest.kt +++ b/frames/src/test/java/com/checkout/frames/screen/paymentform/PaymentFormConfigTest.kt @@ -34,6 +34,7 @@ internal class PaymentFormConfigTest { style = PaymentFormStyle(), supportedCardSchemeList = listOf(CardScheme.VISA, CardScheme.MAESTRO), paymentFlowHandler = PaymentFormConfigTestData.paymentFlowHandler, + baseUrlPrefix = "prefix", prefillData = PrefillData( cardHolderName = "Test Name", billingFormAddress = BillingFormAddress( @@ -61,6 +62,7 @@ internal class PaymentFormConfigTest { assertEquals(PaymentFormConfigTestData.publicKey, publicKey) assertEquals(expectedMockContext, context) assertEquals(PaymentFormConfigTestData.supportedCardSchemes, supportedCardSchemeList) + assertEquals(PaymentFormConfigTestData.baseUrlPrefix, baseUrlPrefix) prefillData?.billingFormAddress.let { billingFormAddress -> assertEquals(PaymentFormConfigTestData.prefillData.cardHolderName, prefillData?.cardHolderName) diff --git a/frames/src/test/java/com/checkout/frames/screen/paymentform/PaymentFormViewModelTest.kt b/frames/src/test/java/com/checkout/frames/screen/paymentform/PaymentFormViewModelTest.kt index dc3f51f68..9f8782413 100644 --- a/frames/src/test/java/com/checkout/frames/screen/paymentform/PaymentFormViewModelTest.kt +++ b/frames/src/test/java/com/checkout/frames/screen/paymentform/PaymentFormViewModelTest.kt @@ -29,6 +29,7 @@ internal class PaymentFormViewModelTest { paymentFlowHandler = PaymentFormConfigTestData.paymentFlowHandler, supportedCardSchemes = PaymentFormConfigTestData.supportedCardSchemes, prefillData = PaymentFormConfigTestData.prefillData, + baseUrlPrefix = null, ) // When