---
id: simple-payment-flow
title: Complete payment flow
hide_title: true
description: "End-to-end payment flow from checkout initialization to settlement"
---

import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import Ingress from "../../../src/components/ingress/ingress.react";

<Ingress title="Complete payment flow">
  Follow Walley's full payment lifecycle, from checkout to settlement, with the
  API calls you need at each stage.
</Ingress>

## Prerequisites

Before you start, make sure you have:

- API credentials from [Merchant Hub](/docs/checkout/prerequisites), and a [bearer token](/docs/checkout/authorization) generated from them
- A store set up in the UAT environment, so you can run the whole flow without moving real money
- A notification endpoint that Walley can reach over HTTPS and that answers `200 OK`
- The [endpoints](/docs/checkout/endpoints) for the environment you are targeting

Every request below goes to UAT. Swap the hostname for production when you are ready.

## Payment lifecycle overview

Five phases make up the full payment lifecycle, each with its own integration steps:

```mermaid
sequenceDiagram
    participant Customer
    participant Frontend
    participant Backend
    participant Walley

    Note over Customer,Walley: Phase 1: Create the checkout session
    Customer->>Frontend: Browse products
    Customer->>Frontend: Add items to cart
    Customer->>Frontend: Proceed to checkout
    Frontend->>Backend: Request checkout session
    Backend->>Walley: POST /checkouts
    Walley-->>Backend: privateId & publicToken
    Backend-->>Frontend: publicToken
    Frontend->>Customer: Display checkout form
    Customer->>Frontend: Complete purchase
    Frontend->>Walley: Submit payment

    Note over Customer,Walley: Phase 2: Receive the order
    Walley->>Backend: GET /notify?checkoutId={privateId}
    Backend-->>Walley: 200 OK
    Backend->>Walley: GET /checkouts/{privateId}
    Walley-->>Backend: Checkout details with orderId
    Backend->>Backend: Store order, update inventory

    Note over Customer,Walley: Phase 3: Capture the payment
    Backend->>Backend: Prepare goods for shipment
    Backend->>Walley: POST /manage/orders/{orderId}/capture
    Walley-->>Backend: 202 Accepted
    Backend->>Customer: Send shipment notification

    Note over Customer,Walley: Phase 4: Refund the order
    Customer->>Backend: Request refund
    Backend->>Walley: POST /manage/orders/{orderId}/refund
    Walley-->>Backend: 202 Accepted
    Backend->>Customer: Refund processed notification

    Note over Customer,Walley: Phase 5: Reconcile settlements
    Backend->>Walley: GET /reports/settlements
    Walley-->>Backend: Settlement data
    Backend->>Walley: GET /reports/settlements/{id}/transactions
    Walley-->>Backend: Transaction details
    Backend->>Backend: Reconcile with accounting
```

The worked example below follows one order of 1 048 SEK through all five phases: a pair of headphones at 999 SEK plus 49 SEK shipping.

## Phase 1: Create the checkout session

**When:** the customer reaches your checkout page.

Create the session from your backend. You get back a `publicToken` for rendering the checkout iframe, and a `privateId` for every later backend call about this session.

The `{checkout.id}` template in `notificationUri` resolves to that same `privateId`. Name the query parameter whatever suits your backend; this example calls it `checkoutId`. See [Templating the URI values](/docs/checkout/optional/init-checkout-features#templating-the-uri-values) for the other variables and which URIs accept them.

<Tabs defaultValue="req" values={[
{ label: 'Request', value: 'req' },
{ label: 'Response', value: 'res' }
]}>

<TabItem value="req">

```http
POST /checkouts HTTP/1.1
Host: api.uat.walleydev.com
Authorization: Bearer bXlVc2VybmFtZTpmN2E1ODA4MGQzZTk0M2VmNWYyMTZlMDE...
Content-Type: application/json

{
  "storeId": 123,
  "countryCode": "SE",
  "reference": "ORDER-2024-12345",
  "merchantTermsUri": "https://example.com/terms",
  "notificationUri": "https://example.com/api/walley/notify?checkoutId={checkout.id}",
  "cart": {
    "items": [
      {
        "id": "PROD-001",
        "description": "Wireless Headphones",
        "unitPrice": 999.00,
        "quantity": 1,
        "vat": 25.0
      },
      {
        "id": "SHIP-001",
        "description": "Standard Shipping",
        "unitPrice": 49.00,
        "quantity": 1,
        "vat": 25.0
      }
    ]
  }
}
```

</TabItem>

<TabItem value="res">

```http
HTTP/1.1 200 OK
Content-Type: application/json

{
  "data": {
    "privateId": "6f4a9bc3-8d2e-4f1a-9b7c-3e8d2f1a9b7c",
    "publicToken": "public-SE-7f1b3d2a2a73d348dfbd17d3965ff1458c249f84c695eac1"
  }
}
```

</TabItem>

</Tabs>

Use the `publicToken` to [render the checkout iframe](/docs/checkout/renderiframe). For every property you can send, see [Initialize checkout](/docs/checkout/initcheckout).

## Phase 2: Receive the order

**When:** the customer completes the purchase.

Walley calls your notification endpoint. Answer `200 OK`, then read the session back to get the `orderId` you need for the rest of the flow.

<Tabs defaultValue="notify" values={[
{ label: 'Notification Walley sends you', value: 'notify' },
{ label: 'Your call to read the session', value: 'get' }
]}>

<TabItem value="notify">

```http
GET /api/walley/notify?checkoutId=6f4a9bc3-8d2e-4f1a-9b7c-3e8d2f1a9b7c HTTP/1.1
Host: example.com
```

The `checkoutId` here carries the `privateId` from Phase 1. Answer `200 OK`, or Walley retries. See [Handle purchase callbacks](/docs/checkout/notificationcallback).

</TabItem>

<TabItem value="get">

```http
GET /checkouts/6f4a9bc3-8d2e-4f1a-9b7c-3e8d2f1a9b7c HTTP/1.1
Host: api.uat.walleydev.com
Authorization: Bearer bXlVc2VybmFtZTpmN2E1ODA4MGQzZTk0M2VmNWYyMTZlMDE...
```

Read `data.order.orderId` from the response and store it. See [Acquire information about a Checkout session](/docs/checkout/getcheckoutinformation) for the full response.

</TabItem>

</Tabs>

:::info Important
Treat this callback as the only reliable signal that a purchase completed. Create the order in your system here, not on the browser redirect.
:::

## Phase 3: Capture the payment

**When:** you ship the goods or deliver the service.

Capturing moves the money. Only captured amounts reach your account. You can capture an order with status [`NotActivated` or `PartActivated`](/docs/manage/concepts/order-lifecycle#order-statuses), in full or a part at a time.

:::info Wait briefly before capturing
The system is eventually consistent. If you capture immediately after the notification arrives, you may get `404 Not Found` or `403 Forbidden` while the order is still becoming available. Retry with exponential backoff.
:::

<Tabs defaultValue="req" values={[
{ label: 'Request', value: 'req' },
{ label: 'Response', value: 'res' }
]}>

<TabItem value="req">

```http
POST /manage/orders/7890abcd-ef12-3456-7890-abcdef123456/capture HTTP/1.1
Host: api.uat.walleydev.com
Authorization: Bearer bXlVc2VybmFtZTpmN2E1ODA4MGQzZTk0M2VmNWYyMTZlMDE...
Content-Type: application/json
Walley-Idempotency: 03304b06-cb33-4f78-bcea-86cb4b202ba0

{
  "amount": 1048.00,
  "actionReference": "SHIP-2024-12345",
  "items": [
    {
      "id": "PROD-001",
      "description": "Wireless Headphones",
      "unitPrice": 999.00,
      "quantity": 1,
      "vat": 25.0
    },
    {
      "id": "SHIP-001",
      "description": "Standard Shipping",
      "unitPrice": 49.00,
      "quantity": 1,
      "vat": 25.0
    }
  ]
}
```

</TabItem>

<TabItem value="res">

```http
HTTP/1.1 202 Accepted
Location: /manage/orders/7890abcd-ef12-3456-7890-abcdef123456
```

Walley answers `202 Accepted` and processes the capture asynchronously. Follow the `Location` header to read the order back.

</TabItem>

</Tabs>

What to know about captures:

- You can capture the whole order or part of it, for example only the items you have shipped.
- Several partial captures are fine.
- Send the `items` array on partial captures so your reconciliation stays accurate.
- For B2B customers the `items` array is required, so the customer sees the right VAT.

For partial capture, capture by amount, replacing items and error handling, see [Capture order](/docs/manage/orders/capture).

## Phase 4: Refund the order

**When:** the customer returns something, or you compensate them.

You can only refund what you have already captured. Refund the whole amount or part of it.

<Tabs defaultValue="req" values={[
{ label: 'Request', value: 'req' },
{ label: 'Response', value: 'res' }
]}>

<TabItem value="req">

```http
POST /manage/orders/7890abcd-ef12-3456-7890-abcdef123456/refund HTTP/1.1
Host: api.uat.walleydev.com
Authorization: Bearer bXlVc2VybmFtZTpmN2E1ODA4MGQzZTk0M2VmNWYyMTZlMDE...
Content-Type: application/json
Walley-Idempotency: 7c1f0a52-9e34-4b81-9d0a-2f6b8c4d1e77

{
  "amount": 1048.00,
  "description": "Full refund, customer cancellation",
  "actionReference": "REFUND-2024-12345",
  "items": [
    {
      "id": "PROD-001",
      "description": "Wireless Headphones",
      "unitPrice": 999.00,
      "quantity": 1,
      "vat": 25.0
    },
    {
      "id": "SHIP-001",
      "description": "Standard Shipping",
      "unitPrice": 49.00,
      "quantity": 1,
      "vat": 25.0
    }
  ]
}
```

</TabItem>

<TabItem value="res">

```http
HTTP/1.1 202 Accepted
Location: /manage/orders/7890abcd-ef12-3456-7890-abcdef123456
```

Walley answers `202 Accepted` and processes the refund asynchronously.

</TabItem>

</Tabs>

What to know about refunds:

- Full refunds, partial refunds, and refunds carrying fees or discounts are all supported.
- Send the `items` array on partial refunds so your reconciliation stays accurate.
- For B2B customers the `items` array is required, so the customer sees the right VAT.

For partial refunds, fees and discounts, replacement articles and error handling, see [Refund order](/docs/manage/orders/refund).

## Phase 5: Reconcile settlements

**When:** daily or weekly, as part of your bookkeeping.

Settlements normally arrive daily and hold everything you captured, less what you refunded. Read them to match Walley's payouts against your own ledger.

<Tabs defaultValue="list" values={[
{ label: 'List settlements', value: 'list' },
{ label: 'Settlement transactions', value: 'tx' }
]}>

<TabItem value="list">

```http
GET /reports/settlements?query=startDate:>=2024-12-01%20AND%20endDate:<=2024-12-04&page=1&perPage=10 HTTP/1.1
Host: api.uat.walleydev.com
Authorization: Bearer bXlVc2VybmFtZTpmN2E1ODA4MGQzZTk0M2VmNWYyMTZlMDE...
```

```http
HTTP/1.1 200 OK
Content-Type: application/json

{
  "data": [
    {
      "id": "1",
      "startDate": "2024-12-01T00:00:00",
      "endDate": "2024-12-04T00:00:00",
      "storeId": 1,
      "totalAmount": 100000.0,
      "currency": "SEK",
      "captured": 103000.0,
      "refunded": -3000.0,
      "reference": "2134"
    }
  ]
}
```

</TabItem>

<TabItem value="tx">

```http
GET /reports/settlements/1/transactions?page=1&perPage=10 HTTP/1.1
Host: api.uat.walleydev.com
Authorization: Bearer bXlVc2VybmFtZTpmN2E1ODA4MGQzZTk0M2VmNWYyMTZlMDE...
```

```http
HTTP/1.1 200 OK
Content-Type: application/json

{
  "data": [
    {
      "orderId": "7890abcd-ef12-3456-7890-abcdef123456",
      "orderNumber": "ORDER-2024-12345",
      "type": "Purchase",
      "amount": 1048.00,
      "currency": "SEK",
      "purchaseDate": "2024-12-01",
      "invoiceNumber": "11111111"
    }
  ]
}
```

</TabItem>

</Tabs>

For query parameters, file formats and reconciliation advice, see [Settlements](/docs/reports/settlements-api/settlements).

## Best practices

**Security**

- Keep API credentials in a secret store, never in source control.
- Call the API over HTTPS, and serve your notification endpoint over HTTPS.
- Restrict who can reach your notification and validation endpoints.

**Error handling**

- Retry with exponential backoff rather than in a tight loop.
- Log the requests and responses you exchange with Walley, so you can trace an order later.
- Alert on repeated failures instead of letting them queue silently.

**Idempotency**

- Send `Walley-Idempotency` on every capture, refund, cancel and reauthorize. See [Idempotency](/docs/manage/idempotency).
- Store the ids of notifications you have processed, so a repeat delivery changes nothing.
- Wrap your own state changes in a database transaction.

**Operations**

- Capture before the order reaches its `expiresAt`, or it expires uncaptured.
- Process notifications asynchronously, and answer `200 OK` straight away.
- Reconcile settlements on a schedule rather than on demand.

## Next steps

You now have an order that goes from checkout to settlement: a session created from your backend, an order created off the notification callback, money captured when you ship, a refund path, and settlement data to reconcile against.

From here:

- Decide how to handle stock. [Inventory validation](/docs/checkout/common-use-cases/inventory-validation) compares three approaches.
- Model your campaigns so refunds still add up. See [Handling discounts](/docs/checkout/common-use-cases/discounts).
- Work through the [go-live checklist](/docs/checkout/go-live-checklist) before you switch to production.

## Related documentation

- [Initialize checkout](/docs/checkout/initcheckout), every property you can send when creating a session
- [Handle purchase callbacks](/docs/checkout/notificationcallback), retry behavior and the events Walley notifies you about
- [Acquire checkout information](/docs/checkout/getcheckoutinformation), the full response and what each status means
- [Order lifecycle](/docs/manage/concepts/order-lifecycle), how an order moves between statuses
- [Capture order](/docs/manage/orders/capture) and [Refund order](/docs/manage/orders/refund), the full request schemas
- [Settlements](/docs/reports/settlements-api/settlements), querying and reconciling payouts
- [Error codes](/docs/checkout/error-codes), what each status code means and what to do about it
