Skip to content

Centralize Kotlin checkout message ingress validation - #645

Merged
markmur merged 2 commits into
mainfrom
kotlin-message-rejection-lifecycle
Aug 19, 2026
Merged

Centralize Kotlin checkout message ingress validation#645
markmur merged 2 commits into
mainfrom
kotlin-message-rejection-lifecycle

Conversation

@markmur

@markmur markmur commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

What changes are you making?

This PR extends the centralized message admission introduced in #644 to Kotlin without adding a coroutines dependency.

Incoming AndroidX WebKit messages now pass through an internal CheckoutMessageIngressPolicy before reaching the checkout protocol client:

val incomingMessage = IncomingCheckoutMessage(
    origin = sourceOrigin,
    isMainFrame = isMainFrame,
)
val ingressPolicy = CheckoutMessageIngressPolicy(
    configuredOrigins = ShopifyCheckoutKit.configuration.allowedMessageOrigins,
    checkoutOrigin = view.checkoutOrigin,
)

when (val decision = ingressPolicy.evaluate(incomingMessage)) {
    CheckoutMessageIngressPolicy.Decision.Accepted -> receiveMessage(message)
    is CheckoutMessageIngressPolicy.Decision.Rejected -> {
        handleMessageRejection(decision.rejection)
    }
}

The policy validates AndroidX WebKit's authenticated source origin and frame metadata before a message reaches the checkout protocol client. ALLOWED_MESSAGE_ORIGIN_RULES intentionally remains "*" so AndroidX WebKit delivers every message with its verified source origin; narrowing the transport rule would silently discard messages before the policy could validate or log them.

Keeping this decision at the native WebView boundary means the protocol client can focus on handling admitted checkout protocol messages rather than transport trust.

Rejection behavior

A rejected message means that specific input was not trusted. It does not mean the checkout session failed: checkout may be displaying an external surface that also posts messages, and unrelated page activity must not be able to terminate an otherwise healthy checkout.

private fun handleMessageRejection(rejection: CheckoutMessageRejection) {
    when (rejection.reason) {
        CheckoutMessageRejection.Reason.CHILD_FRAME ->
            log.d(LOG_TAG, "Ignoring ECP WebMessage from a child frame.")
        CheckoutMessageRejection.Reason.UNSUPPORTED_PORT,
        CheckoutMessageRejection.Reason.ORIGIN_NOT_ALLOWED,
        -> log.w(
            LOG_TAG,
            "Rejected ECP WebMessage from ${rejection.origin}: ${rejection.reason.logDescription}",
        )
    }
}
  • Child-frame messages remain debug-only because payment and 3DS iframes can produce routine bridge traffic.
  • Origin and explicit-port-zero rejections are logged at warning level and dropped.
  • Rejected messages never reach the checkout protocol client.
  • Rejection does not transition PreloadState to Failed or evict the preload.
  • Rejection does not create a CheckoutException or invoke onFail / onCheckoutFailed.
  • The rejection model remains internal because this approach does not expose a public diagnostics stream.

Consumer API

Consumers only configure the origins they trust. The existing lifecycle APIs remain reserved for actual checkout failures:

ShopifyCheckoutKit.configure {
    it.allowedMessageOrigins = setOf(
        "https://checkout.example.com",
        "https://*.example.com",
    )
}

There is no message-rejection subscription. Rejections remain an internal signal that is logged at the appropriate level and dropped.


Before you merge

Important

  • I've added tests to support my implementation
  • I have read and agree with the Contribution Guidelines
  • I have read and agree with the Code of Conduct
  • I've updated the relevant platform README (platforms/swift/README.md and/or platforms/android/README.md)

@github-actions github-actions Bot added the #gsd:50662 Rebase Checkout Kit on UCP label Aug 14, 2026

markmur commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

@markmur
markmur force-pushed the kotlin-message-rejection-lifecycle branch 2 times, most recently from 0db7a62 to 41881d6 Compare August 14, 2026 11:39
@markmur markmur changed the title Route Kotlin message rejection through lifecycle failures Centralize Kotlin checkout message ingress validation Aug 14, 2026
@markmur
markmur force-pushed the swift-message-rejection-lifecycle branch from 63d2a15 to ac84d8f Compare August 14, 2026 11:44
@markmur
markmur force-pushed the kotlin-message-rejection-lifecycle branch from 41881d6 to 94962ec Compare August 14, 2026 11:44
@markmur
markmur marked this pull request as ready for review August 14, 2026 11:47
@markmur
markmur requested a review from a team as a code owner August 14, 2026 11:47
@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown

Package Size

Platform Artifact Base Head Delta
Android release AAR 279.8 KiB 291.2 KiB +11.3 KiB
Android file breakdown
File Base Head Delta
classes.jar 295.6 KiB 308.4 KiB +12.8 KiB
res/layout/checkout_view_content.xml 2.6 KiB 2.6 KiB 0 B
res/layout/checkout_sheet_content.xml 2.0 KiB 2.0 KiB 0 B
res/values/values.xml 1.3 KiB 1.3 KiB 0 B
R.txt 1.2 KiB 1.2 KiB 0 B
AndroidManifest.xml 922 B 922 B 0 B
proguard.txt 798 B 798 B 0 B
res/drawable/close.xml 431 B 431 B 0 B
res/menu/checkout_menu.xml 354 B 354 B 0 B
META-INF/com/android/build/gradle/aar-metadata.properties 157 B 157 B 0 B

Measured from the PR base SHA and PR head SHA. The file breakdown shows uncompressed sizes within each package artifact, so individual files do not sum to the compressed artifact total. This comment reports package artifact sizes only; it is not a final app binary-size report.

@bitrise

bitrise Bot commented Aug 14, 2026

Copy link
Copy Markdown

Install this build

Open Tophat, select your target device, then click Install. Links open on the Mac running Tophat.

SDK Install
Swift Install with Tophat
Kotlin Install with Tophat

Checkout Kit E2E results

Status Suite Target Platform OS version tag Device
kotlin-android kotlin android latest Google Pixel 9
Android 17.0
swift-ios swift ios latest iPhone 15
iOS 27 Beta

@markmur
markmur force-pushed the kotlin-message-rejection-lifecycle branch from 94962ec to ef34984 Compare August 18, 2026 13:02
@markmur
markmur force-pushed the swift-message-rejection-lifecycle branch 2 times, most recently from ee4b8b8 to 56fd72d Compare August 18, 2026 13:25
@markmur
markmur force-pushed the kotlin-message-rejection-lifecycle branch from ef34984 to 496fb50 Compare August 18, 2026 13:25
@markmur markmur self-assigned this Aug 18, 2026
@markmur
markmur requested a review from tiagocandido August 18, 2026 15:19
@markmur
markmur force-pushed the kotlin-message-rejection-lifecycle branch from 496fb50 to a2c216a Compare August 19, 2026 12:58
@markmur
markmur force-pushed the swift-message-rejection-lifecycle branch from 56fd72d to 1dcc9bd Compare August 19, 2026 12:58
@markmur
markmur force-pushed the kotlin-message-rejection-lifecycle branch from a2c216a to 681292c Compare August 19, 2026 13:00
Base automatically changed from swift-message-rejection-lifecycle to main August 19, 2026 13:22
@markmur
markmur merged commit daa6a07 into main Aug 19, 2026
28 checks passed
@markmur
markmur deleted the kotlin-message-rejection-lifecycle branch August 19, 2026 14:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

#gsd:50662 Rebase Checkout Kit on UCP

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants