mirror of
https://github.com/hanzoai/billing.git
synced 2026-10-30 18:46:54 +00:00
billing.hanzo.ai shipped two card forms. One is Square Web Payments hosted
fields — the card lives in Square's iframe and we only ever see a single-use
nonce. The other read the PAN, CVC and expiry into React state and POSTed them
as plain JSON from our own origin to `${NEXT_PUBLIC_COMMERCE_API}/billing/card/
tokenize`, defaulting to the banned `/api/v1` prefix. That put this origin in
PCI SAQ-D scope, sitting next to the very control that keeps us in SAQ-A, and it
could only ever fail: the destination is deleted in this same change.
Nobody wrote that form here. `billing-shell.tsx` imported billing components
from the published `@hanzo/ui/billing` barrel, and that barrel is ONE
pre-bundled module carrying the raw-PAN form; a single named import dragged it
in, twice over (card-form + payment-manager), with nothing in this repo to grep
for. The barrel was redundant anyway — every component it supplied already
exists in `lib/billing`, with real types instead of the `FC<any>` ambient shim
`hanzo-ui.d.ts` declared. So the import is gone and `lib/billing` is the ONE
billing surface. The published bundle stays a dependency only for navigation.
Dropping the `any` shim surfaced two errors it had been hiding, both fixed here:
SECTION_TITLES carried four sections the shell does not render (Cost sits inside
usage, Members inside team, Business and Tax inside settings), and commerce
returns `tags` as a scalar or a list where TransactionRecord wants a list.
Also deleted, all of it dead and all of it PAN-shaped:
- the local CardForm in lib/billing/payment-manager.tsx — unreferenced since
SquareCardForm took the card tab, but still an unmasked PAN input
- lib/components/animated-card.tsx — a live card-number preview, whose only
consumer was that form
- SquareCardForm's onCvvFocus effect, which reached into Square's iframe via
contentWindow.document to drive the preview's flip. Cross-origin, so it
always threw into a swallowed catch — dead code that also undermined the
exact boundary hosted fields exist to draw. Nothing in first-party JS
touches the hosted fields now.
- commerce-client's processCardPayment, which took {number, expMonth, expYear,
cvc} and posted to a route no server registers
`scripts/pci-scope.mjs` keeps it deleted, and is wired to `pnpm lint` in place
of the `echo 'lint ok'` placeholder. It scans SOURCE for what we write and the
BUILT BUNDLE for what we import — the second is the one that would have caught
this, since the form arrived through a dependency. It refuses a vacuous run: a
scan that examined zero files fails rather than passes.
Verified on the rebuilt bundle: 0 hits for card/tokenize, expiry_month,
api.hanzo.ai/api/v1 and the form's own marker string; 4 for web.squarecdn.com.
The deployed bundle has 2 of each of the first three today.
Co-authored-by: Hanzo Dev <[email protected]>