Skip to main content

Mock Bank & SCA Testing

Simulate UK Open Banking redirect and Strong Customer Authentication (SCA) locally before connecting to real ASPSP sandboxes.

Consumer checkout flow

RouteRole
/checkoutBank picker — 16 UK banks, searchable
/checkout ST-03Handoff — QR code (desktop) or deep link (mobile)
/payments/returnCallback — polls status, shows receipt with fee breakdown
/redirect-stubMock ASPSP — simulates bank SCA redirect

Example URL:

https://dev-checkout.bopenbanking.com/checkout
?amount=150.00&currency=GBP&merchant=Demo+Store
&return_url=https://merchant.example.com/thanks

Architecture

/checkout (bank picker)
│ POST /api/v1/payments (Bearer token)

payment-service ──→ bopen.payment_consents (AWAITING_AUTHORISATION)
│ ──→ bopen.payment_intents (AwaitingAuthorisation)

▼ redirect_url
/redirect-stub (simulate bank SCA)

▼ ?code=...&state=...
/payments/return
│ POST /api/v1/payments/:id/sca-complete
│ GET /api/v1/payments/:id (poll)

Receipt — gross/fee/net breakdown, end_to_end_id, status history

Mock merchant harness

/dev/merchant-harness is the primary end-to-end dev entry point:

RouteRole
/dev/merchant-harnessPOST /api/v1/payments and redirect to mock bank
/dev/merchant-successMock thank-you page after SCA completes

Harness features

  • Editable amount, currency, idempotency key, and merchant client ID
  • Bank selection: Monzo, Barclays, NatWest
  • Auto-redirect toggle — disable to test idempotency (click Initiate twice with the same key; expect HTTP 200 and the same payment_id)
  • Full SCA toggle — swaps /redirect-stub for /dev/mock-bank-sca

Default merchant ID: merchant_acquirer_alpha.

Embeddable widget testing

For widget-specific testing, open /widget-demo.html:

  • Adjust amount, currency, merchant name, and API URL
  • Click Launch widget — the iframe loads with the full 4-screen flow
  • The event log captures all BOPEN_* lifecycle events in real time
  • Integration snippet updates live — copy it into a merchant page prototype

Mock bank credentials

StepValue
Usernamecustomer
Passwordpassword123
OTP123456

Quick actions on the mock bank page:

  • Simulate SCA success — skips forms, returns a mock code
  • Simulate cancellation — returns error=access_denied

API authentication

The payment API requires a Keycloak Bearer token (when KEYCLOAK_URL is set):

# Get a token from your Keycloak realm
TOKEN=$(curl -s -X POST \
http://localhost:8080/realms/test-corp/protocol/openid-connect/token \
-d "grant_type=password&client_id=bopen-portal&username=admin@test.com&password=..." \
| python3 -c "import json,sys; print(json.load(sys.stdin)['access_token'])")

# Create a payment
curl -X POST http://localhost:8081/api/v1/payments \
-H "Authorization: Bearer $TOKEN" \
-H "X-Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"amount":"150.00","currency":"GBP","redirect_uri":"http://localhost:3000/payments/return"}'

When KEYCLOAK_URL is not set (dev without Keycloak), the service falls back to the X-Merchant-Client-Id header:

curl -X POST http://localhost:8081/api/v1/payments \
-H "X-Merchant-Client-Id: dev-acquirer-client-0001" \
-H "X-Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"amount":"150.00","currency":"GBP","redirect_uri":"http://localhost:3000/payments/return"}'

Payment status transitions

ScenarioFinal statusAudit events
Successful SCASettledAwaitingAuthorisation → Authorised → Settled
User cancelledFailedAwaitingAuthorisation → Failed
Merchant refundRefundedSettled → Refunded

Each event records changed_by (MERCHANT_API or BANK_CALLBACK) and an ordinal for deterministic ordering. payment_events.raw_payload_sha256 contains a PostgreSQL-computed SHA-256 hash of the raw payload for cryptographic non-repudiation.

Initiating a refund

Once a payment reaches Settled, merchants can initiate a full refund:

curl -X POST http://localhost:8081/api/v1/payments/{payment_id}/refund \
-H "Authorization: Bearer $TOKEN"
# → 200 { "payment_id": "...", "status": "Refunded" }

The payment.refunded webhook fires immediately after the refund is processed.

Swapping to a real ASPSP sandbox

When integrating TrueLayer, Yapily, or the OBIE directory:

  1. Replace the mock bank URL in payment-service with the ASPSP authorisation URL returned by the external consent API
  2. The Next.js callback route (/payments/return) and PostgreSQL state machine are unchanged
  3. The end_to_end_id field is already injected into all payment intents — banks will pass it through for reconciliation