Skip to content

[PIMOB-4287]: adding base url prefix to Frames SDK for multi-region#302

Open
zane-smith-cko wants to merge 12 commits intomasterfrom
feature/PIMOB-4287_add_subdomain_prefix
Open

[PIMOB-4287]: adding base url prefix to Frames SDK for multi-region#302
zane-smith-cko wants to merge 12 commits intomasterfrom
feature/PIMOB-4287_add_subdomain_prefix

Conversation

@zane-smith-cko
Copy link

@zane-smith-cko zane-smith-cko commented Mar 6, 2026

Issue

PIMOB-4287

Proposed changes

Added baseUrlPrefix as a parameter for Frames configuration

  • Example app subdomain base url now uses 'global'
  • If baseUrlPrefix's sub domain is not alphanumeric then we do not accept it and we use the standard environment set URL without a prefix.
  • PK + SK has been updated.

Test Steps

  1. Pay with via card or via CVV
  2. Observe API call being made from global subdomain.

Checklist

Put an x in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code.

  • Reviewers assigned
  • I have performed a self-review of my code and manual testing
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works
  • I have added necessary documentation (if applicable)

Further comments

Screen.Recording.2026-03-09.at.14.20.32.mov

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for an optional subdomain/base-URL prefix so tokenization requests can be routed via a regional/merchant-specific subdomain, and wires this through Frames + CVV flows with updated samples and tests.

Changes:

  • Introduces baseUrlPrefix in Frames configuration/DI and propagates it down to CheckoutApiServiceFactory.
  • Adds Environment.toBaseUrl(...) URL building with prefix validation and corresponding unit tests.
  • Updates example apps and unit tests to pass the new prefix argument(s).

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
frames/src/test/java/com/checkout/frames/screen/paymentform/PaymentFormViewModelTest.kt Updates test setup to include baseUrlPrefix when constructing the VM factory.
frames/src/test/java/com/checkout/frames/screen/paymentform/PaymentFormConfigTest.kt Extends config test coverage to include baseUrlPrefix.
frames/src/test/java/com/checkout/frames/mock/PaymentFormConfigTestData.kt Adds test data constant for baseUrlPrefix.
frames/src/test/java/com/checkout/frames/cvvinputfield/InternalCVVComponentApiTest.kt Updates CVV API test to include baseUrlPrefix.
frames/src/test/java/com/checkout/frames/cvvinputfield/CVVComponentApiFactoryTest.kt Updates factory test for new create(...) signature.
frames/src/main/java/com/checkout/frames/screen/paymentform/model/PaymentFormConfig.kt Adds public config field baseUrlPrefix.
frames/src/main/java/com/checkout/frames/screen/paymentform/PaymentFormViewModel.kt Threads baseUrlPrefix into injector creation.
frames/src/main/java/com/checkout/frames/screen/paymentform/PaymentFormScreen.kt Passes config baseUrlPrefix into VM factory.
frames/src/main/java/com/checkout/frames/di/injector/FramesInjector.kt Threads baseUrlPrefix into CheckoutApiServiceFactory.create(...) and DI builder.
frames/src/main/java/com/checkout/frames/di/component/FramesDIComponent.kt Adds @BindsInstance baseUrlPrefix to Dagger builder.
frames/src/main/java/com/checkout/frames/cvvinputfield/api/InternalCVVComponentApi.kt Threads baseUrlPrefix into CheckoutApiServiceFactory.create(...) for CVV tokenization.
frames/src/main/java/com/checkout/frames/cvvinputfield/CVVComponentApiFactory.kt Extends CVV factory API to accept baseUrlPrefix.
example_app_frames/src/main/java/com/checkout/example/frames/ui/utils/Constants.kt Adds demo constant for regional subdomain and updates demo keys.
example_app_frames/src/main/java/com/checkout/example/frames/ui/screen/MainActivity.kt Passes regional subdomain into PaymentFormConfig.
example_app_frames/src/main/java/com/checkout/example/frames/ui/screen/HomeScreen.kt Passes regional subdomain into CheckoutApiServiceFactory.create(...).
example_app_frames/src/main/java/com/checkout/example/frames/ui/screen/CVVTokenizationScreen.kt Passes regional subdomain into CVVComponentApiFactory.create(...).
checkout/src/test/java/com/checkout/EnvironmentExtensionTest.kt Adds unit tests for Environment.toBaseUrl(...) behavior.
checkout/src/test/java/com/checkout/CheckoutApiServiceFactoryTest.kt Updates factory test for new signature.
checkout/src/main/java/com/checkout/logging/utils/EnvironmentExtension.kt Adds Environment.toBaseUrl(...) and prefix validation helper.
checkout/src/main/java/com/checkout/base/model/Environment.kt Refactors Environment enum to remove url field.
checkout/src/main/java/com/checkout/CheckoutApiServiceFactory.kt Adds baseUrlPrefix parameter and uses Environment.toBaseUrl(...) for tokenization base URL.
app/src/main/java/checkout/checkout_android/DemoActivity.java Updates demo to pass regional subdomain into PaymentFormConfig.
app/src/main/java/checkout/checkout_android/Constants.java Adds regional subdomain constant and updates demo keys.
app/src/main/java/checkout/checkout_android/CVVTokenizationActivity.java Updates demo to pass regional subdomain into CVVComponentApiFactory.create(...).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

SANDBOX(EnvironmentConstants.SANDBOX_SERVER_URL),
public enum class Environment {
PRODUCTION,
SANDBOX
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the public url property from Environment is a breaking API change for SDK consumers that may rely on Environment.url. Consider keeping a (possibly deprecated) url property (backed by the current default tokenization URL) or exposing a public replacement for retrieving the base URL.

Suggested change
SANDBOX
SANDBOX;
@Deprecated(
message = "Environment.url is deprecated. Use the appropriate configuration or tokenization URL accessor instead."
)
public val url: String
get() = when (this) {
PRODUCTION -> PRODUCTION_URL
SANDBOX -> SANDBOX_URL
}
private companion object {
private const val PRODUCTION_URL: String = "https://api.checkout.com/tokens"
private const val SANDBOX_URL: String = "https://api.sandbox.checkout.com/tokens"
}

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this meant to be publicly exposed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for merchants. It was for us to use in frames module while creating TokenRepositoryImpl

@zane-smith-cko zane-smith-cko changed the title Feature/pimob 4287 add subdomain prefix [PIMOB-4287]: adding base url prefix to Frames SDK for multi-region Mar 9, 2026
@zane-smith-cko zane-smith-cko force-pushed the feature/PIMOB-4287_add_subdomain_prefix branch from a274ee9 to 3dd091f Compare March 9, 2026 10:58
@zane-smith-cko zane-smith-cko marked this pull request as ready for review March 9, 2026 14:23
@zane-smith-cko zane-smith-cko requested a review from a team as a code owner March 9, 2026 14:23
Environment.PRODUCTION -> "production"
Environment.SANDBOX -> "sandbox"
Environment.PRODUCTION ->
"production"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"production" and "sandbox" should be constants


internal fun Environment.toBaseUrl(baseUrlPrefix: String? = null) = when (this) {
Environment.PRODUCTION -> baseUrlPrefix?.baseUrlPrefixValidator()?.let {
"https://$baseUrlPrefix.api.checkout.com/tokens"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should use the PRODUCTION/SANDBOX_SERVER_URL to build this so that if they change we don't need to remember to update the ones with baseUrlPrefx. Find a good way either splitting the base SERVER_URLS at the :// delimiter or maybe Uri.create()

)
baseUrlPrefix: String?,
): TokenRepository {
return TokenRepositoryImpl(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did linter ask for this change or was it for debugging?

}

private fun String?.baseUrlPrefixValidator() = this
?.takeIf {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. isLetterOrDigit allows chars like π or é and such, and this is strictly part of an url so we should check if prefix.all { char -> char in 'a'..'z' || char in 'A'..'Z' || char '0'..'9' }
  2. Same as the comment re tests: let's check if - _ and . could be valid here

val invalidPrefixList = listOf(
"invalid_prefix",
"invalid-prefix",
"invalid.prefix",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the top 3 look like they could be valid prefixes - worth checking

@@ -223,6 +224,7 @@ fun invokeCheckoutSDKToGenerateTokenForGooglePay(context: Context) {
"pk_test_6e40a700-d563-43cd-89d0-f9bb17d35e73",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be invalid and needing replacement

Copy link

@adrian-orlowski-cko adrian-orlowski-cko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM
Thank you for rotating the keys!

Comment on lines +18 to +21
/**
* Constants for environments
*/
internal const val HTTPS_PROTOCOL: String = "https://"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/**
* Constants for environments
*/
internal const val HTTPS_PROTOCOL: String = "https://"
/**
* Constant for HTTPS protocol prefix, used when constructing environments' base URLs for network communication.
*/
internal const val HTTPS_PROTOCOL: String = "https://"

internal fun Environment.toBaseUrl(baseUrlPrefix: String? = null) = when (this) {
Environment.PRODUCTION -> PRODUCTION_SERVER_URL.applyPrefix(baseUrlPrefix)
Environment.SANDBOX -> SANDBOX_SERVER_URL.applyPrefix(baseUrlPrefix)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nit for formatting consistency between functions' spacing

Suggested change
}
}

return this.replace(HTTPS_PROTOCOL, "$HTTPS_PROTOCOL$validatedPrefix.")
}

private fun String?.baseUrlPrefixValidator() = this
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function is declared as an extension on String?, but it's always called via the safe-call operator (prefix?.baseUrlPrefixValidator()), meaning this will never actually be null inside the function. Do we need it?


private fun String?.baseUrlPrefixValidator() = this
?.takeIf { prefix ->
prefix.all { char -> char in 'a'..'z' || char in 'A'..'Z' || char in '0'..'9' } && prefix.isNotEmpty()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reorder the isNotEmpty() check to come first. In the validator, prefix.all { ... } returns true for an empty string (vacuous truth in Kotlin). The logic is correct because && prefix.isNotEmpty() catches it, but flipping the order is both more efficient (short-circuits immediately on empty input) and reads more naturally.


private fun String?.baseUrlPrefixValidator() = this
?.takeIf { prefix ->
prefix.all { char -> char in 'a'..'z' || char in 'A'..'Z' || char in '0'..'9' } && prefix.isNotEmpty()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validator only allows [a-zA-Z0-9]. Per RFC 1123, valid subdomain labels can also contain hyphens (-), e.g., my-subdomain. If this restriction is intentional, it might be worth a brief comment explaining why hyphens are excluded. If not, the range check should include char == '-'.

* @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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @param baseUrlPrefix - an optional alphanumeric prefix used to route requests to a specific regional or merchant-specific subdomain (e.g., "msdd"). Must be alphanumeric.
* @param baseUrlPrefix - an optional alphanumeric prefix used to route requests to a specific regional or merchant-specific subdomain (e.g., "msdd"). Must be alphanumeric.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants