The Payment Journey

A step-by-step walkthrough of what happens when a user makes an Open Banking payment

Overview

This page walks you through exactly what happens when a user makes a payment using Open Banking. We'll look at it from three perspectives: what the user sees, what's happening technically, and what your code needs to do.

By the end, you'll understand the complete journey from "Pay" button click to payment confirmation.

The Big Picture

An Open Banking payment involves four main stages:

  1. Setup - Your application prepares the payment request
  2. Authorisation - User consents to the payment at their bank
  3. Execution - The payment is processed
  4. Confirmation - Payment status is verified

Time: The entire process typically takes 30 seconds to 2 minutes.

Stage 1: Setup (Preparing the Payment)

What the User Sees

The user is on your application and clicks a "Pay" or "Checkout" button.

What's Happening Technically

Your application needs to:

  1. Define the payment details (amount, recipient, reference)
  2. Create or select a payee (recipient)
  3. Authenticate with Moneyhub (client credentials)
  4. Generate an authorisation URL

What Your Code Does

// 1. Define payment details
const paymentDetails = {
  amount: 5000,  // £50.00 in pence
  payeeId: "payee-123",  // Created earlier or inline
  payerRef: "Order-12345",  // Reference
};

// 2. Create authorization URL
const authUrl = await moneyhub.getPaymentAuthorizeUrl({
  bankId: "hsbc",  // Or "test" for test banks
  amount: paymentDetails.amount,
  payeeId: paymentDetails.payeeId,
  payerRef: paymentDetails.payerRef,
  state: generateRandomState(),
  nonce: generateRandomNonce(),
});

// 3. Redirect user to authUrl
res.redirect(authUrl);

Behind the Scenes: Authorisation URL Creation

The authorisation URL contains (encoded as a JWT or in the URL parameters):

{
  "client_id": "your-client-id",
  "scope": "openid payment id:hsbc",
  "redirect_uri": "https://yourapp.com/callback",
  "state": "random-security-token",
  "nonce": "another-random-token",
  "claims": {
    "id_token": {
      "mh:payment": {
        "essential": true,
        "value": {
          "amount": 5000,
          "payeeId": "payee-123",
          "payerRef": "Order-12345"
        }
      }
    }
  }
}

What each part means:

  • client_id - Your application identifier
  • scope - What you're requesting (openid + payment + specific bank)
  • redirect_uri - Where to send user back
  • state - Security token (you'll verify this later)
  • nonce - Another security token
  • claims.mh:payment - The exact payment details

Stage 2: Authorisation (User Consent)

This is where the user leaves your application and goes to their bank.

Step 2.1: Moneyhub Consent Screen

What the User Sees:

After being redirected, the user first sees a Moneyhub consent screen explaining:

  • Who is requesting the payment (your application)
  • What they'll be asked to do
  • Privacy and security information

What's Happening:

Moneyhub is:

  • Validating the authorisation request
  • Preparing to connect to the user's bank
  • Logging the consent request

Duration: 5-10 seconds

Step 2.2: Bank Selection

What the User Sees:

If you didn't specify a specific bank (used id:test or id:all), the user sees a list of banks to choose from.

What's Happening:

Moneyhub displays available banks that support payments. User clicks their bank.

Duration: 5-20 seconds

Step 2.3: Redirect to Bank

What the User Sees:

The user is redirected to their bank's website or app. The URL changes to their bank's domain.

What's Happening:

Moneyhub redirects the user to the bank's OAuth authorisation endpoint with:

  • The payment details
  • A request for the user to authenticate
  • Required scopes and permissions

Technical: This is a standard OAuth 2.0 redirect with Open Banking-specific parameters.

Step 2.4: Bank Authentication

What the User Sees:

The user is on their bank's website/app. They:

  • Enter their banking credentials (username, password)
  • Complete 2-factor authentication (SMS, app notification, etc.)

What's Happening:

The bank:

  • Authenticates the user
  • Verifies they have the authority to make payments
  • Checks the account has sufficient funds (some banks)

Important: Your application and Moneyhub NEVER see the user's bank credentials. Authentication happens entirely on the bank's systems.

Duration: 10-60 seconds

Step 2.5: Payment Consent Screen

What the User Sees:

After authentication, the bank displays a consent screen showing:

  • The payment amount (e.g., "£50.00")
  • The recipient details
  • The account the payment will come from
  • The payment reference
  • An "Approve" and "Cancel" button

Example:

┌────────────────────────────────────────┐
│          HSBC Payment Consent          │
├────────────────────────────────────────┤
│                                        │
│  You are authorizing a payment of:    │
│                                        │
│       £50.00                           │
│                                        │
│  To:    ABC Company Ltd                │
│         Sort Code: 12-34-56            │
│         Account: 12345678              │
│                                        │
│  From:  Your Current Account           │
│         ****1234                       │
│                                        │
│  Reference: Order-12345                │
│                                        │
│  ┌────────┐         ┌────────┐        │
│  │ Cancel │         │ Approve│        │
│  └────────┘         └────────┘        │
│                                        │
└────────────────────────────────────────┘

What's Happening:

The bank:

  • Displays the exact payment details from the authorisation request
  • Waits for user confirmation
  • Records the user's decision

User Choice:

  • Approve - Payment is authorized, proceed to execution
  • Cancel - User is redirected back with an error

Duration: 10-30 seconds

Step 2.6: Authorisation Code Returned

What the User Sees:

After approving, the user is redirected back to your application. The URL will look like:

https://yourapp.com/callback?
  code=auth-code-abc123&
  state=random-security-token&
  id_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

What's Happening:

The bank has:

  • Generated a single-use authorisation code
  • Redirected the user back to your redirect_uri
  • Included the code and state in the URL

Important Parameters:

  • code - Short-lived authorisation code (expires in ~60 seconds)
  • state - The security token you sent earlier (VERIFY THIS)
  • id_token - Contains payment ID and connection info

Stage 3: Execution (Completing the Payment)

What the User Sees

The user is back on your application. They might see a loading spinner or "Processing payment..." message.

What Your Code Does

// 1. Extract parameters from callback
const { code, state, id_token } = req.query;

// 2. VERIFY STATE (security check)
if (state !== storedState) {
  throw new Error('Invalid state - possible CSRF attack');
}

// 3. Exchange code for tokens
const tokens = await moneyhub.exchangeCodeForTokens({
  paramsFromCallback: { code, state, id_token },
  localParams: { state, nonce },
});

// tokens contains:
// - access_token: Use to check payment status
// - id_token: Contains payment ID
// - token_type: "Bearer"

// 4. Decode ID token to get payment ID
const decoded = jwt.decode(tokens.id_token);
const paymentId = decoded['mh:payment'];
const connectionId = decoded['mh:con_id'];

What's Happening Behind the Scenes

When you exchange the code, several things happen:

  1. Moneyhub verifies the code is valid and hasn't been used
  2. Moneyhub contacts the bank to complete the authorisation
  3. The bank initiates the payment in their systems
  4. Moneyhub creates a payment record and returns tokens
  5. The payment ID is encoded in the ID token for you to track

Token Exchange Request

Here's what actually gets sent:

POST /oidc/token HTTP/1.1
Host: identity.moneyhub.co.uk
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=auth-code-abc123&
redirect_uri=https://yourapp.com/callback&
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&
client_assertion=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

Response:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "scope": "openid payment id:hsbc"
}

Decoded ID Token:

{
  "sub": "user-id",
  "mh:con_id": "connection-id-xyz",
  "mh:payment": "payment-id-789",
  "aud": "your-client-id",
  "exp": 1234567890,
  "iat": 1234564290,
  "iss": "https://identity.moneyhub.co.uk/oidc"
}

Key Claims:

  • mh:payment - The payment ID you need to track
  • mh:con_id - The bank connection ID
  • sub - User ID (if user was pre-created)

Stage 4: Confirmation (Verifying the Payment)

What the User Sees

Your application displays the payment status:

  • "Payment pending..."
  • "Payment completed!"
  • "Payment failed"

What Your Code Does

// Use the payment ID from the ID token
const paymentId = decoded['mh:payment'];

// Check payment status
const payment = await moneyhub.getPayment({
  paymentId: paymentId,
  token: tokens.access_token,
});

console.log(payment.status);
// Possible statuses: 
// - "Pending"
// - "AcceptedSettlementInProcess"
// - "AcceptedSettlementCompleted"
// - "Rejected"

Payment Status Flow

                    ┌──────────────┐
                    │   Pending    │
                    └──────┬───────┘
                           │
              ┌────────────┼────────────┐
              │                         │
              ▼                         ▼
    ┌───────────────────┐     ┌──────────────┐
    │AcceptedSettlement │     │   Rejected   │
    │   InProcess       │     │              │
    └─────────┬─────────┘     └──────────────┘
              │
              ▼
    ┌───────────────────┐
    │AcceptedSettlement │
    │    Completed      │
    └───────────────────┘

Status Meanings:

  • Pending - Payment initiated, waiting for bank processing
  • AcceptedSettlementInProcess - Bank accepted, processing the settlement
  • AcceptedSettlementCompleted - Payment successfully completed
  • Rejected - Payment was rejected (insufficient funds, technical issue, etc.)

Polling for Status Updates

Some payments take a few seconds to process. You may need to poll:

async function waitForPaymentCompletion(paymentId, accessToken) {
  let attempts = 0;
  const maxAttempts = 10;
  
  while (attempts < maxAttempts) {
    const payment = await moneyhub.getPayment({
      paymentId,
      token: accessToken,
    });
    
    if (payment.status === 'AcceptedSettlementCompleted') {
      return { success: true, payment };
    }
    
    if (payment.status === 'Rejected') {
      return { success: false, payment };
    }
    
    // Still pending, wait and try again
    await sleep(2000);  // Wait 2 seconds
    attempts++;
  }
  
  return { success: false, error: 'Timeout waiting for payment' };
}

The Complete Flow Visualized

┌─────────────┐
│             │  1. User clicks "Pay"
│  Your App   │◄─────────────────────────────┐
│             │                               │
└──────┬──────┘                               │
       │                                      │
       │ 2. Create authorization URL          │
       │                                      │
       ▼                                      │
┌─────────────┐                               │
│             │                               │
│  Moneyhub   │                               │
│  Identity   │                               │
│             │                               │
└──────┬──────┘                               │
       │                                      │
       │ 3. Redirect to bank                  │
       │                                      │
       ▼                                      │
┌─────────────┐                               │
│             │  4. User logs in              │
│    Bank     │◄──────────────┐               │
│             │               │               │
└──────┬──────┘               │               │
       │                      User            │
       │ 5. User approves     │               │
       │    payment           │               │
       │                      │               │
       ▼                      │               │
       │ 6. Redirect with     │               │
       │    auth code         │               │
       │                      │               │
       └──────────────────────┴───────────────┘
                              │
                              │ 7. Exchange code
                              │
       ┌──────────────────────┘
       │
       ▼
┌─────────────┐
│             │  8. Return tokens
│  Moneyhub   │     (with payment ID)
│             │
└──────┬──────┘
       │
       │ 9. Check payment status
       │
       ▼
┌─────────────┐
│             │
│  Your App   │  10. Show confirmation
│             │      to user
└─────────────┘

Timeline Example

Here's a real-world example of how long each step typically takes:

00:00 - User clicks "Pay with Bank"
00:01 - Authorization URL created
00:02 - User redirected to Moneyhub
00:05 - User redirected to HSBC
00:15 - User enters credentials (2FA)
00:25 - User sees payment consent screen
00:30 - User approves payment
00:32 - User redirected back to your app
00:33 - Code exchanged for tokens
00:34 - Payment status: Pending
00:36 - Payment status: AcceptedSettlementInProcess
00:45 - Payment status: AcceptedSettlementCompleted
00:46 - User sees "Payment successful!"

Total time: 46 seconds

What Can Go Wrong?

User Cancels

If the user clicks "Cancel" at any point:

Redirect: https://yourapp.com/callback?error=access_denied&state=...

Your code should:

  • Check for error parameter
  • Show appropriate message
  • Log the cancellation

Insufficient Funds

If the account doesn't have enough money:

The payment status will be: Rejected

Your code should:

  • Check payment status
  • Handle rejection gracefully
  • Offer alternative payment methods

Technical Errors

Various technical issues can occur:

  • Bank systems temporarily unavailable
  • Network timeouts
  • Invalid payment details

Your code should:

  • Implement retry logic
  • Handle errors gracefully
  • Provide clear error messages to users

State Mismatch

If the state parameter doesn't match:

This is a security issue. Reject the request immediately.

if (state !== storedState) {
  // Log security incident
  // Reject the request
  throw new Error('State mismatch - possible CSRF attack');
}

Key Differences: One-Time vs Ongoing Access

One-Time Payment (What We've Described)

  • User authorizes a specific payment
  • Consent is single-use
  • No refresh token
  • Payment executes once

Ongoing Access (e.g., Account Data)

  • User grants access for a period (e.g., 90 days)
  • Consent is reusable
  • Refresh token provided
  • You can fetch data multiple times

The authorisation flow is similar, but the scopes and tokens differ.

Security Highlights

Throughout this journey, security is maintained:

  1. User credentials never shared - Only the bank sees them
  2. Encrypted communication - All requests use HTTPS/TLS
  3. State validation - Prevents CSRF attacks
  4. Short-lived codes - Authorisation codes expire in ~60 seconds
  5. Single-use codes - Can't be reused
  6. Explicit consent - User sees exact payment details
  7. Audit trail - All actions logged

Next Steps

Now that you understand the complete payment journey, you're ready to:

  1. Review the Key Terminology Glossary for any unfamiliar terms
  2. Learn about Security Concepts in more detail
  3. Try the [Your First Payment](../getting started/your-first-payment) tutorial

Quick Reference

Authorisation URL → User Consent → Code Exchange → Payment Status → Confirmation

Time: 30 seconds - 2 minutes

Tokens: Access token (API calls), ID token (payment ID)

Status: Pending → AcceptedSettlementInProcess → AcceptedSettlementCompleted

Errors: Check for error parameter in redirect, handle rejected payments

Further Reading

  • OAuth and OIDC Basics - Understand the authentication standards
  • Security Concepts - Deep dive into Open Banking security
  • [Troubleshooting Common Errors](../getting started/troubleshooting-common-errors) - Fix common issues

What’s Next

Look up technical terms and concepts