Epic 008 — Full Surfboard ECR integration
Status
In progress (foundation tasks 007-011 complete; ready to start 012).
Purpose
Extend Foodtruck Order so it becomes a proper POS application using Surfboard's Electronic Cash Register (ECR) integration. Foodtruck Order remains the POS application and user interface. Surfboard provides card payments, payment terminal integration, ECR/fiscal cash-register operations, control-unit/fiscal infrastructure, and fiscal reporting. Foodtruck Order integrates directly with Surfboard's APIs. This epic explicitly does not use or plan around Surfboard CheckoutX.
Scope and boundaries
In scope:
- fiscal/ECR-connected card payments
- Swish payments
- cash payments
- other payment methods confirmed as relevant to a Swedish foodtruck POS
- ECR lifecycle: cash-register discovery/configuration, state retrieval, shifts, cash management, receipts, reports, end of day
- non-commercial checkout preserved completely outside payment/ECR
- Mocking Bird integration for every externally integrated flow
- local and staging validation
Out of scope / non-goals:
- Surfboard CheckoutX or replacing Foodtruck Order's checkout UI
- multi-tenant SaaS platform; only light merchant/store/register abstraction
- speculative payment methods without confirmed Surfboard support
- production fiscal resources from staging
- authentication, multi-device sync, or other unrelated product features
Confirmed Surfboard capabilities
Source: Surfboard official documentation and the Surfboard documentation MCP server
(https://www.surfboardpayments.com/.well-known/mcp), verified 2026-04-11. See
docs/decisions/004-surfboard-integration-discovery.md for the earlier discovery record.
- Cash register resource under a store:
POST /merchants/:merchantId/stores/:storeId/cash-register(create)GET /merchants/:merchantId/cash-register(fetch all for merchant)GET /merchants/:merchantId/stores/:storeId/cash-register/:cashRegisterId(get by ID)GET /merchants/:merchantId/stores/:storeId/cash-register/:cashRegisterId/state-info(state)PUT .../open-shift,PUT .../close-shift,PUT .../end-of-dayPOST .../cash-register/deposit-petty-cashPUT .../cash-register/withdrawPATCH .../cash-register/:cashRegisterId/closing-timeGET .../cash-register/:cashRegisterId/filesDELETE .../cash-register/:cashRegisterId
- Cash-register state includes
cashRegisterState,shiftInfo,pettyCash,openedAt,closedAt. - Shift rules: one open shift per register; one register per terminal at a time; no shift means no sales; a terminal can move between registers after closing.
- End of day is one-time, irreversible, and can be automatic at the scheduled close time.
- Sales are
POST /orderswithcontrolFunctions.ecrEnabled: true, against the terminal that holds an open shift on the register. initiatePaymentsOptions.paymentMethodsupports at leastCARD,SWISH,CASH,KLARNA, and other activated methods.- Payment methods are activated per merchant/store through the Payment Methods API; Swish requires store-level configuration including a Swish number.
- Partial/mixed payments are supported (card + cash, card + Swish, etc.).
- Returns/refunds are orders with negative quantities and
purchaseOrderId/purchasePaymentIdreferences. - Receipts: digital link, email, terminal print, ESC/POS;
PUT /orders/:orderId/receipt. For a Surfboard register the receipt number is generated by the register; a client-suppliedsequenceNumberis stored for reference only. - Reports/files:
GET .../files?reportType=&startDate=&endDate=returns Z-report, X-report, journal memory and similar files.
Uncertain capabilities (require investigation/spikes)
- Exact in-store Swish behaviour on a terminal: whether the terminal displays the Swish QR code itself or whether Foodtruck Order must generate/display a QR code and how confirmation is delivered.
- Exact
swishModeandpayeePhoneNumbervalues required for in-store terminal Swish versus onlineECOM/NSWISH. - Whether Swish payment confirmation is automatic through order status polling / webhooks or requires manual operator confirmation in the foodtruck flow.
- Exact
cashRegisterStateenumeration beyond the observedCASH_REGISTER_OPEN. - Whether X-reports are generated on demand or only retrieved as files.
- Exact receipt fields Foodtruck Order must send for a Surfboard-register sale.
- ECR behaviour when an order is created without an open shift (error envelope).
- Idempotency/deduplication guarantees beyond application-owned operation IDs.
- Whether Surfboard provides a sandbox/demo terminal that can exercise ECR flows without physical hardware (SurfTester may be required).
Recommended Swish implementation path
- Confirm terminal-native Swish flow using Surfboard docs and, if needed, a staging terminal.
- Prefer automated path: Swish button -> order with paymentMethod SWISH -> terminal handles Swish -> Foodtruck Order observes completion via status polling/webhook -> ECR sale completed automatically.
- If terminal cannot display QR or auto-confirm, fall back to Foodtruck Order displaying a dynamic Swish QR code with exact amount and operator confirmation.
- Use manual confirmation only if no automatic verification is possible.
Core requirement: all commercial payments connect to the ECR
Every completed commercial sale must be handled correctly through the Surfboard ECR flow, not merely stored in Foodtruck Order history. The implementation must define and preserve the mapping between Foodtruck Order order, Surfboard order/payment, ECR/fiscal sale, payment method, payment status, and refunds/voids. Idempotency and reconciliation must prevent duplicate fiscal sales under retries, timeouts, delayed responses, and connectivity loss.
Non-commercial checkout
Non-commercial checkout remains entirely outside Surfboard. It must not initiate any payment, create any Surfboard payment or ECR sale, require an open ECR shift, or communicate with the Surfboard API. It completes the operational order and sends it to the queue/history according to existing semantics.
KDS checkout UX
The KDS keeps separate, directly accessible buttons for Card, Swish, Cash, and Non-commercial. No dropdowns, radio selection, multi-step selection, or unnecessary confirmation dialogs. Each button starts its flow directly.
Epic scenarios
ECR-S01 - Commercial card sale is registered in the ECR
- Given an open ECR shift on a register linked to a terminal
- When an operator presses Card for an order
- Then the card payment uses the existing Surfboard terminal flow
- And the completed sale is created as an ECR-enabled Surfboard order
- And the order is represented in Surfboard ECR reporting
- Evidence: adapter/application/API tests plus Mocking Bird scenarios.
ECR-S02 - Commercial cash sale is registered in the ECR
- Given an open ECR shift
- When an operator presses Cash
- Then a Surfboard order with paymentMethod CASH is created with ecrEnabled true
- And the ECR records the cash payment
- Evidence: adapter/application/API tests plus Mocking Bird scenarios.
ECR-S03 - Commercial Swish sale is registered in the ECR
- Given an open ECR shift
- When an operator presses Swish
- Then the preferred supported Swish flow is used
- And the completed sale is registered in the ECR
- And duplicate registration is prevented
- Evidence: adapter/application/API tests plus Mocking Bird scenarios.
ECR-S04 - Non-commercial checkout bypasses Surfboard/ECR
- Given any ECR state, including no open shift
- When an operator presses Non-commercial
- Then the operational order is completed and queued
- And no Surfboard API request is made
- And no ECR sale or shift is required
- Evidence: application/API tests and a negative integration test.
ECR-S05 - Checkout requires an open ECR shift
- Given a terminal with no open shift
- When a commercial checkout is attempted
- Then the Surfboard request is rejected
- And Foodtruck Order surfaces a safe error
- And no order is finalized
- Evidence: adapter/application tests plus Mocking Bird scenarios.
ECR-S06 - Shift lifecycle
- Given a configured cash register
- When a cashier opens, runs, and closes a shift
- Then terminal/register linkage follows Surfboard rules
- And invalid operations are rejected safely
- Evidence: adapter/application/API tests plus Mocking Bird scenarios.
ECR-S07 - End of day / Z-report
- Given a closed or open shift at end of day
- When end of day is triggered deliberately
- Then the register closes for the trading day and a Z-report is generated
- And accidental duplicate closure is prevented
- Evidence: adapter/application/API tests plus Mocking Bird scenarios.
ECR-S08 - Cash management
- Given an open shift
- When petty cash is deposited or withdrawn
- Then the ECR updates the float and records the movement
- Evidence: adapter/application/API tests plus Mocking Bird scenarios.
ECR-S09 - Receipt handling
- Given a completed ECR sale
- When a receipt is requested
- Then the appropriate Surfboard receipt capability is used
- And non-commercial orders do not produce fiscal receipts
- Evidence: adapter/application/API/component tests.
ECR-S10 - Idempotency and duplicate prevention
- Given timeouts, delayed responses, or retries
- When a checkout is repeated
- Then no duplicate fiscal sale is created
- And ambiguous outcomes are recorded as unresolved
- Evidence: adapter/application tests plus Mocking Bird retry scenarios.
ECR-S11 - Swish fallback manual confirmation
- Given automatic Swish verification is unavailable
- When an operator uses the fallback flow
- Then a single confirmation registers the Swish payment through the ECR flow
- And the system does not claim independent verification
- Evidence: application/API/component tests.
ECR-S12 - Report retrieval
- Given a completed trading day
- When reports are requested
- Then Surfboard-generated report files can be retrieved
- Evidence: adapter/application tests plus Mocking Bird scenarios.
Environment and configuration
- Local: deterministic fake and Mocking Bird; no Surfboard access required.
- Staging: Surfboard Demo credentials from the secret store; networked terminal; ECR-enabled merchant/store/register; HTTPS webhook ingress.
- Production: configuration-only switch to Live after certification and onboarding.
- Secrets remain server-side; never exposed to frontend or fixtures.
Recommended task breakdown
See tasks/README.md and the individual tasks/ECR-TASK-*.md files.
Traceability
Evidence will be recorded per scenario in this epic evidence matrix once implementation tasks are completed. Each task file contains its own acceptance criteria and Mocking Bird requirements.
| Scenario | Evidence (planned) | Status |
|---|---|---|
| ECR-S01 | Surfboard checkout/fiscal adapter, application, API, Mocking Bird | Planned |
| ECR-S02 | Surfboard fiscal adapter, application, API, Mocking Bird | Planned |
| ECR-S03 | Surfboard fiscal adapter, application, API, Mocking Bird | Planned |
| ECR-S04 | Application/API negative tests | Planned |
| ECR-S05 | Adapter/application/API tests, Mocking Bird | Planned |
| ECR-S06 | Adapter/application/API tests, Mocking Bird | Planned |
| ECR-S07 | Adapter/application/API tests, Mocking Bird | Planned |
| ECR-S08 | Adapter/application/API tests, Mocking Bird | Planned |
| ECR-S09 | Adapter/application/API/component tests | Planned |
| ECR-S10 | Adapter/application tests, Mocking Bird retry scenarios | Planned |
| ECR-S11 | Application/API/component tests, Mocking Bird | Planned |
| ECR-S12 | Adapter/application tests, Mocking Bird | Planned |