Skip to content

ADR-001 Phase 3.5 PR-4 — Disable tenant-side Checkout; redirect to main-site checkout #1254

@superdav42

Description

@superdav42

ADR-001 Phase 3.5 PR-4 — Disable tenant-side Checkout in sovereign mode; redirect to main-site checkout

For #1250. Cross-repo parent: Ultimate-Multisite/ultimate-multisite-multi-tenancy#43.

Architectural decision

Checkout always runs on the main site. Sovereign tenants are the result of a checkout, not its host. Tenant-side checkout AJAX, form rendering, cart, and signup-field validators are all disabled in sovereign tenants; users who land on a checkout URL inside a sovereign tenant are redirected to the main site's /register/ page (or equivalent).

This eliminates the largest single risk surface in the Phase 3.1 audit (§2.7.5: tenant-AJAX wu_create_order writing across wu_payments, wu_customers, wu_memberships, wp_users simultaneously) without porting any of that logic to REST.

Files

File Change
inc/checkout/class-checkout.php Guard the 4 AJAX handlers at :199-222 (wu_create_order, wu_validate_form, wu_check_user_exists, wu_inline_login). In sovereign context, return JSON error pointing caller to main-site checkout URL.
inc/checkout/class-checkout-pages.php When sovereign, replace tenant checkout/registration URLs with the main-site equivalent (resolve via get_network()->site_idget_blog_details( $main_site_id )->siteurl . '/register/').
inc/checkout/class-cart.php Guard model loading; the Cart class should not instantiate / hit the DB in a sovereign tenant context.
inc/checkout/class-legacy-checkout.php Same as class-checkout.php — guard hook registration.

Reference pattern

For AJAX handlers:

public function wu_create_order() {

    if ( defined( 'WU_MT_SOVEREIGN_TENANT' ) && WU_MT_SOVEREIGN_TENANT ) {

        wp_send_json_error( [
            'code'             => 'sovereign_checkout_disabled',
            'message'          => __( 'Checkout runs on the main site.', 'wp-ultimo' ),
            'main_site_url'    => $this->get_main_site_checkout_url(),
        ], 400 );

        return;
    }

    // ...existing body...
}

For URL replacement helper (new method on Checkout_Pages):

public function get_main_site_checkout_url(): string {

    $main_site = get_blog_details( get_network()->site_id );

    if ( ! $main_site ) {
        return network_site_url( '/register/' );
    }

    return trailingslashit( $main_site->siteurl ) . 'register/';
}

UX requirement

Pages that previously rendered checkout forms or upgrade buttons inside sovereign tenants must render a clear "Upgrade on the main site" link instead of erroring or rendering an empty form. Wherever a checkout shortcode / block / element would render, replace its output in sovereign context with:

<a href="{main_site_checkout_url}" class="wu-sovereign-checkout-link">
  Upgrade your plan on the main site
</a>

Tenant admins and customers see a working link, not a broken form.

Verification

  1. Non-sovereign tenant: checkout works exactly as before. Existing E2E tests green.
  2. Sovereign tenant — AJAX: wu_create_order POST returns 400 sovereign_checkout_disabled with main_site_url in the response.
  3. Sovereign tenant — page render: any URL that previously rendered a checkout form renders the "Upgrade on the main site" link with a working URL.
  4. Sovereign tenant — Cart class not instantiated: confirm via debug log or unit test that Cart::__construct is never called during a sovereign tenant request.
  5. Main site: checkout flow on the main site works unchanged.

Branch

dev-main integration branch.

LOC estimate

~50 lines across 4 files, mostly guards + the URL helper.

Tier

tier:2 — multi-file, requires UX decisions for the "Upgrade on main site" replacement element.


aidevops.sh v3.17.27 plugin for OpenCode v1.15.7 with claude-haiku-4-5 spent 5h 48m and 38 tokens on this as a headless worker.

Metadata

Metadata

Assignees

Labels

adr-001ADR-001 sovereign tenant isolation workenhancementNew feature or requestsolved:workerTask was solved by a headless workerstatus:in-reviewPR open, awaiting review/mergetier:2~1-2 days, multi-file

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions