Net -173 lines. No behaviour change to any reachable endpoint.
Dead code removed
routes/paypal.ts (156 lines) and routes/demo.ts (39) are the pre-cart single-item checkout flow. Nothing has imported them since the cart flow landed in 9ab689e:
app.ts mounts only cartCheckoutRouter and cartCheckoutWebhookRouter
the frontend calls only /api/checkout/cart/* (frontend/src/cart/cartApi.ts:76-92)
no test references either file
Between them and the live cartCheckout.ts they duplicated PAYPAL_BASE, getAccessToken(), and a second /webhooks/paypal handler — one that treats custom_id as an item ID while the live one treats it as a checkout ID. Only one was ever mounted.
Live duplication extracted
/paypal/create and /demo/purchase each repeated the same shipping-address ownership check, cart lock + empty guard, and checkouts / checkout_items inserts. Now one openCheckout() returning a discriminated union, so callers keep control of the transaction and the response:
The call sites now differ only where they genuinely differ: processor name, and whether processor_order_id is known upfront (null for PayPal, set after order creation; demo-${Date.now()} for demo). Added CartItem / LockedCart interfaces to type loadLockedCart, which removed the (it: any) casts. Left client: any alone — typing it as PoolClient would touch completeCheckout too and belongs in its own pass.
Follow-up, not done here
paypal.ts was the only writer of items.reserved_until and items.paypal_order_id. Those columns are now write-dead — admin.ts:155 only nulls them, and the cart flow tracks expiry on cart_items.expires_at. Dropping them wants its own migration.
Verification
npx tsc --noEmit — clean
Unit tests — 16/16
Integration tests — 14/14, including completes a multi-item cart purchase and marks all items sold, which covers the refactored demo path end to end
Net **-173 lines**. No behaviour change to any reachable endpoint.
## Dead code removed
`routes/paypal.ts` (156 lines) and `routes/demo.ts` (39) are the pre-cart single-item checkout flow. Nothing has imported them since the cart flow landed in 9ab689e:
- `app.ts` mounts only `cartCheckoutRouter` and `cartCheckoutWebhookRouter`
- the frontend calls only `/api/checkout/cart/*` (`frontend/src/cart/cartApi.ts:76-92`)
- no test references either file
Between them and the live `cartCheckout.ts` they duplicated `PAYPAL_BASE`, `getAccessToken()`, and a second `/webhooks/paypal` handler — one that treats `custom_id` as an item ID while the live one treats it as a checkout ID. Only one was ever mounted.
## Live duplication extracted
`/paypal/create` and `/demo/purchase` each repeated the same shipping-address ownership check, cart lock + empty guard, and `checkouts` / `checkout_items` inserts. Now one `openCheckout()` returning a discriminated union, so callers keep control of the transaction and the response:
```ts
const opened = await openCheckout(client, req.customerId as number, shippingAddressId, 'paypal', null);
if (!opened.ok) { await client.query('ROLLBACK'); return res.status(400).json({ error: opened.error }); }
const { checkoutId, cart } = opened;
```
The call sites now differ only where they genuinely differ: processor name, and whether `processor_order_id` is known upfront (`null` for PayPal, set after order creation; `demo-${Date.now()}` for demo). Added `CartItem` / `LockedCart` interfaces to type `loadLockedCart`, which removed the `(it: any)` casts. Left `client: any` alone — typing it as `PoolClient` would touch `completeCheckout` too and belongs in its own pass.
## Follow-up, not done here
`paypal.ts` was the only writer of `items.reserved_until` and `items.paypal_order_id`. Those columns are now write-dead — `admin.ts:155` only nulls them, and the cart flow tracks expiry on `cart_items.expires_at`. Dropping them wants its own migration.
## Verification
- `npx tsc --noEmit` — clean
- Unit tests — 16/16
- Integration tests — 14/14, including `completes a multi-item cart purchase and marks all items sold`, which covers the refactored demo path end to end
🤖 Generated with [Claude Code](https://claude.com/claude-code)
routes/paypal.ts and routes/demo.ts were the pre-cart single-item
checkout flow. Nothing has imported them since the cart flow landed:
app.ts mounts only cartCheckout, the frontend calls /api/checkout/cart/*,
and no test touches them. They duplicated PAYPAL_BASE, getAccessToken,
and a second handler for the /webhooks/paypal mount.
Also extract openCheckout() from /paypal/create and /demo/purchase in
cartCheckout.ts, which repeated the same address-ownership check, cart
lock, and checkouts/checkout_items inserts. It returns a discriminated
union so callers keep control of the transaction and the response. Add
CartItem/LockedCart interfaces, dropping the (it: any) casts.
Note: paypal.ts was the only writer of items.reserved_until and
items.paypal_order_id. Those columns are now write-dead; the schema is
left alone for a separate migration.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Net -173 lines. No behaviour change to any reachable endpoint.
Dead code removed
routes/paypal.ts(156 lines) androutes/demo.ts(39) are the pre-cart single-item checkout flow. Nothing has imported them since the cart flow landed in9ab689e:app.tsmounts onlycartCheckoutRouterandcartCheckoutWebhookRouter/api/checkout/cart/*(frontend/src/cart/cartApi.ts:76-92)Between them and the live
cartCheckout.tsthey duplicatedPAYPAL_BASE,getAccessToken(), and a second/webhooks/paypalhandler — one that treatscustom_idas an item ID while the live one treats it as a checkout ID. Only one was ever mounted.Live duplication extracted
/paypal/createand/demo/purchaseeach repeated the same shipping-address ownership check, cart lock + empty guard, andcheckouts/checkout_itemsinserts. Now oneopenCheckout()returning a discriminated union, so callers keep control of the transaction and the response:The call sites now differ only where they genuinely differ: processor name, and whether
processor_order_idis known upfront (nullfor PayPal, set after order creation;demo-${Date.now()}for demo). AddedCartItem/LockedCartinterfaces to typeloadLockedCart, which removed the(it: any)casts. Leftclient: anyalone — typing it asPoolClientwould touchcompleteCheckouttoo and belongs in its own pass.Follow-up, not done here
paypal.tswas the only writer ofitems.reserved_untilanditems.paypal_order_id. Those columns are now write-dead —admin.ts:155only nulls them, and the cart flow tracks expiry oncart_items.expires_at. Dropping them wants its own migration.Verification
npx tsc --noEmit— cleancompletes a multi-item cart purchase and marks all items sold, which covers the refactored demo path end to end🤖 Generated with Claude Code