Skip to main content

Frontend Development

The bopen frontend is a statically-exported Next.js 16 application at apps/web. It serves two distinct UIs: the Checkout Widget (consumer-facing) and the Acquirer Portal (B2B admin).

Tech stack

  • Next.js 16 with App Router, output: "export" (static HTML)
  • TypeScript strict mode
  • Tailwind CSS v4 (CSS-first configuration)
  • qrcode — client-side QR code generation for desktop bank handoff

Project structure

apps/web/src/
├── app/
│ ├── layout.tsx # Root layout (fonts, globals.css)
│ ├── page.tsx # Home page
│ ├── globals.css # Tailwind v4 entry + CSS custom properties
│ │
│ ├── checkout/ # Consumer checkout widget
│ │ ├── page.tsx
│ │ └── CheckoutClient.tsx # Multi-step: summary → picker → QR/handoff
│ │
│ ├── payments/return/ # SCA callback landing page
│ │ ├── page.tsx
│ │ └── PaymentReturnClient.tsx # Fee breakdown, end_to_end_id, auto-redirect
│ │
│ ├── portal/ # Acquirer Portal (auth-gated)
│ │ ├── layout.tsx # Auth guard + 15-min session management
│ │ ├── page.tsx # Redirect → /portal/transactions
│ │ ├── login/
│ │ │ ├── page.tsx
│ │ │ └── LoginClient.tsx # Email + password + 6-digit TOTP
│ │ ├── transactions/
│ │ │ ├── page.tsx
│ │ │ └── TransactionsClient.tsx # Ledger table + slide-out audit panel
│ │ ├── settlements/
│ │ │ ├── page.tsx
│ │ │ └── SettlementsClient.tsx # Balance cards + payout history + CSV export
│ │ └── developers/
│ │ ├── page.tsx
│ │ └── DevelopersClient.tsx # API keys + webhook registration + test
│ │
│ ├── redirect-stub/ # Mock bank redirect (dev only)
│ └── dev/ # Development harnesses

├── components/
│ ├── checkout/
│ │ └── CheckoutShell.tsx # Shared layout wrapper for checkout screens
│ └── portal/
│ ├── Sidebar.tsx # Portal navigation sidebar
│ └── TransactionSlideover.tsx # Slide-out detail panel with audit trail

└── lib/
├── banks.ts # UK bank list (16 banks) with brand colours
├── payments.ts # Payment API client (createPayment, getPayment, etc.)
└── portal-mock.ts # Mock data for portal (replace with real API calls)

Checkout widget

The checkout flow is managed as a client-side state machine within CheckoutClient.tsx. Session state is persisted to sessionStorage before the external bank redirect so it survives the round-trip.

Screens

PhaseDescription
summaryAmount, merchant name, SCA legal disclosure (PSR 2017)
pickerPopular banks grid + searchable list of all 16 UK banks
creatingSpinner while POST /payments completes
authoriseMobile: deep-link "Open app" button; Desktop: QR code via qrcode package
errorUser-facing error with "Try again"

The callback page (/payments/return) handles the bank redirect, calls sca-complete, polls for terminal status, then shows the receipt with:

  • Gross / fee / net amount breakdown
  • end_to_end_id ISO 20022 reference
  • Append-only audit trail with actor labels (MERCHANT_API, BANK_CALLBACK)
  • 5-second countdown auto-redirect to merchant_return_url

Mobile vs desktop

The widget detects mobile via navigator.userAgent at runtime (the app is statically exported). On mobile, the authorise screen renders an <a href={bankRedirectUrl}> styled as a button. On desktop, it uses qrcode.toDataURL() via dynamic import to render a <img> QR code, keeping the bank redirect URL out of the initial JS bundle.

sessionStorage keys

KeyValue
bopen_payment_idUUID of the created payment intent
bopen_stateOAuth state token for SCA completion

These allow /payments/return to recover the correct payment even if the user's browser loses the URL parameters during the bank redirect.

Acquirer Portal

All portal routes are wrapped by portal/layout.tsx, which enforces:

  • Auth guard: checks bopen_portal_token in localStorage; redirects to /portal/login if absent or expired
  • 15-minute inactivity logout: refreshes expiry on click, keydown, mousemove, touchstart; polls every 60 seconds to catch idle expiry

Views

RouteDescription
/portal/loginEmail + password + 6-digit TOTP form. Any valid email + password ≥ 8 chars + 6-digit code succeeds in dev mode.
/portal/transactionsSearchable, filterable transactions ledger. Click any row to open the TransactionSlideover showing the full payment_events audit trail.
/portal/settlementsBalance cards (available, pending, account masked as ****NNNN). Daily payout history table. Client-side CSV export.
/portal/developersAPI key management (cycle / revoke), webhook endpoint registration, event subscription checklist, test webhook button.

Commands

CommandDescription
npm run devStart development server on port 3000
npm run buildStatic export to out/
npm run lintESLint

Environment variables

Create apps/web/.env.local for local configuration:

NEXT_PUBLIC_PAYMENT_API_URL=http://localhost:8081

The checkout widget and portal both resolve the payment service URL from NEXT_PUBLIC_PAYMENT_API_URL. If unset, it defaults to http://localhost:8081.