Skip to main content

Architecture Overview

bopen is an open banking A2A acquiring platform deployed on AWS. It follows a microservices architecture with a statically-exported Next.js frontend and a Rust payment service, with Keycloak providing FAPI-compliant IAM.

High-level architecture

┌──────────────────────────────────────────────────────────────────────────┐
│ AWS Amplify (5 apps) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Checkout │ │ Acquirer │ │ Product │ │ Payer Hub │ │
│ │ Widget │ │ Portal │ │ Owner │ │ my.dev. │ │
│ │ checkout. │ │ acquirer. │ │ Portal │ │ bopen.tech │ │
│ │ dev.bopen.. │ │ dev.bopen.. │ │ admin.dev. │ │ /payer/* │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
└─────────┼────────────────┼──────────────── ┼──────────────── ┼─────────┘
│ HTTPS │ HTTPS + KC JWT │ bopen-platform │ HMAC JWT
▼ ▼ ▼ ▼
┌───────────────────────────────────────────────────────────────────────────┐
│ Payment Service (Rust / ECS Fargate) │
│ /api/v1/payments/* /api/v1/portal/* /api/v1/admin/* │
│ /api/v1/payer/auth/* /api/v1/payer/* │
└────────────────────┬─────────────────────────┬───────────────────────────┘
│ │ Admin API
▼ ▼
┌────────────────────┐ ┌─────────────────────────────┐
│ RDS PostgreSQL │ │ Keycloak (ECS Fargate) │
│ bopen.* │ │ auth.dev.bopen.tech │
│ keycloak_core.* │ │ merchant realms │
└────────────────────┘ │ bopen-platform realm │
└─────────────────────────────┘
│ SMTP

┌──────────────┐
│ AWS SES │
│ DKIM verified│
│ dev.bopen. │
│ tech domain │
└──────────────┘
Current dev.bopen.tech deployment — verified live

All components above are deployed and live as of July 2026. SES is deployed and DKIM-verified on dev.bopen.tech. All five Amplify apps are live with custom domains. Keycloak runs as a single task (not 2-task HA) due to a CloudFormation ECS resource-provider timeout constraint — see IAM deployment notes.

Components

Checkout Widget (apps/web/public/widget/)

A zero-dependency vanilla JS application served inside a sandboxed iframe. Merchants embed it with a single <script> tag. Features:

  • Loader (/v1/checkout.js): reads data-checkout-token, injects iframe, relays postMessage events
  • Widget (/widget/index.html): 5 screen states, 16 UK banks, QR code (desktop) / deep link (mobile), 2500 ms polling, BOPEN_PAYMENT_SUCCESS / BOPEN_PAYMENT_FAILED events
  • Bundle: ~12 KB gzipped (well within 20 KB target)
  • Branding: merchant colour scheme, logo, and font loaded at runtime from GET /api/v1/portal/branding

See Checkout Widget.

Consumer Checkout (apps/web/src/app/checkout/)

Next.js multi-step flow: order summary → bank selection → SCA handoff → processing → receipt with fee breakdown and end_to_end_id.

Product Owner Portal (apps/web/src/app/admin/)

Platform super-admin control room at admin.app.bopen.tech:

  • Loginbopen-platform Keycloak realm, PKCE S256 OIDC; roles: OWNER / OPS / SUPPORT
  • Overview — global merchant KYB status matrix; live counts (approved/pending/suspended/held)
  • Merchants — KYB approval / suspension, payout hold / release
  • Disputes — cross-tenant dispute queue; resolve in favour of merchant or payer; escalate to OBL DMS
  • ASPSP Health — conversion rate and latency heatmap per UK bank

Setup: run ./infra/cloudformation/scripts/deploy-platform-realm.sh then invite-platform-user.sh.

Acquirer Portal (apps/web/src/app/portal/)

B2B merchant dashboard at acquirer.dev.bopen.tech:

  • Self-registration (/portal/register) — provisions Keycloak realm + merchant DB record (KYB: PENDING)
  • Login — per-merchant Keycloak realm, PKCE S256 OIDC flow
  • Transactions — searchable ledger with slide-out audit trail, refund button, raise-dispute button
  • Settlements — balance cards, payout history, CSV and PDF export
  • Disputes — view customer complaints, submit evidence, track resolution status
  • Widget Branding — colour picker, logo URL, font selector with live widget preview; stored as branding_config JSONB on the merchant record
  • Team — invite staff with ADMIN / DEVELOPER / FINANCE roles; remove members
  • Developer Tools — webhook registration, signing secret, event delivery history

Payer Hub (apps/web/src/app/payer/)

Consumer consent dashboard at my.dev.bopen.tech — no password required:

  • Magic-link auth — enter email or phone; receive a one-time link (10-min expiry) via SES/SMS; exchange for a 15-min HMAC session JWT stored in sessionStorage
  • Dashboard — active VRP mandates + last 50 payments across all merchants
  • Payment receipt — full details + raise-dispute form (OBL CEG requirement)
  • Mandate detail — spending caps, expiry, prominent Cancel Permission kill-switch (fires DELETE /api/v1/payer/consents/{id})

Payment Service (services/payment-service)

Rust / Axum microservice on ECS Fargate:

  • Payment lifecycle (create consent → intent → SCA → settle → refund/partial-refund)
  • Dispute lifecycle: open → under-review → resolved (webhooks on state changes)
  • Fee calculation (percentage + flat, snapshotted at execution time)
  • end_to_end_id generation (ISO 20022, 35-char, BACS/FPS compliant)
  • JWT validation: Keycloak JWKS cached by issuer URL, azp claim → merchant lookup; validate_aud = false (Keycloak tokens carry aud: "account", identity done via azp)
  • Platform JWT validation: bopen-platform realm tokens, role extracted from realm_access.roles
  • Payer JWT validation: HMAC-signed (HS256), issued by the service itself after magic-link verification
  • Keycloak Admin API: realm provisioning, user management, bopen-platform realm setup
  • Append-only audit ledger with actor tracking (MERCHANT_API, BANK_CALLBACK)

Keycloak IAM

FAPI-compliant IAM engine on ECS Fargate at auth.dev.bopen.tech:

  • Merchant realms — one per acquirer, PKCE bopen-portal client, PS256, 15-min tokens
  • bopen-platform realm — internal staff; roles: OWNER, OPS, SUPPORT; bopen-admin PKCE client
  • Single task in bopen-dev (HA requires resolving CFN ECS resource-provider timeout — see IAM)
  • SES SMTP fully configured and DKIM-verified on dev.bopen.tech

See Identity & Access Management.

Database (database/)

Versioned PostgreSQL migrations on RDS (001–013):

  • B2B payments (bopen.*): merchants (+ branding_config, payout_held), payment_consents, payment_intents (+ refunded_amount), payment_events, webhook_deliveries
  • Dispute engine (bopen.*): disputes (reason/status enums, evidence fields, resolution tracking)
  • Payer auth (bopen.*): payer_tokens (magic-link SHA-256 hash, 10-min expiry)
  • Platform staff (bopen.*): platform_users (keycloak_user_id, role enum)
  • IAM (keycloak_core.*): Keycloak session/realm state (separate schema, separate DB user)

See Database Schema.

Infrastructure (infra/cloudformation/)

13 nested CloudFormation templates — all deployed in dev.bopen.tech account:

TemplateDeployed
vpc.yaml, ecs.yaml, ecr.yaml, rds.yaml, logging.yaml
api-gateway.yaml, keycloak.yaml, ses.yaml
amplify-portal.yaml, amplify-web.yaml, amplify-docs.yaml
amplify-admin.yaml (Product Owner Portal)
amplify-payer.yaml (Payer Hub)

See AWS Infrastructure.

Monorepo layout

bopen/
├── apps/
│ └── web/ # Next.js (static export — 5 Amplify apps)
│ └── public/
│ ├── v1/checkout.js # Widget loader
│ └── widget/index.html # Embeddable widget
│ └── src/
│ ├── app/
│ │ ├── admin/ # Product Owner Portal (/admin/*)
│ │ │ ├── login/
│ │ │ ├── overview/
│ │ │ ├── merchants/
│ │ │ ├── disputes/
│ │ │ └── aspsp-health/
│ │ ├── portal/ # Acquirer Portal (/portal/*)
│ │ │ ├── transactions/
│ │ │ ├── settlements/
│ │ │ ├── disputes/
│ │ │ ├── branding/
│ │ │ ├── team/
│ │ │ └── developers/
│ │ ├── payer/ # Payer Hub (/payer/*)
│ │ │ ├── verify/
│ │ │ ├── dashboard/
│ │ │ ├── payments/detail/
│ │ │ └── consents/detail/
│ │ └── checkout/ # Consumer checkout
│ └── lib/
│ ├── keycloak.ts # Merchant portal auth helpers
│ ├── adminAuth.ts # Platform admin auth helpers
│ └── payerAuth.ts # Payer magic-link auth helpers
├── services/
│ └── payment-service/ # Rust Axum service
│ └── src/
│ ├── auth.rs # JWKS cache + merchant/platform/payer JWT validation
│ ├── keycloak_admin.rs # Admin API client + platform realm setup
│ ├── handlers.rs # All HTTP handlers (payments, portal, admin, payer)
│ └── state.rs
├── database/
│ └── scripts/
│ ├── migrations/ # 001–013 versioned schema
│ └── seeds/ # Dev/test data
├── infra/cloudformation/
│ ├── templates/ # 13 nested stack templates
│ └── scripts/
│ ├── deploy.sh # Full stack deploy
│ ├── deploy-admin-amplify.sh # Product Owner Portal
│ ├── deploy-portal-amplify.sh # Acquirer Portal
│ ├── deploy-payer-amplify.sh # Payer Hub
│ ├── deploy-platform-realm.sh # bopen-platform Keycloak realm
│ ├── invite-platform-user.sh # Invite internal staff
│ ├── generate-payer-session-secret.sh
│ └── generate-ses-smtp-credentials.sh
└── docs/ # This documentation site

End-to-end payment flow

1. Acquirer registers via /portal/register
→ Keycloak realm provisioned, merchant DB record created (KYB: PENDING)
→ SES email invite sent to admin (live on dev.bopen.tech)

2. Platform operator approves KYB via admin.dev.bopen.tech/admin/merchants
→ kyb_status = APPROVED; merchant can now process payments

3. Merchant calls POST /api/v1/payments with Keycloak Bearer token
→ JWT validated (JWKS), merchant resolved via azp claim
→ KYB + payout-hold checks enforced
→ payment_consent + payment_intent created; fee breakdown snapshotted
→ Returns redirect_url, end_to_end_id, gross/fee/net amounts

4. Consumer completes SCA (via Checkout Widget or direct checkout)
→ Payment transitions: AwaitingAuthorisation → Authorised → Settled
→ payment_events audit trail updated (MERCHANT_API / BANK_CALLBACK)

5. BOPEN_PAYMENT_SUCCESS postMessage fires to merchant page
→ Merchant webhook receives settlement notification (payment.settled)

6. Payer raises dispute (optionally)
→ POST /api/v1/payer/payments/{id}/dispute from Payer Hub
→ dispute.opened webhook sent to merchant
→ Merchant submits evidence via /portal/disputes
→ Platform operator resolves via admin.dev.bopen.tech/admin/disputes
→ dispute.resolved webhook confirms outcome