How cp10 works
A guided walkthrough of the standalone checkout engine at
web/checkout-eet/p/static/checkout-eet-engine-cp10/ — how it gets onto a page, how a card
and a PayPal purchase flow through it, what happens when a bank says no, and why it shares zero code
with its siblings. Every screenshot below is the real thing, captured from the engine running locally
against the mock backend.
1The cast
Five things cooperate to turn a visitor into a subscriber. Only the first two are "ours" on the page:
- The host page
- What the customer visits (
p/static/cp-eet_9-layout-update/). It owns the branding, plan selection, order summary, jQuery — and one empty<div class="epcheckout-target">where the engine will appear. - The engine — cp10
- The payment form itself: email/name fields, card boxes, PayPal, the Subscribe button, and all the
logic. One big file (
src/index.js, ~3,000 lines) plus small extracted modules, each with tests beside it. - The loader
utils/et_utils.js— a page-side helper. ItsepCheckout.load()fetches the engine's HTML over ajax and injects it into that div. The engine never calls the loader; it is only carried by it.- The BFF backend
- The subscription API the engine POSTs to (offer details, email checks,
create_subscription). Locally,?localBackend=1swaps it for a one-file PHP mock (p/mock-backend.php). - Braintree
- The payment gateway. Its SDK renders the card boxes as its own iframes and turns card data into a one-time token — our code never sees a card number.
2How the engine gets onto the page
window, one jQuery, one URL. That is why the page can read the engine's results from plain
globals, and why the page must provide jQuery ≥ 3.5 before the engine arrives.
sequenceDiagram
participant P as Host page
participant L as Loader (et_utils.js)
participant R as PHP route
participant E as cp10 engine
P->>L: epCheckout.load({selector, cp_engine_url})
Note over P: sets window.epCheckoutParams
(offer id, version label)
L->>R: ajax GET ?page=checkout-eet-engine-cp10
R-->>L: index.php with dist/bundle.js BAKED INLINE
L->>P: inject HTML into .epcheckout-target
Note over E: inline script runs, initInlineCP() boots
E->>E: read epCheckoutParams, freeze checkoutEngineVersion
E->>E: mount Braintree hosted fields (card iframes)
The bundle is baked into the HTML server-side rather than loaded via <script src> —
a deploy filter once dropped dist/ and silently killed the form, so cp10 inlines it and logs
a precise console error if it's ever missing (index.php, the bundle-include guard).
Here is the real result — the host page with cp10 injected (captured live; the engine request was pointed at the cp10 route at the network layer):
- Page-owned territory — plan selection, order summary, header. The engine never touches these; it reads the chosen offer from
epCheckoutParams. - Injected cp10 — everything in the form column from "Email" down is the engine's markup living inside the page's div. The card boxes are Braintree's iframes mounted into
#bt-card-numberetc. - The delegation seam — the express PayPal button belongs to the page, but its clicks are delegated into the engine's
#paypal-option. This only works because both share one jQuery instance — the reason jQuery is a page-provided precondition, never bundled.
3A card purchase, end to end
sequenceDiagram
participant C as Customer
participant E as cp10 engine
participant B as Braintree (iframes)
participant S as BFF backend
C->>E: fill email + names, click Subscribe
E->>S: known customer? (email check)
E->>B: tokenize card (inside Braintree's iframes)
B-->>E: one-time payment nonce
Note over E: optional 3DS challenge here
E->>S: POST create_subscription (nonce + offer + fraud signature)
S-->>E: subscription_id
E->>E: window.newSubData = payload
E->>C: redirect to onboarding
- Tokenization keeps card data out of our hands. The digits live in Braintree's iframes; we receive only a nonce — a single-use stand-in token. PCI burden stays with the gateway.
- The fraud signature rides along:
buildForterCardParams(src/forter-card-params.js) assembles device/payer metadata; absent PayPal fields are omitted entirely (they used to go out as the literal string"undefined"). - 3D-Secure can interpose a bank challenge. One predicate decides whether to block:
block ⟺ a liability shift was possible but did not happen
(
shouldBlockForThreeDsResult, src/three-d-secure-result.js). - The result contract is two globals:
window.newSubData(new customer) orwindow.existingSubData(existing customer). The page's post-purchase hooks read them directly — same window, no messaging needed — then the engine redirects to onboarding.
4When the bank says no
Failures arrive as a raw text body — no structured decline code reaches the frontend. The mapper in
src/decline-codes.js chooses the customer's banner in two tiers:
| Tier | Rule | Why |
|---|---|---|
| 1 — reliable signal | If the attempt's payment method was PayPal (_paymentType), show the PayPal banner. Period. |
The engine knows ground truth about which instrument was used; every other banner gives card-specific advice ("the number on the back of your card") that is wrong for a PayPal payment. |
| 2 — substring fallback | An ordered needle list maps the body to one of nine verbatim banners ("Insufficient Funds" is checked before "Declined", etc.). | Byte-identical to the legacy behaviour; the order is load-bearing and golden-master-tested, so nobody can silently change what a customer reads. |
This is what tier 2 looks like for a body containing Processor Declined (2046) on the
card path — captured live:
- The generic-decline banner, rendered into
#payment-errorbyhandlePurchaseError→mapDeclineResponse. Every one of the nine banners is preserved character-for-character (phone numbers, link copy) and pinned by a golden-master test against the legacy implementation.
5The PayPal flow
Selecting PayPal starts the express handshake: the engine opens a popup, asks the backend for a checkout token, sends the popup to PayPal, and dims the page behind an overlay while it polls for the popup to come back:
- The overlay (
.paypal-checkout-overlay) blocks the form while the popup is open; "Click to Continue" refocuses the popup. The engine broadcastspaypalModalStarted / Failed / Closedvia origin-lockedpostMessage— three events the page's overlay handling depends on. Dropping any of them breaks this handshake. - PayPal selected —
_paymentTypeis now'paypal', which is the tier-1 decline signal from §4.
- The popup returns from PayPal carrying
token=…&PayerID=…; the engine's 100ms poll spots it, closes the popup, and hides the overlay. - The engine exchanges the token for a billing agreement (BAID) and fetches the payer's details — watch the form below: First/Last name became "Mock Buyer", written back from the (mock) PayPal account.
- The same Subscribe button now drives the same
create_subscriptioncall as the card path — just with the BAID instead of a card nonce. Here it was answered with a decline, and tier 1 correctly produced the PayPal banner:
- Payer details written back — "Mock Buyer" came from the PayPal account via
get_express_checkout_detail, not from typing. - The PayPal banner — Before the fix, this exact failure body showed the generic card banner, because banner choice depended on a fragile magic phrase in the response text.
6Why it shares nothing with cp8/cp9
cp10 is a fork of cp9's engine with every shared path severed: its own copy of the gateway constants, its own modules, dependencies, and build. The duplication is deliberate — the goal is fault isolation, so nothing anyone does to cp8/cp9 can break cp10, and vice versa. The only tolerated couplings run to the page and the platform, and both are documented preconditions:
The accepted cost: a gateway-ID rotation is now an N-place edit (canonical file + every engine that vendored it). The keys are browser-public already, so the duplication adds no secret exposure.
7Poke it yourself
# the bare engine, no host page (you'll see the un-hidden address block — # that's what a missing page-provided jQuery looks like) cd web/checkout-eet/p/static/checkout-eet-engine-cp10 php -S localhost:8123 # open http://localhost:8123/index.php # 135 unit tests — decline map, 3DS predicate, messaging, version, modules npm run test:unit # 4 e2e tests driving the real page+engine+mock-backend flow (needs network) npm run test:e2e # the full experience in a browser: host page + engine + mock backend cd ../../.. # web/checkout-eet php -S localhost:8100 # open: http://localhost:8100/p/?page=cp-eet_9-layout-update&o=fs-1p1w8w-2_49p1w&localBackend=1 # (loads the cp9 engine by design; the e2e shows the same page driving cp10)
Reading order
prime-checkout-eet-engine-cp10.md— the contract (load handshake, isolation boundary, traps).index.php— serving: the inline bundle, the guarded platform mounts.src/index.js— the engine; everything in §2–§5 lives here.- The extracted modules in
src/— each small, pure, and tested:decline-codes.js,three-d-secure-result.js,engine-messaging.js,checkout-engine-version.js,forter-card-params.js, and friends. decline-codes-investigation.md— why the decline mapping looks the way it does.tests/e2e/load-contract.spec.ts— the four flows of §2–§5, executable.
AI-generated walkthrough (2026-06-10). All four screenshots were captured live from the
engine running against the mock backend during generation; flows and file pointers verified against the
code at that date. Source assets:
2026-06-10_0113_cp10-how-it-works-walkthrough/screenshots/.