Skip to content

feat: require the subdomain option or an explicit useLegacyDomain opt-out - #448

Merged
armando-rodriguez-cko merged 9 commits into
masterfrom
feat/INT-1688-mandatory-subdomain
Aug 31, 2026
Merged

feat: require the subdomain option or an explicit useLegacyDomain opt-out#448
armando-rodriguez-cko merged 9 commits into
masterfrom
feat/INT-1688-mandatory-subdomain

Conversation

@armando-rodriguez-cko

@armando-rodriguez-cko armando-rodriguez-cko commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Makes the merchant-specific subdomain (MSSD) mandatory. Initializing without a subdomain already emitted a deprecation warning, so this finishes the job: it now throws a ValueError. Callers must either set subdomain, or explicitly opt out with useLegacyDomain: true, which ships @deprecated in the type definitions. Both, or neither, throws.

Changes

  • src/auth-builder.js — new validateDomainOptions() replaces the two deprecation warnings; the EnvironmentSubdomain is only built once the options are known valid; new isPreviousPlatform()
  • src/config.js — new PREVIOUS_SECRET_KEY_REGEX
  • types/dist/Checkout.d.ts — documents subdomain as required and adds useLegacyDomain, marked @deprecated so editors and tsc flag it
  • test/config/config.js, test/environment-subdomain/environment-subdomain-integration.js — the cases that asserted invalid subdomains being silently dropped now assert the throw; added coverage for the legacy opt-out and for both-set
  • test/transfers, test/hosted-payments, test/payments-links — the clients there now pass the subdomain their own nock host already expected

Two exemptions

A custom host replaces the base URL outright, so the merchant has already said where requests go. Previous (ABC) keys predate merchant-specific subdomains.

Fixed along the way

The Previous exemption needed a new regex: MBC_LIVE_SECRET_KEY_REGEX only matches live keys, so sandbox Previous keys (sk_test_<uuid>) were not recognised as that platform.

Verification

884 tests passing, 0 failing, 79 pending. The one eslint error (prefer-const in src/api/balances/balances.js) is pre-existing on master and untouched here.

API Reference

Breaking changes

Yes, two. This needs a major release, classified and versioned when the release is cut.

  1. The merchant-specific subdomain is mandatory for the Default and DefaultOAuth platforms. Code that omitted it and relied on the implicit fallback to api.checkout.com / access.checkout.com now fails at client construction. Migration: set the subdomain, or use the legacy-domain opt-out as a temporary measure. The Previous (ABC) platform is unaffected.
  2. An invalid subdomain now fails instead of being silently ignored. Callers passing a malformed value keep working against the shared host today; after this change they fail fast. This one is easy to miss because it is not what the ticket asked for, so it needs its own line in the release notes.

README

Updated in this PR: a "Subdomain value" section above the Default example, the subdomain added to the configuration samples, and a "Legacy domain (emergency use only)" section at the bottom.

Notes

The suite routes every client it builds through a single helper that uses the shared hosts. Applying the merchant-specific subdomain there looked better, since it is the path merchants are being moved to, but the sandbox OAuth clients are not provisioned for it: .NET CI failed 224 integration tests with invalid_client when the token request went to {subdomain}.access.sandbox.checkout.com. Binding those OAuth clients to the subdomain is a platform task and should land before merchants are told the subdomain is mandatory.

Reference implementation: checkout-sdk-net#590. Tracked as INT-1688.

No version bump here: that happens on master when the release is cut, per the release workflow.


Review follow-ups (2026-08-31)

Breaking changes, complete list:

  1. The merchant-specific subdomain is now required; building without it (and without the legacy opt-out) throws.
  2. An invalid subdomain now throws instead of being silently ignored.
  3. The public EnvironmentSubdomain class now throws on an invalid subdomain instead of falling back to the un-prefixed host.
    Behaviour note: host resolution is now deferred to build time, which also fixes a latent order-dependence bug where setting the subdomain before the environment produced the wrong host.

Deprecation signal: documentation (JSDoc); the custom host option remains a documented third opt-out route.

@agent-wall-e

agent-wall-e Bot commented Aug 10, 2026

Copy link
Copy Markdown

🟡 Risk Classification: MINOR

Approval route: AI Review + Human Approval
Rollback controls: Staged rollout + rollback

Classification reasons

  • exceeds_bounded_scope:827>200

Operational gates

  • ✅ jira_ticket (INT-1688)
  • ✅ independent_review

Files analysed: 11


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 10, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
exceeds_bounded_scope827>200 classifying §2.1 M8 More than 200 non-test, non-doc, non-lockfile lines changed.

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

@agent-wall-e

agent-wall-e Bot commented Aug 11, 2026

Copy link
Copy Markdown

🟡 Risk Classification: MINOR

Approval route: AI Review + Human Approval
Rollback controls: Staged rollout + rollback

Classification reasons

  • exceeds_bounded_scope:827>200

Operational gates

  • ✅ jira_ticket (INT-1688)
  • ✅ independent_review

Files analysed: 36


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 11, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
exceeds_bounded_scope827>200 classifying §2.1 M8 More than 200 non-test, non-doc, non-lockfile lines changed.

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

@agent-wall-e

agent-wall-e Bot commented Aug 11, 2026

Copy link
Copy Markdown

🟠 Advisory review: Concerns worth a look

This PR needs a human approval. Before you give it, these are the things I'd want resolved.

The PR correctly enforces the mandatory subdomain requirement by replacing deprecation warnings with thrown ValueErrors, adding the useLegacyDomain escape hatch, and updating tests throughout. One concrete logic bug exists in validateDomainOptions: the Previous-platform exemption check is never reached for OAuth credentials because isPreviousPlatform is likely keyed on the secret-key regex, but OAuth callers pass a client secret (not a secret key), so the exemption path may not fire correctly — however the more immediate issue is that the diff is truncated and isPreviousPlatform itself is not visible, making it impossible to verify the exemption logic is sound.

Concerns

  • The diff is truncated: isPreviousPlatform() in src/auth-builder.js is referenced but its implementation is cut off, so the correctness of the Previous-platform exemption cannot be verified from what is shown.
  • In validateDomainOptions, the order of checks means validateSubdomainFormat(subdomain) is called before the Previous-platform exemption, so a Previous-platform caller passing no subdomain (falsy) would pass the format check but still hit the isPreviousPlatform path — that ordering appears safe, but a Previous-platform caller passing a malformed subdomain would throw on format before reaching the exemption, which may be intentional or a surprise depending on whether Previous-platform callers ever set a subdomain.
  • The EnvironmentSubdomain.createUrlWithSubdomain now throws on null or undefined subdomain, but within auth-builder.js it is only called via new EnvironmentSubdomain(environment, options.subdomain) after options?.subdomain is confirmed truthy — so no regression there, but the unit tests that now assert throws for null/undefined call createUrlWithSubdomain directly, which is a valid public API change that callers wrapping that method would hit.
  • In test/transfers/transfers.js, the nock intercepts use https://transfers.sandbox.checkout.com (not a subdomain host), but the SDK is initialized with subdomain: '123456789' — if the subdomain is applied to the transfers URL, the actual request host would be 123456789.transfers.sandbox.checkout.com, causing the nock intercepts to miss; this needs verification that the transfers endpoint is exempt from subdomain prefixing.
  • All integration test files switch from subdomain: process.env.CHECKOUT_MERCHANT_SUBDOMAIN to useLegacyDomain: true with a comment explaining sandbox OAuth clients are not provisioned for the subdomain — this means the integration test suite no longer exercises the subdomain path at all, which is a coverage gap noted in the PR but worth flagging explicitly to the reviewer.
  • The PREVIOUS_SECRET_KEY_REGEX in src/config.js matches sk_(test_)? followed by a UUID-shaped string; the existing MBC_LIVE_SECRET_KEY_REGEX used sk_? (optional underscore) — the new regex uses sk_ (mandatory underscore), which may not match keys of the form sk<uuid> without an underscore if those exist, though this is low risk given the pattern mirrors the live regex with test_ added.

⚠️ The diff was too large to read in full, so this review covers only part of the change.


This is not an approval. wall-e cannot auto-approve this PR — it is an opinion to help whoever does. Advisory review · us.anthropic.claude-sonnet-4-6 · wall-e 2026.06.19-02

@agent-wall-e

agent-wall-e Bot commented Aug 11, 2026

Copy link
Copy Markdown

🟡 Risk Classification: MINOR

Approval route: AI Review + Human Approval
Rollback controls: Staged rollout + rollback

Classification reasons

  • exceeds_bounded_scope:840>200

Operational gates

  • ✅ jira_ticket (INT-1688)
  • ✅ independent_review

Files analysed: 36


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 11, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
exceeds_bounded_scope840>200 classifying §2.1 M8 More than 200 non-test, non-doc, non-lockfile lines changed.

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

@agent-wall-e

agent-wall-e Bot commented Aug 11, 2026

Copy link
Copy Markdown

🔴 Risk Classification: MAJOR

Approval route: AI Review + Human Approval Required
Rollback controls: Change-freeze window + documented rollback plan

Classification reasons

  • security_sensitive_path:.github/workflows/build-master.yml
  • security_sensitive_path:.github/workflows/build-pull-request.yml
  • security_sensitive_path:.github/workflows/build-release.yml

Operational gates

  • ✅ jira_ticket (INT-1688)
  • ✅ independent_review

Files analysed: 39


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 11, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
security_sensitive_path.github/workflows/build-master.yml classifying §2.1 M4/M5 Path matched a sensitive pattern (auth, secrets, crypto, PCI, migrations, network IaC).
security_sensitive_path.github/workflows/build-pull-request.yml classifying §2.1 M4/M5 Path matched a sensitive pattern (auth, secrets, crypto, PCI, migrations, network IaC).
security_sensitive_path.github/workflows/build-release.yml classifying §2.1 M4/M5 Path matched a sensitive pattern (auth, secrets, crypto, PCI, migrations, network IaC).

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

@agent-wall-e

agent-wall-e Bot commented Aug 12, 2026

Copy link
Copy Markdown

🔴 Risk Classification: MAJOR

Approval route: AI Review + Human Approval Required
Rollback controls: Change-freeze window + documented rollback plan

Classification reasons

  • security_sensitive_path:.github/workflows/build-master.yml
  • security_sensitive_path:.github/workflows/build-pull-request.yml
  • security_sensitive_path:.github/workflows/build-release.yml

Operational gates

  • ✅ jira_ticket (INT-1688)
  • ✅ independent_review

Files analysed: 38


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 12, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
security_sensitive_path.github/workflows/build-master.yml classifying §2.1 M4/M5 Path matched a sensitive pattern (auth, secrets, crypto, PCI, migrations, network IaC).
security_sensitive_path.github/workflows/build-pull-request.yml classifying §2.1 M4/M5 Path matched a sensitive pattern (auth, secrets, crypto, PCI, migrations, network IaC).
security_sensitive_path.github/workflows/build-release.yml classifying §2.1 M4/M5 Path matched a sensitive pattern (auth, secrets, crypto, PCI, migrations, network IaC).

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

@agent-wall-e

agent-wall-e Bot commented Aug 12, 2026

Copy link
Copy Markdown

🟡 Risk Classification: MINOR

Approval route: AI Review + Human Approval
Rollback controls: Staged rollout + rollback

Classification reasons

  • exceeds_bounded_scope:840>200

Operational gates

  • ✅ jira_ticket (INT-1688)
  • ✅ independent_review

Files analysed: 34


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 12, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
exceeds_bounded_scope840>200 classifying §2.1 M8 More than 200 non-test, non-doc, non-lockfile lines changed.

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

…-out

Initializing without a subdomain already emitted a deprecation warning, so this
finishes the job: it now throws a ValueError. Callers must either set
`subdomain`, or opt out explicitly with `useLegacyDomain: true`, which ships
deprecated in the type definitions. Both, or neither, throws.

An invalid subdomain also throws now instead of being silently dropped back to
the shared host, which is a second breaking change: a malformed value currently
works and the caller never finds out.

Two exemptions. A custom `host` replaces the base URL outright, so the merchant
has already said where requests go. Previous (ABC) keys predate merchant-specific
subdomains, matched by the new PREVIOUS_SECRET_KEY_REGEX, which covers both live
and sandbox key shapes (MBC_LIVE_SECRET_KEY_REGEX only matched live).

Mirrors checkout-sdk-net#590. Refs INT-1688.
…is unset

CI failed at module load with "subdomain is required". Twenty-four test files
read the subdomain from CHECKOUT_MERCHANT_SUBDOMAIN, but no workflow exports it,
so the option arrived undefined. That used to be harmless, it just meant no
subdomain; now it throws.

They all spread domainOptions() instead, which uses the subdomain when the
variable is set and the legacy hosts otherwise. Exporting the variable in CI
would not be enough on its own: the sandbox OAuth clients are not provisioned for
the subdomain, so the token request would come back invalid_client.

This only reproduced in CI because the variable happens to be set locally.
Flagged in review. A custom host is exempt from *requiring* a subdomain, since it
replaces the base URL outright, but a subdomain passed alongside one was neither
validated nor used: it was quietly dropped. Now the format check applies whenever
a subdomain is present, wherever the base URL comes from.
The suite could only run against the shared hosts, so the subdomain path this PR
makes mandatory had no integration coverage. Reviewers flagged that on every SDK,
and it is the right thing to flag.

The domain helper now has two modes. Default is unchanged, the shared hosts,
because the sandbox OAuth clients are not provisioned for the subdomain and the
token request returns invalid_client. Set CHECKOUT_TEST_USE_SUBDOMAIN=true and the
suite runs against CHECKOUT_MERCHANT_SUBDOMAIN instead, so once sandbox is
provisioned like production it is a one-line change in the workflows, already
wired and documented, rather than a rewrite of every fixture.

The switch is deliberately separate from CHECKOUT_MERCHANT_SUBDOMAIN, which CI
already exports: provisioning should drive the behaviour, not the presence of a
secret.
Versions are bumped on master during the release, not in a feature branch, per
the release workflow. This branch should carry only the change itself; the major
bump is classified and applied when the release is cut.
Two problems with the previous approach. It needed a new variable in 21 workflow
files, which is not viable without access to create secrets. And it wrapped the
builder chain in a configureDomain helper that is not part of the public API, so
the tests stopped looking like the code a merchant would actually write.

Every fixture now calls the real opt-out inline, in the chain, with a comment
saying why: the sandbox OAuth clients are not provisioned for the merchant-specific
subdomain, so the token request comes back invalid_client. When sandbox is
provisioned, those calls become the subdomain setter.

The unit tests covering all four combinations are untouched: they already used the
public API directly.
@armando-rodriguez-cko
armando-rodriguez-cko force-pushed the feat/INT-1688-mandatory-subdomain branch from e412755 to 122c10e Compare August 27, 2026 12:00
@agent-wall-e

agent-wall-e Bot commented Aug 27, 2026

Copy link
Copy Markdown

🟡 Risk Classification: MINOR

Approval route: AI Review + Human Approval
Rollback controls: Staged rollout + rollback

Classification reasons

  • exceeds_bounded_scope:840>200

Operational gates

  • ✅ jira_ticket (INT-1688)
  • ✅ independent_review

Files analysed: 34


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 27, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
exceeds_bounded_scope840>200 classifying §2.1 M8 More than 200 non-test, non-doc, non-lockfile lines changed.

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

david-ruiz-cko
david-ruiz-cko previously approved these changes Aug 28, 2026
@armando-rodriguez-cko
armando-rodriguez-cko requested a review from a team August 28, 2026 16:11
…th client

The dedicated sandbox clients are not provisioned for the merchant
subdomain; the default client now carries every scope the suites need.
@agent-wall-e

agent-wall-e Bot commented Aug 31, 2026

Copy link
Copy Markdown

🔴 Risk Classification: MAJOR

Approval route: AI Review + Human Approval Required
Rollback controls: Change-freeze window + documented rollback plan

Classification reasons

  • security_sensitive_path:test/issuing/cardholders/cardholders-it.js

Operational gates

  • ✅ jira_ticket (INT-1688)
  • ✅ independent_review

Files analysed: 40


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 31, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
security_sensitive_pathtest/issuing/cardholders/cardholders-it.js classifying §2.1 M4/M5 Path matched a sensitive pattern (auth, secrets, crypto, PCI, migrations, network IaC).

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

@agent-wall-e

agent-wall-e Bot commented Aug 31, 2026

Copy link
Copy Markdown

🟡 Risk Classification: MINOR

Approval route: AI Review + Human Approval
Rollback controls: Staged rollout + rollback

Classification reasons

  • exceeds_bounded_scope:840>200

Operational gates

  • ✅ jira_ticket (INT-1688)
  • ✅ independent_review

Files analysed: 34


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 31, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
exceeds_bounded_scope840>200 classifying §2.1 M8 More than 200 non-test, non-doc, non-lockfile lines changed.

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

david-ruiz-cko
david-ruiz-cko previously approved these changes Aug 31, 2026
… review findings

- EnvironmentSubdomain.createUrlWithSubdomain now throws a ValueError on an
  invalid subdomain instead of silently returning the un-prefixed URL, and no
  longer swallows URL parsing errors; JSDoc, the .d.ts and the unit tests are
  updated to match the other SDKs.
- Restore the original CRLF line endings in src/config.js and
  types/dist/Checkout.d.ts so the diff only shows the real changes.
- Document the custom host route in the README legacy-domain section and the
  Private Link pl- prefixed subdomain in the Subdomain value section.
- Add a unit test asserting a NAS-shaped secret key is not exempt from the
  subdomain requirement, and a comment documenting that the Previous (ABC)
  exemption is inferred from the secret-key shape.
@agent-wall-e

agent-wall-e Bot commented Aug 31, 2026

Copy link
Copy Markdown

🟡 Risk Classification: MINOR

Approval route: AI Review + Human Approval
Rollback controls: Staged rollout + rollback

Classification reasons

  • no_low_class_matched
  • prod_source_modified

Operational gates

  • ✅ jira_ticket (INT-1688)
  • ✅ independent_review

Files analysed: 36


wall-e 2026.06.19-02 · policy 376219bc71e6…

@agent-wall-e

agent-wall-e Bot commented Aug 31, 2026

Copy link
Copy Markdown
🔬 Debug — why this classification?

Each reason code emitted by the classifier, its source clause in the AI in SDLC Control Framework, and what it means.

Reason code Kind Clause Meaning
no_low_class_matched informational §2.2 (fall-through) None of the deterministic Low classes (§2.2.3, §2.2.4, §2.2.7, docs-only) applied; classifier fell through to LLM evaluation.
prod_source_modified informational §2.1 M7 (informational) At least one file is non-doc, non-test, non-IaC — i.e. application source code was modified.

Kinds:

  • classifying — this rule contributed to the chosen tier.
  • informational — context only; did not by itself decide the tier.

See issue #3 for the proposal to formalise this map as Appendix A of the standards doc.

wall-e 2026.06.19-02 · debug

@sonarqubecloud

Copy link
Copy Markdown

@armando-rodriguez-cko
armando-rodriguez-cko merged commit 9b831fe into master Aug 31, 2026
3 checks passed
@armando-rodriguez-cko
armando-rodriguez-cko deleted the feat/INT-1688-mandatory-subdomain branch August 31, 2026 15:17
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.

2 participants