Standard checkout assumes SKU + quantity + shipping. Regulated and B2B flows often need structured attestations first: vet license numbers, contract PO references, delivery dock instructions, or “I confirm I am not shipping to a banned jurisdiction.” Those answers must be validated, auditable, and visible in admin + ERP exports—not buried in free-text order comments.
This note is the architecture pattern I use when extending Shopware checkout without forking core templates blindly.
1. Capture layer (storefront)
Options
| Approach | When it fits |
|---|---|
| Classic Symfony Form + Twig | Server-rendered, fast to secure, easy CSRF |
| Storefront JS app posting JSON | Complex multi-step UI; still needs identical server validation |
Non-negotiables
- CSRF on POST; rate limiting on public routes (IP + customer id when logged in).
- Schema versioning in payload:
{"v":1,"fields":{...}}so you can migrate old orders when fields rename. - PII policy: collect only what legal approved; redact in logs.
2. Validation layer (server)
Mirror compliance language in assertions (Symfony Callback constraints, custom validators):
- Regex for license formats
- Cross-field rules (“if country = X then document Y required”)
- Max lengths aligned with DB columns to avoid truncation surprises
Return field-level errors as JSON for SPA or re-render Twig with error bags.
3. Cart bridge — three patterns we have used
Pattern A — Hidden “service” line items
Create a virtual product (hidden from search) per fee/attestation bundle. Cart processor adds line with:
payloadcontaining JSON answers (or a reference id to aorder_drafttable if payload size is a concern)stackablerules documented so promotions do not zero it accidentally
Pros: Appears on invoice PDFs naturally.
Cons: Tax engines (Avalara) may treat it as taxable—coordinate early.
Pattern B — Quote custom fields only
Store structured answers on cart or order customFields without new SKUs.
Pros: Simpler tax.
Cons: ERP that only reads line items may miss data—add export mapping.
Pattern C — Sidecar order_draft table
POST creates order_draft row; checkout references draftUuid until order placed; on OrderPlacedEvent, attach FK to order.
Pros: Large payloads, file uploads.
Cons: More moving parts—must garbage-collect abandoned drafts.
4. Checkout gating
- Cart validator or subscriber: if required fields missing →
CartExceptionwith translated snippet key. - Payment step: re-validate (client can tamper); block
payroute until server state clean. - Admin order detail: custom tab rendering JSON pretty + copy button for support.
5. Events worth subscribing to
CheckoutOrderPlacedEvent— push to ERP / OMS; include structured fields in payload builder.StateMachineTransitionEventfor fulfillment—sometimes answers change which transition is legal.
6. Testing matrix
- Guest vs logged-in; session expiry mid-flow.
- Multi-shipment orders—ensure answers follow the header order, not a single delivery.
- Reorder from account—should not silently clone stale attestations without user confirmation.
Resume framing
“Delivered regulated Shopware checkout: server validation, cart bridge pattern chosen with tax/ERP trade-offs, checkout gating, and admin visibility for support.”