Skip to main content
This playbook is the minimum required test matrix before any change to cart, shipping, payment, or order modules merges to main. It pairs backend orchestration, the payment gateway, the checkout UI, and the storefront — a missed regression in any of these costs real orders.

Cadence

  • Per PR touching cart, order, payments, or any PSP module: run the smoke sequence
  • Weekly against production: full matrix (smoke + cross-PSP + idempotency)
  • Pre-release for every backend tag: full matrix + chaos drills

1. Test fixtures

A reliable stability program needs known-good and known-broken fixtures so every regression test has predictable inputs.
Never point smoke tests at real merchant shops. Use the fixture shops below.

Fixture shops

SlugPurposeShipping profilePayment methods
qa-physical-easypostReal EasyPost rates, physical goods, US→USEasyPostStripe (test), PayMob (sandbox), Bonum (sandbox)
qa-physical-printfulPOD goods via PrintfulPrintfulStripe (test)
qa-physical-uaeUAE→MENA, exercises Telr + PayMobCustom flat-rate by countryTelr (sandbox), PayMob (sandbox), Stripe (test)
qa-digital-onlyPure digital items, no shippingnoneStripe (test), Bonum (sandbox)
qa-mixedPhysical + digital, split shippingEasyPost (physical only)Stripe (test)
qa-cryptoWeb3 product, on-chain settlementnone / digitalx402 / Coinbase Commerce / SOL Pay

Fixture customer accounts

Test-mode-only — never real cards.
EmailCardAddress countryWhy
qa+stripe-pass@...4242 4242 4242 4242USHappy path
qa+stripe-3ds@...4000 0025 0000 3155USForces 3DS challenge
qa+stripe-decline@...4000 0000 0000 0002USGeneric decline
qa+stripe-insufficient@...4000 0000 0000 9995USInsufficient funds
qa+stripe-disputed@...4000 0000 0000 0259USTriggers dispute post-charge
qa+stripe-mena@...4242 4242 4242 4242AE / SA / EGAVS variations, currency conversion

Fixture environments

  • Stage (apiv3stage + checkoutstage) — primary smoke target. Hits Stripe test mode, EasyPost test API, PayMob sandbox, Telr sandbox.
  • Production (apiv3prod + checkout.droplinked.io) — full live target. Run from a UAE-IP runner for IP-allowlist coverage.
  • Local docker compose — backend-only assertions; can’t exercise full Stripe Elements.

Fixture freshness

Schedule a weekly clean of qa-* shop carts older than 7 days, void all qa+* test charges, and archive completed test orders. Stops fixture rot from masking regressions.

2. Per-PR smoke sequence

Twelve assertions. Should complete in under 10 minutes against stage.
#ScenarioFixtureExpected
S1Add 1 physical item → checkout initqa-physical-easypost200, cart ID returned, totals match items + 0 shipping
S2Enter US shipping address → availableShipping populated+ US addr≥1 shipping group, ≥1 rate per group with rateId + price + carrier
S3Select shipping rate → totals updatecontinuedtotals.shipping reflects rate; totals.total = items + shipping + tax
S4Create Stripe PaymentIntent → client_secretcontinuedclientSecret + paymentIntentId present, no shipping mismatch
S5Pay with 4242… → order moves to PAIDcontinuedOrder flips PENDINGPAID within 30s of webhook
S6Stripe charge has populated branding fieldsStripe dashboard test modestatement_descriptor_suffix, receipt_email, shipping block populated
S7Digital-only cart skips shipping entirelyqa-digital-onlyFrontend hides shipping section; checkout completes with billing only
S8Mixed cart splits shipping correctlyqa-mixedOnly physical item in availableShipping; digital is unshippable but non-blocking
S9EasyPost-tagged product gets real carrier ratesqa-physical-easypost, US→USUSPS / UPS / FedEx appear; no silent fallthrough to custom strategy
S10Printful-tagged product gets Printful ratesqa-physical-printfulPrintful strategy fires; no EasyPost call
S113DS challenge card completes paymentqa+stripe-3ds@…Redirects to 3DS challenge, returns, charge completes
S12Decline card lands clean errorqa+stripe-decline@…Generic “card declined” surfaced, cart preserved, no order created
A PR that breaks any of S1–S12 against stage must not merge.
S1–S6 + S7 + S9 should run automatically in CI. S5, S11, S12 require Stripe sandbox client interaction (Playwright against test mode).

3. Cross-PSP matrix (weekly)

Run the same 12 scenarios across each enabled PSP for the relevant currency.
PSPCurrencyOriginDest
StripeUSDUSUS
StripeAEDAEAE
StripeAEDAESA
PayMobEGPEGEG
PayMobAEDAEAE
TelrAEDAEAE
BonumMNTMNMN
Coinbase CommerceUSDCglobaldigital (digital-only carts)
x402 / SOL PayUSDCglobaldigital (digital-only carts)
PayPalUSDUSUS
For each row, capture:
  • Time-to-complete checkout (p50, p95)
  • Webhook latency (PSP → integration service → backend)
  • Order status transition path
  • Cart-preserved-on-failure behavior

4. Idempotency + replay matrix

Every PR that touches webhook handlers or cart-status transitions must pass:
#ScenarioExpected
I1Same webhook delivered twice (Stripe replay)Second is no-op; no double-paid; no duplicate emails
I2Webhook arrives before payment-intent succeeded callbackBackend resolves to correct final state regardless of arrival order
I3Webhook arrives 24h late (Stripe retries)Still processed correctly; idempotency key honored
I4Two concurrent “Pay Now” clicksOne PaymentIntent created; second click 4xxs cleanly (no double-charge)
I5User abandons checkout, returns next dayCart preserved if not expired; expired carts show empty-state, not 500
I6Selected shipping rate expires (EasyPost ~15min TTL)Backend revalidates; customer asked to reselect; no silent total drift
I7Network failure mid-payment (Stripe requires_action → connection drop)Order reconciles via webhook within 5 minutes

5. Chaos drills (pre-release / monthly)

Test failure modes that only happen in production.
#DrillHow to simulatePass criteria
C1EasyPost insufficient fundsDrain EasyPost wallet in sandboxBackend alerts ops; existing checkouts fall back to flat-rate; new checkouts surface a graceful banner
C2Stripe 5xxToxiproxy outage simulationBackend retries once, then fails gracefully; cart preserved
C3Mongo Atlas connection dropBlock Atlas IP in test envBackend serves cached cart; surfaces error on writes; no 500s to reads
C4Integration service downStop the 3rdp containerBackend opens circuit breaker after 3 failures; surfaces “Payment provider unavailable” within 2s
C5Webhook signature failure (rotated key)Send webhook with stale HMAC401, log, alert; do NOT process the event
C6Restricted Stripe key from unauthorized IPSpin up runner without IP allowlistStripe returns 403; backend should not retry-loop
C7UAE→US Stripe currency conversionAED-priced order, US cardConversion happens once; receipt + dashboard agree on FX rate
C8Mixed cart with one out-of-stock itemStock one item to 0 mid-checkoutItem removed with clear message; cart total recomputes; selected shipping rate revalidates

6. Known regression — EasyPost silently uses custom strategy

A product configured with shippingProfileId = 'easypost' can silently fall through to the custom shipping strategy if the strategy factory only registers Printful + Custom. The custom strategy’s canHandle returns true for any non-Printful profile, so EasyPost-tagged products get routed to a strategy expecting merchant flat-rate config. Result: empty availableShipping, checkout dead-ends at “Shipping these items is not available to this address.”
Fix path:
1

Implement an EasyPost shipping strategy

Wrap the easy-post module’s rate-fetch in a ShippingRateStrategy adapter.
2

Register it ahead of the custom strategy

Add it to the strategy factory’s default-initialization list before the custom strategy.
3

Tighten canHandle

Update the custom strategy to only return true for explicit 'custom' / 'flat-rate' identifiers — never as a catch-all.
4

Guard the regression

S9 in the per-PR smoke matrix is the standing regression guard once the strategy ships.

7. Tooling roadmap

What we have:
  • A narrowly-Bonum stress-test script — pattern-extend for other PSPs
  • Some payment-gateway specs needing repo-level jest cleanup
  • A backend CI workflow with lint+test as a soft-fail
What we need:
1

Playwright suite for S1–S12

Drive Stripe Elements test-mode card entry; assert on backend cart state via direct API checks between UI steps.
2

GitHub Actions workflow

checkout-smoke.yml runs the Playwright suite on every PR labelled area:checkout or touching cart/order/payments modules.
3

Tagged Stripe test-mode dashboard

Isolate regressions from real-money flow with a dedicated dashboard tag.
4

Weekly UAE-egress cron

Run sections 2 + 3 + 4 against production weekly from a UAE-IP runner; post to a #stability channel.
5

Chaos toolkit

Toxiproxy (or similar) for section 5 drills.

Appendix — “Shipping these items is not available” fault tree

"Shipping these items is not available to this address"
└── frontend: MethodsLoader status === 'failed'
    └── ShippingMethodsSection: noShippingAvailable === true
        └── cart.availableShipping has length 0
            ├── FetchShippingRateUseCase returned hasErrors: true
            │   ├── No strategy matched profileId
            │   ├── Strategy threw (EasyPost outage, Printful 5xx)
            │   └── Address mapping failed (country not in DB)
            └── FetchShippingRateUseCase returned empty responses
                ├── Custom strategy: shop has no flat-rate config for destination
                ├── Printful strategy: API returned no shippable carriers
                └── EasyPost insufficient funds (manifests as no rates)
Walk this tree from leaf upward to pin which node fired. Update the tree when a new node is discovered.