---
updatedAt: 2026-10-28T13:03:43.000Z
agentTools:
  projectIndex: https://developers.solidstudio.io/llms.txt
---

# Payments & settlement

How the back-office payments API exposes CDR payment status, multi-leg settlement details, recovery actions, VAT and commission configuration, and invoice generation.

## Model

A **payment** in this API is a CDR viewed from the billing side. The payment `id` is the CDR id. Two independent status values describe it:

| Value              | Where                                                       | Meaning                                                                                                    |
| ------------------ | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `paymentStatus`    | `GET /backoffice/payments`, `GET /backoffice/payments/{id}` | Business status of the CDR payment. Exists for every CDR.                                                  |
| `processingStatus` | `GET /backoffice/payments/cdrs/{cdrId}/settlement-details`  | State of the asynchronous settlement record. Exists only when the async settlement engine handled the CDR. |

A **settlement** collects the CDR amount through one or more **legs** (for example a preauthorization capture, an off-session card charge, a prepaid-wallet debit). Each leg has one or more **attempts**. A successful leg is a **contribution**. Later attempts only ask for the **remaining amount**, so collected money is never requested twice.

## Payment status

`paymentStatus` values returned by the list and details endpoints:

| `paymentStatus`   | Meaning                                                                                         |
| ----------------- | ----------------------------------------------------------------------------------------------- |
| `PENDING`         | CDR received, payment not finished yet (includes deferred payments).                            |
| `PAID`            | Payment collected. Also returned when payment was skipped for a CDR that is not free of charge. |
| `PAYMENT_SKIPPED` | No payment taken because the charge was free of charge (private pool flagged `FREE_OF_CHARGE`). |
| `FAILED`          | Payment failed. The CDR can be exempted or recovered.                                           |
| `EXEMPTED`        | An operator wrote the debt off (`POST /backoffice/payments/exempt/{cdrId}`). No refund is made. |
| `UNKNOWN`         | Any other internal CDR state. Response-only, not accepted as a filter.                          |

`paymentMethod` is one of `CREDIT_CARD`, `WALLET`, `PAYPAL`, `CORPORATE`, `PREPAID_WALLET`, `CHECKOUT`, `UNKNOWN`. `billingProfile` is `PRIVATE`, `COMPANY` (business profile without organization) or `ORGANIZATION` (business profile with organization).

## Settlement processing status

`processingStatus` values of the async settlement record:

| `processingStatus`    | Meaning                                                                                   |
| --------------------- | ----------------------------------------------------------------------------------------- |
| `RECEIVED`            | Waiting for processing or for a booked retry.                                             |
| `PAYMENT_IN_FLIGHT`   | An external payment was requested and its result is not known yet.                        |
| `COMPLETED`           | Settlement finished, the debt is covered.                                                 |
| `FAILED`              | Collection failed. Consult `overview.actions`.                                            |
| `QUARANTINED`         | Automation stopped. Operator action needed (see `overview.diagnostics.quarantineReason`). |
| `NO_PAYMENT_REQUIRED` | Nothing to collect.                                                                       |
| `OPERATOR_EXEMPTED`   | Operator exempted the debt. No refund is made.                                            |

```mermaid
stateDiagram-v2
    [*] --> RECEIVED: CDR received
    RECEIVED --> NO_PAYMENT_REQUIRED: nothing to collect
    RECEIVED --> PAYMENT_IN_FLIGHT: leg attempt sent
    PAYMENT_IN_FLIGHT --> COMPLETED: remaining amount covered
    PAYMENT_IN_FLIGHT --> RECEIVED: shortfall, automatic retry booked
    PAYMENT_IN_FLIGHT --> FAILED: no further attempt possible
    PAYMENT_IN_FLIGHT --> QUARANTINED: outcome unsafe to automate
    FAILED --> RECEIVED: RESCHEDULE / REFRESH_PLAN
    FAILED --> OPERATOR_EXEMPTED: exempt
    QUARANTINED --> RECEIVED: RESUME / REFRESH_PLAN (repairable reasons)
    QUARANTINED --> PAYMENT_IN_FLIGHT: RETRY_TRUTH_LOOKUP
    QUARANTINED --> FAILED: ABANDON
```

Leg statuses: `PLANNED`, `IN_PROGRESS`, `SUCCEEDED`, `FAILED`, `SOURCE_UNAVAILABLE`, `SOURCE_EXHAUSTED`, `SKIPPED`, `CANCELLED`, `UNKNOWN`.
Attempt states: `SCHEDULED`, `IN_PROGRESS`, `SUCCEEDED`, `FAILED_FINAL`, `FAILED_RETRYABLE`, `RECONCILIATION_REQUIRED` (outcome unknown, not a decline), `UNKNOWN`.

Reading rules for settlement details:

* Use `overview.summary` (`payableAmount`, `confirmedCollectedAmount`, `remainingAmount`, `coverageExcess`) for money. Do not sum `attempt.amount`: it is the requested amount, not collected money.
* `legs` show only the current snapshot. `overview.summary.contributions` aggregate confirmed money across all snapshots, including contributions kept after `REFRESH_PLAN`.
* `amountsAvailable: false` means amounts cannot be interpreted reliably; amount fields are then `null`, never `0`.
* A positive `coverageExcess` records over-collection. It does not mean a refund was made.
* Use `overview.actions[].enabled` to decide which recovery mode to offer. `requiresExecutionCheck: true` means the `POST` re-checks payment sources and can still refuse.
* `overview.retry.nextAutomaticRetryAt` is the next booked automatic retry (UTC), `null` when none is booked.
* `404` with no body means no async settlement record exists. The CDR can still exist.
* The `GET` never contacts payment providers and never changes state.

## Typical flow: investigate and recover a failed payment

```mermaid
sequenceDiagram
    autonumber
    participant C as Your backend
    participant API as Public API

    C->>API: GET /backoffice/payments/config
    API-->>C: { configuredEngine, supportedRecoveryModes, ... }
    C->>API: GET /backoffice/payments?status=FAILED&page=1&size=20
    API-->>C: page of payments (paymentStatus FAILED)
    C->>API: GET /backoffice/payments/cdrs/{cdrId}/settlement-details
    API-->>C: legs, overview.summary, overview.actions
    alt an action is enabled
        C->>API: POST /backoffice/payments/cdrs/{cdrId}/reschedule-settlement?mode=RESCHEDULE
        API-->>C: 202 { cdrId, outcome: SCHEDULED }
        C->>API: GET /backoffice/payments/cdrs/{cdrId}/settlement-details
    else debt must be written off
        C->>API: POST /backoffice/payments/exempt/{cdrId}
        API-->>C: 204
    end
```

List failed payments:

```bash
curl -s -H "Authorization: $API_KEY" \
  "https://<host>/backoffice/payments?status=FAILED&fromDate=2026-10-01T00:00:00&toDate=2026-11-01T00:00:00&page=1&size=20"
```

```json
{
  "content": [
    {
      "cdrId": "cdr-123",
      "operatorName": "Example CPO",
      "evseId": "PL*EXA*E0001*1",
      "userId": "user-42",
      "totalCost": 50.00,
      "currency": "PLN",
      "totalEnergy": 21.5,
      "paymentStatus": "FAILED",
      "paymentMethod": "CREDIT_CARD",
      "startedAt": "2026-10-20T10:00:00Z",
      "stoppedAt": "2026-10-20T10:55:00Z",
      "cdrTokenType": "APP_USER",
      "billingProfile": "PRIVATE",
      "freeOfCharge": false
    }
  ],
  "totalElements": 1,
  "totalPages": 1,
  "currentPage": 1
}
```

Read settlement details (excerpt):

```bash
curl -s -H "Authorization: $API_KEY" \
  "https://<host>/backoffice/payments/cdrs/cdr-123/settlement-details"
```

```json
{
  "cdrId": "cdr-123",
  "snapshotVersion": 2,
  "processingStatus": "FAILED",
  "currency": "PLN",
  "strategy": { "name": "flexible-multi-leg", "version": 1, "entryContext": "OFF_SESSION" },
  "overview": {
    "summary": {
      "amountsAvailable": true,
      "payableAmount": 50.00,
      "confirmedCollectedAmount": 20.00,
      "remainingAmount": 30.00,
      "coverageExcess": 0.00,
      "currency": "PLN"
    },
    "actions": [
      { "mode": "RESCHEDULE", "enabled": true, "reasonCode": null, "requiresExecutionCheck": true },
      { "mode": "ABANDON", "enabled": false, "reasonCode": "NOT_RETRYABLE", "requiresExecutionCheck": false }
    ],
    "retry": { "enabled": false, "automaticRetryCount": 0, "nextAutomaticRetryAt": null },
    "diagnostics": { "hasUnansweredCustomerFundsOperation": false, "quarantineReason": null }
  }
}
```

## Recovery operations

Four operations can move a payment forward. They are not interchangeable.

| Operation          | Endpoint                                                                | Allowed when                                                                                                        | Effect                                                                                                                                                                                              |
| ------------------ | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Reprocess          | `POST /backoffice/payments/reprocess`                                   | CDR is `RECEIVED`, `PAYMENT_DEFERRED`, `PAYMENT_SKIPPED` or `PAYMENT_FAILED` internally, and comes from an OCPI CPO | Asks the CPO for its CDRs again (OCPI reconciliation over a time window around the CDR's last CPO update). A re-received CDR resumes its existing settlement; collected money is not charged again. |
| Recover settlement | `POST /backoffice/payments/cdrs/{cdrId}/reschedule-settlement?mode=...` | Async engine owns the settlement and the mode is enabled in `overview.actions`                                      | Acts on the async settlement only. Does not run CDR reconciliation.                                                                                                                                 |
| Exempt             | `POST /backoffice/payments/exempt/{cdrId}`                              | `paymentStatus` is `FAILED` and, if an async settlement exists, it accepts the exemption                            | Sets the CDR to `EXEMPTED`, releases pending preauthorizations / wallet holds. Collected money stays collected, nothing is refunded.                                                                |
| Plan continuation  | `/backoffice/payments/settlement-plan-continuations`                    | Historical async settlements, activation enabled by deployment flag                                                 | Operations repair for historical plans. See below.                                                                                                                                                  |

### Reprocess

```bash
curl -s -X POST -H "Authorization: $API_KEY" -H "Content-Type: application/json" \
  -d '{ "cdrIds": ["cdr-123", "cdr-456"] }' \
  "https://<host>/backoffice/payments/reprocess"
```

Returns `204` immediately; the work runs in the background in batches. Only one reprocess run is allowed at a time ("A operation was already triggered. Please wait for the current process to finish"). CDRs in other states, or whose connector is not an OCPI connector, are skipped.

### Recovery modes

`mode` is required. Money already collected always stays collected, and no mode refunds money.

| `mode`               | What it does                                                                                                                                                                                                                      | Typical use                                                                                                                     |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `RESCHEDULE`         | Retries now on the plan the settlement already has, only for the remaining amount. Reopens every failed payment source, including hard declines (automatic retry does not). Does not consume or reset the automatic retry budget. | `FAILED` or `RECEIVED` settlement, customer fixed their card. Blocked for quarantined settlements.                              |
| `REFRESH_PLAN`       | Re-plans the remaining amount with the strategy configured today, starting a new snapshot. Keeps earlier contributions.                                                                                                           | Strategy configuration changed and should apply. Refused while a payment is in flight or when nothing remains.                  |
| `RETRY_TRUTH_LOOKUP` | Asks the provider once more about a payment it never answered for. Requests no new payment.                                                                                                                                       | Quarantined with an unanswered payment.                                                                                         |
| `RESUME`             | Releases a quarantined settlement on its existing plan after its input data was fixed. Keeps attempts already used.                                                                                                               | Quarantine caused by missing/unusable data. Refused while a payment is unanswered.                                              |
| `ABANDON`            | Closes the settlement for good. Nothing more is attempted.                                                                                                                                                                        | Quarantined settlement whose money is accounted for but cannot be settled automatically. Refused while a payment is unanswered. |

```bash
curl -s -X POST -H "Authorization: $API_KEY" \
  "https://<host>/backoffice/payments/cdrs/cdr-123/reschedule-settlement?mode=RESCHEDULE"
```

```json
{ "cdrId": "cdr-123", "outcome": "SCHEDULED" }
```

The HTTP status follows the `outcome`:

| HTTP  | `outcome`                                                                                                                                                                  |
| ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200` | `COMPLETED`                                                                                                                                                                |
| `202` | `SCHEDULED`, `TRUTH_RECORDED`, `ABANDONED`, `OPERATOR_EXEMPTED`, `WAITING_FOR_CUSTOMER_FUNDS_TRUTH`, `RETRYABILITY_UNKNOWN_ASK_AGAIN`, `BLOCKED_CUSTOMER_FUNDS_UNANSWERED` |
| `404` | `NOT_FOUND` (no async settlement for this CDR)                                                                                                                             |
| `409` | `NOT_RETRYABLE`, `BLOCKED_QUARANTINED`                                                                                                                                     |
| `501` | `ASYNC_DISABLED` (async engine not active)                                                                                                                                 |

`RETRYABILITY_UNKNOWN_ASK_AGAIN` means no payment source could be reached to decide; try again later. Disabled actions in settlement details carry a `reasonCode`: `ASYNC_DISABLED`, `MANUAL_RETRY_BLOCKED`, `STATE_NOT_RETRYABLE`, `NOT_RETRYABLE`, `CUSTOMER_FUNDS_UNANSWERED`, `BLOCKED_CUSTOMER_FUNDS_UNANSWERED`, `MONEY_DATA_UNAVAILABLE`, `NO_REMAINING_AMOUNT`, `BLOCKED_QUARANTINED`.

### Automatic retry

When enabled (`automaticRetryEnabled` in `/backoffice/payments/config`), the async engine books later passes over the same frozen plan for the remaining amount. The delays are deployment configuration (default `6h, 24h, 24h`, so three retries after the first pass). Automatic retry does not retry a hard-declined source. A quarantined settlement never resumes by itself.

### Exempt

```bash
curl -s -X POST -H "Authorization: $API_KEY" \
  "https://<host>/backoffice/payments/exempt/cdr-123"
```

`204` on success. `404` when the CDR does not exist or is not in payment-failed state. `409` when the async settlement refuses the exemption in its current state.

### Settlement plan continuation

Operations tool for async settlements created with an older plan interpretation. It continues only the outstanding debt and keeps captures, decline history and retry counters. It does not select today's strategy (unlike `REFRESH_PLAN`), and does not undo exemptions.

1. `GET /backoffice/payments/settlement-plan-continuations?afterCdrId=&limit=25` — list assessments, keyset-paged by CDR id (`limit` 1–50, default 25). Pass the last `cdrId` as `afterCdrId` for the next page.
2. `GET /backoffice/payments/settlement-plan-continuations/{cdrId}` — dry run. Returns `snapshotVersion`, `strategyConfigHash`, `recipeDigest`, `remaining`, `runtimeLegs`, `historicalContracts` and a `decision`.
3. `POST /backoffice/payments/settlement-plan-continuations/{cdrId}` with `{ "repairId": "<new UUID>", "expectedSnapshotVersion", "expectedConfigHash", "expectedRecipeDigest" }` copied from the dry run.

`decision` values: `ELIGIBLE`, `ACTIVATED`, `ALREADY_ACTIVATED`, `DISABLED`, `NOT_FOUND`, `STATE_EXCLUDED`, `RECONCILIATION_REQUIRED`, `NO_REMAINING_DEBT`, `NO_ADAPTABLE_LEGS`, `MANUAL_REVIEW_REQUIRED`, `EXPECTATION_MISMATCH`, `REPAIR_ID_CONFLICT`. Activation requires `planContinuationEnabled: true` in `/backoffice/payments/config`; discovery and dry runs are always available. A repeated accepted command is a no-op.

## Settlement strategies

A **settlement strategy** is a named, versioned policy that decides which payment sources may cover a CDR and in which order. It is deployment configuration; the API only reads it.

* A strategy has **variants**. One variant is selected per CDR by **entry context** (`NO_PAYMENT`, `OFF_SESSION`, `PROVIDER_FUNDS_HOLD`, `PREPAID_WALLET`), then by satisfied **entry preconditions** (for example `off-session-candidate-available`, `provider-funds-hold-available`, `prepaid-wallet-available`), then by lowest **priority**.
* A variant is an ordered list of **blocks** (for example `full-off-session`, `funds-hold-capture`, `remainder-off-session`, `wallet-free-balance-debit`, `wallet-overdraft-debit`, cleanup of an unused preauthorization). A block can be required or optional and can activate immediately, after named predecessor outcomes, or only after the debt is paid (cleanup).
* **Coverage requirement** says whether a block must settle the whole remainder (`must_settle_remaining`), may contribute part of it (`may_contribute_partial`), or moves no money (`no_customer_funds`). Requested amounts are always capped at the remaining amount.
* The plan selected for a CDR is **frozen**: automatic retry and `RESCHEDULE` reuse it; only `REFRESH_PLAN` adopts the current strategy. The frozen strategy is visible in settlement details as `strategy.name`, `version`, `configHash`, `selectedVariantName`, `entryContext`.
* Prepaid-wallet balance and overdraft limits are enforced by the wallet at debit time, not by the strategy.

```bash
curl -s -H "Authorization: $API_KEY" \
  "https://<host>/backoffice/payments/settlement-strategies"
```

```json
{
  "active": "example-flexible-multi-leg",
  "strategies": [
    {
      "name": "example-flexible-multi-leg",
      "version": 1,
      "configHash": "3f9a...",
      "canonicalForm": "...",
      "source": "...",
      "active": true
    }
  ]
}
```

## VAT and commission

VAT rates are stored per country (ISO 3166 alpha-2) as a percentage 0–100. `PUT` upserts.

```bash
curl -s -X PUT -H "Authorization: $API_KEY" -H "Content-Type: application/json" \
  -d '{ "countryCode": "PL", "rate": 23 }' \
  "https://<host>/backoffice/payments/vat"
```

`GET /backoffice/payments/vat/{countryCode}` returns `{ "countryCode": "PL", "rate": 23 }` or `404`. `GET /backoffice/payments/vat` returns a page sorted by country ascending.

The payments commission is a single percentage (0–100): `GET /backoffice/payments/commission` returns `{ "percentage": 5.00 }`, `POST /backoffice/payments/commission` with `{ "percentage": 5.00 }` changes it (`204`).

## External reference

`PUT /backoffice/payments/{cdrId}/external-reference` with `{ "externalInvoiceNumber": "INV-2026-0001" }` (max 255 chars) stores an invoice number from an external billing system on the CDR (`204`). It is returned as the external invoice number in payment details.

## Invoices

`POST /backoffice/invoices` generates an invoice PDF for a CDR. Available only when the invoices feature is enabled in the deployment. `Accept-Language` (optional) selects the invoice language.

```bash
curl -s -X POST -H "Authorization: $API_KEY" -H "Content-Type: application/json" \
  -H "Accept-Language: en" \
  -d '{ "cdrId": "cdr-123" }' \
  "https://<host>/backoffice/invoices"
```

```json
{ "status": "DONE", "invoicePdf": "https://files.example.com/invoices/cdr-123.pdf" }
```

When `status` is not `DONE`, `invoicePdf` may be missing. `400` is returned when the CDR state does not allow invoice generation, `404` when the CDR or invoice template is not found.

## Charging pool payment methods

`PUT /backoffice/charging-pools/{chargingPoolId}/payment-methods` controls payment at a private charging pool:

* `{ "paymentMethods": ["FREE_OF_CHARGE"] }` switches payments off. Sessions there are free for users with access, except corporate sessions, which are always settled to their organization. Their CDRs report `freeOfCharge: true` and `paymentStatus: PAYMENT_SKIPPED`.
* `{ "paymentMethods": null }` removes the assignment. The pool offers the environment's payment methods and is paid like a public pool.

No other list is supported. Returns `204`.

## Listing parameters

`GET /backoffice/payments`:

| Parameter             | Notes                                                                                               |
| --------------------- | --------------------------------------------------------------------------------------------------- |
| `page`                | 1-based, default `1`, min `1`                                                                       |
| `size`                | default `10`, min `1`                                                                               |
| `cdrId`, `userId`     | exact match                                                                                         |
| `userEmail`, `evseId` | case-insensitive substring                                                                          |
| `fromDate`, `toDate`  | `yyyy-MM-ddTHH:mm:ss`, no offset; filter on session start, `fromDate` inclusive, `toDate` exclusive |
| `status`              | `PENDING`, `PAID`, `PAYMENT_SKIPPED`, `FAILED`, `EXEMPTED`                                          |
| `cdrTokenType`        | repeatable: `APP_USER`, `RFID`, `OTHER`, `AD_HOC_USER`                                              |
| `billingProfile`      | repeatable: `PRIVATE`, `COMPANY`, `ORGANIZATION`                                                    |

Sorting is fixed: newest session start first. `GET /backoffice/payments/filters` returns the values to show in filter UIs (`cdrTokenTypes` without `AD_HOC_USER`, `profiles`).

`GET /backoffice/payments/vat`: `page` 1-based (default `1`), `size` default `10`, max `100`, sorted by country.

## Related

* [Getting started](getting-started.md)
* [Authorization](authorization.md)
* [Sessions and CDRs](sessions-cdrs.md)
* [Customers and tokens](customers-tokens.md)
* [Charging network](charging-network.md)
* [Tariffs and coupons](tariffs-coupons.md)
* [Bulk operations](bulk-operations.md)