Skip to content
🎓 Find your path Subscribe

The First Real Sale (and Three Bugs It Exposed)

Tier 3 · What we built 8 min read

Before this, read:


At 13:34 on 2026-06-09, JD placed the first order on shop.agenttree.army: Out Of Usage tee, White, XL, shipped to Draper UT. The pipeline handled the Stripe checkout. The drain ran. Printful order 161851271 was created, $17.31 billed to Printful. Status: pending fulfillment.

The pipeline worked. It also broke three times on the way.

All three were caught and fixed on the same order, same day. None had fired in the synthetic test harness because the harness used Printful’s estimate_order_costs endpoint — a dry-run path that is more forgiving than the live order submission path. That gap between test and production is the first lesson.

The fulfill_drain.py module re-fetches the Stripe session after a payment completes to get billing and shipping details. The re-fetch included an expand[] parameter for certain session fields — specifically, a field that the live Stripe session for this store’s configuration did not support expansion on.

Stripe returned a 400. The drain fetched nothing. The order sat unprocessed.

Fix (commit da87660): removed the unsupported expand parameter. The fix is surgical — one line removed from the session-fetch call. No retry logic needed once the call succeeded.

Bug 2: billing address used as shipping address

Section titled “Bug 2: billing address used as shipping address”

The order webhook receives the Stripe session payload and extracts the delivery address to pass to Printful. The initial implementation read from billing_details — the address tied to the card.

Printful requires the shipping address. On JD’s test order, the billing address had a blank line1. Printful returned an error: 'line1 blank'.

Fix (commit da87660): switched the extraction to shipping_details.address. These are separate fields in the Stripe session object, and for a physical goods store, the shipping address is always the right one.

This is a common e-commerce implementation mistake. Billing and shipping are often the same address — which is why synthetic tests pass — but they are distinct fields, and relying on billing details for a physical shipment is wrong by design.

Printful’s order API has a confirm parameter that transitions an order from draft to production. An order left in draft state does not get fulfilled. The Printful integration was passing confirm=True in the request body instead of as a query parameter (?confirm=1).

The order was created — Printful accepted it — but it sat as a draft, never transitioning to production.

Fix (commit c3c5425): moved confirm from the JSON body to the query string: POST /orders?confirm=1. One character change in the URL construction. The fix also added a migration for retry_count and last_error columns on the orders table so drain failures are auditable.

All three fixes shipped the same day as the first order, before the next drain cron ran. The CHANGELOG entry at 13:34 notes:

“Applied retry_count/last_error migration + reloaded PostgREST cache. Confirmed Printful order 161851271 (status pending, $17.31 billed → Printful billing CONFIRMED working). 119 tests green.”

The retry mechanism meant the drain picked up the corrected order on its next 2-minute tick without JD doing anything. The order moved to Printful after the fixes were deployed.

The two-layer test harness (Layer 1 Playwright, Layer 2 synthetic webhook) was the right call: it caught the 23 store-launch issues and proved the architecture before real money was on the line. But it could not catch bugs that only surface on the live Stripe-to-Printful path, because it used a dry-run endpoint that doesn’t execute the full session-fetch-and-submit chain.

Three patterns emerge from these three bugs:

  1. Read API documentation at the endpoint level, not the object level. Stripe’s expand[] behavior is per-context. Printful’s confirm placement is documented but easy to misread.
  2. Billing and shipping are separate. For physical goods, never default to billing details for the delivery address.
  3. Test with the exact call chain you’ll use in production. A dry-run endpoint is useful for architecture validation; it is not a substitute for a test that exercises the live path.

A fourth pattern is implied by all three: the value of a real first transaction isn’t the revenue. It’s what it tells you about the parts of your system that synthetic signals can’t reach.


Next: The operator that runs the store day-to-day — and the hard walls that keep money and identity with a human — is the autonomous CEO-operator.