diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 87a26e910..6511d1148 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -165,6 +165,23 @@ If your change intentionally modifies the public API:
If you did _not_ intend to change public API and `apiCheck` is failing, the diff shows what your change inadvertently affected — treat it as a signal that something in your PR has consumer-visible impact.
+### Releasing a new Embedded Checkout Protocol version
+
+Open a pull request with the following changes:
+
+1. Bump `embeddedCheckoutProtocolAndroid` in `platforms/android/gradle/libs.versions.toml`.
+2. Update `protocol/languages/kotlin/embedded-checkout-protocol/api/embedded-checkout-protocol.api` if the public protocol API changed.
+
+Supported protocol release versions are `YYYY.MM.DD.PATCH` and prerelease versions are `YYYY.MM.DD.PATCH-{alpha|beta|rc}.N`.
+
+Once merged, run the [Release package workflow](../../actions/workflows/release.yml):
+
+1. Select `Embedded Checkout Protocol` as the platform.
+2. Enter the expected version. The workflow reads the protocol version from `platforms/android/gradle/libs.versions.toml` and fails if the typed version does not match.
+3. Select `Dry run` first to review the release plan without creating a release.
+4. Rerun with `Draft release` to create a draft GitHub Release with the `embedded-checkout-protocol/`-prefixed tag (e.g. `embedded-checkout-protocol/2026.04.08.1-alpha.1`) for human review.
+5. Publish the draft release when ready. Publishing the draft kicks off the [Embedded Checkout Protocol publish workflow](../../actions/workflows/android-protocol-publish.yml). **A manual approval by a maintainer is required before publication to Maven Central.**
+
### Releasing a new Android version
Open a pull request with the following changes:
@@ -172,6 +189,7 @@ Open a pull request with the following changes:
1. Bump `checkoutKitAndroid` in `platforms/android/gradle/libs.versions.toml`.
2. Add an entry to the top of `platforms/android/CHANGELOG.md`.
3. Update `checkoutKit.nativeSdkVersions.android` in `platforms/react-native/modules/@shopify/checkout-kit-react-native/package.json` to the same lowercase SemVer.
+4. If the Android Kit release depends on a new protocol version, release `embeddedCheckoutProtocolAndroid` first.
Supported release versions are `X.Y.Z` and prerelease versions are `X.Y.Z-{alpha|beta|rc}.N`.
@@ -181,7 +199,7 @@ Once merged, run the [Release package workflow](../../actions/workflows/release.
2. Enter the expected version. The workflow reads the SDK version from `platforms/android/gradle/libs.versions.toml` and fails if the typed version does not match.
3. Select `Dry run` first to review the release plan without creating a release.
4. Rerun with `Draft release` to create a draft GitHub Release with the `android/`-prefixed tag (e.g. `android/3.0.1`) for human review.
-5. Publish the draft release when ready. Publishing the draft kicks off the [Android publish workflow](../../actions/workflows/android-publish.yml). **A manual approval by a maintainer is required before publication to Maven Central.**
+5. Publish the draft release when ready. Publishing the draft kicks off the [Android publish workflow](../../actions/workflows/android-publish.yml). The workflow verifies that `com.shopify:embedded-checkout-protocol` is already available on Maven Central before publishing `com.shopify:checkout-kit`. **A manual approval by a maintainer is required before publication to Maven Central.**
---
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 72ad035a0..305e24431 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -29,6 +29,14 @@
+
+Releasing a new Embedded Checkout Protocol version?
+
+- [ ] I have bumped `embeddedCheckoutProtocolAndroid` in `platforms/android/gradle/libs.versions.toml`
+- [ ] I have updated `protocol/languages/kotlin/embedded-checkout-protocol/api/embedded-checkout-protocol.api` if the public API changed
+
+
+
Releasing a new Android version?
diff --git a/.github/scripts/validate-release-version b/.github/scripts/validate-release-version
index e3d07fb88..e70f58b63 100755
--- a/.github/scripts/validate-release-version
+++ b/.github/scripts/validate-release-version
@@ -8,7 +8,7 @@ Usage: validate-release-version [expected-version] [expected-tag]
Validates the selected SDK's checked-in version declarations, optional user
version input, optional git tag, and prints GitHub Actions outputs to stdout.
-Platforms: iOS, Android, React Native
+Platforms: iOS, Android, Embedded Checkout Protocol, React Native
USAGE
}
@@ -22,6 +22,12 @@ EXPECTED_VERSION="${2:-}"
EXPECTED_TAG="${3:-}"
SEMVER_REGEX='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-(alpha|beta|rc)\.(0|[1-9][0-9]*))?$'
+CALVER_REGEX='^[0-9]{4}\.(0[1-9]|1[0-2])\.(0[1-9]|[12][0-9]|3[01])\.(0|[1-9][0-9]*)(-(alpha|beta|rc)\.(0|[1-9][0-9]*))?$'
+
+# Most release targets are SemVer. Embedded Checkout Protocol overrides these
+# defaults to CalVer in its platform branch below.
+VERSION_REGEX="$SEMVER_REGEX"
+VERSION_FORMAT="X.Y.Z or X.Y.Z-{alpha|beta|rc}.N"
extract_first_match() {
local file="$1"
@@ -109,11 +115,26 @@ case "$PLATFORM_INPUT" in
RN_PACKAGE_FILE="platforms/react-native/modules/@shopify/checkout-kit-react-native/package.json"
VERSION=$(version_catalog_value "$ANDROID_VERSION_FILE" "checkoutKitAndroid")
+ ANDROID_PROTOCOL_VERSION=$(version_catalog_value "$ANDROID_VERSION_FILE" "embeddedCheckoutProtocolAndroid")
RN_ANDROID_VERSION=$(json_string_field "$RN_PACKAGE_FILE" '.checkoutKit.nativeSdkVersions.android')
check_same_version "$VERSION" "$RN_PACKAGE_FILE" "$RN_ANDROID_VERSION"
;;
+ "Embedded Checkout Protocol"|embedded-checkout-protocol|EmbeddedCheckoutProtocol|ecp|ECP)
+ PLATFORM="embedded-checkout-protocol"
+ DISPLAY_PLATFORM="Embedded Checkout Protocol"
+ RELEASE_TITLE_PREFIX="Embedded Checkout Protocol"
+ TAG_PREFIX="embedded-checkout-protocol/"
+ PUBLISH_WORKFLOW="android-protocol-publish.yml"
+ VERSION_REGEX="$CALVER_REGEX"
+ VERSION_FORMAT="YYYY.MM.DD.PATCH or YYYY.MM.DD.PATCH-{alpha|beta|rc}.N"
+
+ ANDROID_VERSION_FILE="platforms/android/gradle/libs.versions.toml"
+
+ VERSION=$(version_catalog_value "$ANDROID_VERSION_FILE" "embeddedCheckoutProtocolAndroid")
+ ;;
+
"React Native"|react-native|ReactNative|rn|RN)
PLATFORM="react-native"
DISPLAY_PLATFORM="React Native"
@@ -126,18 +147,23 @@ case "$PLATFORM_INPUT" in
;;
*)
- echo "::error::Unsupported platform '$PLATFORM_INPUT'. Expected one of: iOS, Android, React Native." >&2
+ echo "::error::Unsupported platform '$PLATFORM_INPUT'. Expected one of: iOS, Android, Embedded Checkout Protocol, React Native." >&2
exit 1
;;
esac
-if [[ ! "$VERSION" =~ $SEMVER_REGEX ]]; then
- echo "::error::${DISPLAY_PLATFORM} SDK version '$VERSION' is invalid. Expected X.Y.Z or X.Y.Z-{alpha|beta|rc}.N." >&2
+if [[ ! "$VERSION" =~ $VERSION_REGEX ]]; then
+ echo "::error::${DISPLAY_PLATFORM} version '$VERSION' is invalid. Expected $VERSION_FORMAT." >&2
+ exit 1
+fi
+
+if [ -n "${ANDROID_PROTOCOL_VERSION:-}" ] && [[ ! "$ANDROID_PROTOCOL_VERSION" =~ $CALVER_REGEX ]]; then
+ echo "::error::Embedded Checkout Protocol version '$ANDROID_PROTOCOL_VERSION' is invalid. Expected YYYY.MM.DD.PATCH or YYYY.MM.DD.PATCH-{alpha|beta|rc}.N." >&2
exit 1
fi
if [ -n "$EXPECTED_VERSION" ] && [ "$EXPECTED_VERSION" != "$VERSION" ]; then
- echo "::error::Requested version '$EXPECTED_VERSION' does not match ${DISPLAY_PLATFORM} SDK version '$VERSION'. Bump the SDK version in a PR first, or rerun with '$VERSION'." >&2
+ echo "::error::Requested version '$EXPECTED_VERSION' does not match ${DISPLAY_PLATFORM} version '$VERSION'. Bump the version in a PR first, or rerun with '$VERSION'." >&2
exit 1
fi
@@ -145,7 +171,7 @@ TAG="${TAG_PREFIX}${VERSION}"
RELEASE_TITLE="[${RELEASE_TITLE_PREFIX}] ${VERSION}"
if [ -n "$EXPECTED_TAG" ] && [ "$EXPECTED_TAG" != "$TAG" ]; then
- echo "::error::Git tag '$EXPECTED_TAG' does not match ${DISPLAY_PLATFORM} SDK version '$VERSION'. Expected tag '$TAG'." >&2
+ echo "::error::Git tag '$EXPECTED_TAG' does not match ${DISPLAY_PLATFORM} version '$VERSION'. Expected tag '$TAG'." >&2
exit 1
fi
@@ -166,6 +192,9 @@ fi
echo "publish_workflow=$PUBLISH_WORKFLOW"
echo "prerelease=$PRERELEASE"
echo "npm_tag=$NPM_TAG"
+ if [ -n "${ANDROID_PROTOCOL_VERSION:-}" ]; then
+ echo "android_protocol_version=$ANDROID_PROTOCOL_VERSION"
+ fi
}
echo "✓ ${DISPLAY_PLATFORM} version '$VERSION' validates and maps to tag '$TAG'." >&2
diff --git a/.github/workflows/android-protocol-publish.yml b/.github/workflows/android-protocol-publish.yml
new file mode 100644
index 000000000..e2a562121
--- /dev/null
+++ b/.github/workflows/android-protocol-publish.yml
@@ -0,0 +1,91 @@
+name: Embedded Checkout Protocol — Publish to Maven Central
+
+on:
+ release:
+ types:
+ - published
+ workflow_dispatch:
+
+jobs:
+ build:
+ # Only run for Embedded Checkout Protocol releases. Protocol releases are
+ # tagged `embedded-checkout-protocol/YYYY.MM.DD.PATCH[-prerelease.N]`.
+ if: ${{ github.event_name == 'workflow_dispatch' || startsWith(github.event.release.tag_name, 'embedded-checkout-protocol/') }}
+ environment:
+ name: Central Repository Deployment
+
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+
+ env:
+ OSSRH_API_BASE_URL: https://ossrh-staging-api.central.sonatype.com
+
+ defaults:
+ run:
+ working-directory: protocol/languages/kotlin
+
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
+ with:
+ submodules: true
+
+ - name: Validate release tag matches protocol version
+ working-directory: ${{ github.workspace }}
+ env:
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
+ run: |
+ set -euo pipefail
+ if [ "$GITHUB_EVENT_NAME" = "release" ]; then
+ TAG="$RELEASE_TAG"
+ else
+ TAG="$GITHUB_REF_NAME"
+ fi
+
+ .github/scripts/validate-release-version "Embedded Checkout Protocol" "" "$TAG"
+
+ - name: Install JDK 1.17
+ uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+ with:
+ distribution: zulu
+ java-version: 17
+
+ - name: Publish Package
+ run: ./gradlew :embedded-checkout-protocol:publishReleasePublicationToOssrh-staging-apiRepository
+ env:
+ OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
+ OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
+ ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.OSSRH_GPG_SECRET_KEY_ID }}
+ ORG_GRADLE_PROJECT_signingKey: ${{ secrets.OSSRH_GPG_SECRET_KEY }}
+ ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }}
+
+ - name: Find OSSRH Repository
+ id: find-repo
+ run: |
+ TOKEN=$(echo -n "${{ secrets.OSSRH_USERNAME }}:${{ secrets.OSSRH_PASSWORD }}" | base64)
+ RESPONSE=$(curl -s -H "Authorization: Bearer $TOKEN" \
+ "${OSSRH_API_BASE_URL}/manual/search/repositories?ip=any&state=open")
+ echo "Response: $RESPONSE"
+
+ REPO_KEY=$(echo "$RESPONSE" | jq -r \
+ '.repositories[] | .key' | head -1)
+ echo "Repository key: $REPO_KEY"
+
+ if [ -z "$REPO_KEY" ]; then
+ echo "Error: No open repository found"
+ exit 1
+ fi
+
+ echo "repo_key=$REPO_KEY" >> $GITHUB_OUTPUT
+
+ - name: Upload Repository to Central Portal
+ run: |
+ TOKEN=$(echo -n "${{ secrets.OSSRH_USERNAME }}:${{ secrets.OSSRH_PASSWORD }}" | base64)
+ REPO_KEY="${{ steps.find-repo.outputs.repo_key }}"
+
+ echo "Uploading repository: $REPO_KEY"
+ curl -v -X POST -H "Authorization: Bearer $TOKEN" \
+ "${OSSRH_API_BASE_URL}/manual/upload/repository/$REPO_KEY"
+
+ echo "Upload completed successfully"
diff --git a/.github/workflows/android-publish.yml b/.github/workflows/android-publish.yml
index c7362a9f3..ffd496130 100644
--- a/.github/workflows/android-publish.yml
+++ b/.github/workflows/android-publish.yml
@@ -32,6 +32,7 @@ jobs:
submodules: true
- name: Validate release tag matches Android version
+ id: release
working-directory: ${{ github.workspace }}
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
@@ -43,7 +44,21 @@ jobs:
TAG="$GITHUB_REF_NAME"
fi
- .github/scripts/validate-release-version Android "" "$TAG"
+ .github/scripts/validate-release-version Android "" "$TAG" >> "$GITHUB_OUTPUT"
+
+ - name: Verify Embedded Checkout Protocol is published
+ env:
+ PROTOCOL_VERSION: ${{ steps.release.outputs.android_protocol_version }}
+ run: |
+ set -euo pipefail
+ POM_URL="https://repo.maven.apache.org/maven2/com/shopify/embedded-checkout-protocol/${PROTOCOL_VERSION}/embedded-checkout-protocol-${PROTOCOL_VERSION}.pom"
+
+ if ! curl -fsSL --retry 5 --retry-delay 10 --retry-all-errors -o /dev/null "$POM_URL"; then
+ echo "::error::com.shopify:embedded-checkout-protocol:${PROTOCOL_VERSION} is not available on Maven Central. Publish embedded-checkout-protocol/${PROTOCOL_VERSION} before releasing Android Kit."
+ exit 1
+ fi
+
+ echo "::notice::Found com.shopify:embedded-checkout-protocol:${PROTOCOL_VERSION} on Maven Central."
- name: Install JDK 1.17
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
@@ -54,7 +69,6 @@ jobs:
- name: Publish Package
run: |
./gradlew \
- :embedded-checkout-protocol:publishReleasePublicationToOssrh-staging-apiRepository \
:lib:publishReleasePublicationToOssrh-staging-apiRepository
env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 62339340f..634f2b88d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -42,8 +42,12 @@ jobs:
android:
- 'platforms/android/**'
- 'protocol/**'
+ - '.github/workflows/android-publish.yml'
+ - '.github/workflows/android-protocol-publish.yml'
- '.github/workflows/android-test.yml'
- '.github/workflows/breaking-changes.yml'
+ - '.github/workflows/release.yml'
+ - '.github/scripts/validate-release-version'
- '.github/workflows/ci.yml'
swift:
- 'platforms/swift/**'
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index fac2bd1b2..42952dc54 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -10,9 +10,10 @@ on:
options:
- iOS
- Android
+ - Embedded Checkout Protocol
- React Native
version:
- description: Expected SDK version. Must match the checked-in SDK version for the selected platform.
+ description: Expected package version. Must match the checked-in version for the selected platform.
required: true
type: string
mode:
@@ -88,11 +89,15 @@ jobs:
RELEASE_TITLE: ${{ steps.release.outputs.release_title }}
PRERELEASE: ${{ steps.release.outputs.prerelease }}
PUBLISH_WORKFLOW: ${{ steps.release.outputs.publish_workflow }}
+ ANDROID_PROTOCOL_VERSION: ${{ steps.release.outputs.android_protocol_version }}
run: |
set -euo pipefail
echo "Release plan:"
echo " Platform: ${DISPLAY_PLATFORM}"
echo " Version: ${VERSION}"
+ if [ -n "$ANDROID_PROTOCOL_VERSION" ]; then
+ echo " Embedded Checkout Protocol version: ${ANDROID_PROTOCOL_VERSION}"
+ fi
echo " Tag: ${TAG}"
echo " Title: ${RELEASE_TITLE}"
echo " Prerelease: ${PRERELEASE}"
@@ -152,6 +157,7 @@ jobs:
TAG: ${{ steps.release.outputs.tag }}
RELEASE_TITLE: ${{ steps.release.outputs.release_title }}
PUBLISH_WORKFLOW: ${{ steps.release.outputs.publish_workflow }}
+ ANDROID_PROTOCOL_VERSION: ${{ steps.release.outputs.android_protocol_version }}
REF_NAME: ${{ github.ref_name }}
PLATFORM: ${{ inputs.platform }}
run: |
@@ -170,6 +176,12 @@ jobs:
- Publish workflow: \`${PUBLISH_WORKFLOW}\`
SUMMARY
+ if [ -n "$ANDROID_PROTOCOL_VERSION" ]; then
+ cat >> "$GITHUB_STEP_SUMMARY" <> "$GITHUB_STEP_SUMMARY" <