> For the complete documentation index, see [llms.txt](https://developer.vario-software.de/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.vario-software.de/documentation/apps/app-backends/pos-payment-methods.md).

# POS Payment Methods

Provide a payment method the cash register can use, fulfilled by your app.

A POS payment method is what the cashier presses at the register. Most are handled by the ERP itself — cash goes straight into the till. But a payment method can also be **handed to an app**: the ERP places a payment *order*, something external fulfils it, and the receipt only closes once the result comes back.

That "something external" is a **payment consumer**. The VARIO local-link is one — it drives card terminals in the shop. An app can be another.

{% hint style="info" %}
Read [App Backends](/documentation/apps/app-backends.md) first.
{% endhint %}

This page describes the platform mechanism. The reference implementation of a consumer today is the **local-link** (card terminals over the local network). If you are building an app-side consumer, expect to work directly against the REST resources described here.

## The five pieces

| Entity               | What it is                                                                           |
| -------------------- | ------------------------------------------------------------------------------------ |
| **Payment backend**  | Who is responsible: `SELF` (the ERP) or `APP` (you)                                  |
| **Payment method**   | The button on the register — `CASH` or `CARD`, pointing at a backend                 |
| **Payment consumer** | The worker that fulfils orders, with a heartbeat                                     |
| **Payment rule**     | Routes an order to a consumer, based on method, operation, register or sales channel |
| **Payment order**    | One unit of work, identified by an `externalPaymentId`                               |

## Registering the backend

There is no framework migration helper here — create the backend through the API, with the same identifying fields as every other app backend:

```json
{
  "label": "My Terminal App",
  "type": "APP",
  "appId": "<your-app-identifier>",
  "placePayment": true,
  "canCancel": true,
  "canRefund": true
}
```

`placePayment` is the switch that matters. It is only meaningful for `type: "APP"`:

> With `SELF`, the payment is booked directly in the register. With `APP` and `placePayment`, an order is **placed** for an external consumer to fulfil.

`canCancel` and `canRefund` advertise what you support. They are not cosmetic — see the warnings below.

## The runtime flow

```
cashier presses method
        │
        ▼
POS adds a document line  ──►  ERP creates a PosPaymentOrder (externalPaymentId)
                                        │
                                        ▼
                          rule routes it to a consumer
                                        │
                     consumer picks it up (push, or request-next)
                                        │
                          consumer performs the payment
                                        │
                                        ▼
                   PUT …/orders/{externalPaymentId}/status?status=SUCCESSFUL
                                        │
                                        ▼
        POS is polling the order  ──►  receipt closes
```

Note what triggers this: **the POS adds a document line**, not a call to a payment API. The order is a side effect. If no order appears, the POS simply continues — that is the `SELF` path.

### Picking up work

Either be pushed to (the local-link subscribes over a WebSocket) or ask for the next order:

```
POST /community/{version}/erp/pos/payment/consumer/{id}/request-next
```

No request body, and it returns at most one order. Send a heartbeat so the ERP can show whether you are alive:

```
PUT /community/{version}/erp/pos/payment/consumer/{id}/heart-beat
```

Process **one order at a time**. A terminal that is already busy rejects a second request, and parallel chains send payments to an occupied device.

### Reporting the result

**That is the entire write-back surface — `status` and `errorMessage`, as query parameters.** There is no field for an authorisation code, card reference, receipt number or the actual amount. If you need any of that later (for example a terminal reference required to reverse the payment), you must store it yourself, keyed by `externalPaymentId`. The local-link keeps its own store for exactly this reason.

```
PUT /community/{version}/erp/pos/payment/orders/{externalPaymentId}/status
    ?status=SUCCESSFUL|CANCELLED|REJECTED|PROCESSING|PENDING
    &errorMessage=<optional text>
```

The ERP updates the receipt itself once the status is `SUCCESSFUL`; the POS reloads the document afterwards.

### Operations

| `paymentOperation` | Meaning                                            | Extra                               |
| ------------------ | -------------------------------------------------- | ----------------------------------- |
| `PAYMENT`          | Normal payment                                     | —                                   |
| `CANCEL`           | Reverse a specific earlier payment                 | `cancelledExternalPaymentId` is set |
| `REFUND`           | Pay money back with no original payment to reverse | —                                   |

`CANCEL` needs the original transaction, so you must be able to find your own record of it. `REFUND` stands alone.

## What to watch out for

{% hint style="warning" %}
**Never report a payment as failed because reporting failed.** If the card was charged and the status call then errors, retrying the *report* is correct — retrying the *payment* is not, and reporting `REJECTED` tells the register the customer did not pay. That is how double charges happen. Retry the status call, treat `4xx` as final, and log loudly if you have to give up: the customer has been charged and the order will sit unresolved.
{% endhint %}

{% hint style="warning" %}
**An order can be cancelled while you are working on it.** The cashier can give up and set it to `CANCELLED`. If the terminal then completes anyway, you have a charged payment against a cancelled order — detect it, don't overwrite the status, and flag it for manual reconciliation. Watch the order while you process it so you can abort at the device.
{% endhint %}

{% hint style="warning" %}
**Aborting is a request, not a command.** Once a terminal reaches the point of no return it may ignore the abort and complete normally. Handle the late success.
{% endhint %}

{% hint style="info" %}
**Your `errorMessage` is shown to the cashier verbatim.** Write messages a shop assistant can act on, and remember it travels as a URL query parameter.
{% endhint %}

{% hint style="warning" %}
**Timeouts don't line up.** The POS gives up waiting for pickup after about 20 seconds and abandons the payment after about three minutes. A consumer that only polls every 30 seconds will trigger the "payment system not responding" dialog even when it is perfectly healthy. Subscribe to pushes, or poll faster than the pickup timeout.
{% endhint %}

{% hint style="warning" %}
**Capabilities have configuration consequences.** A backend with neither `canCancel` nor `canRefund` can **block the register from being activated** at all, because cancelling a receipt then becomes impossible — and it prevents splitting a receipt across several card payments. Declare honestly, but understand the cost.
{% endhint %}

{% hint style="warning" %}
**A rule is required.** A `placePayment` backend with no matching payment rule fails **at payment time**, not at configuration time — the cashier sees "no payment consumer could be determined". Ship setup instructions, or check for a rule during install.
{% endhint %}

{% hint style="warning" %}
**There is no idempotency key.** `externalPaymentId` is the only anchor you get, on both the pickup and the status call. Guard against processing the same order twice yourself.
{% endhint %}

{% hint style="info" %}
**Partial payments are normal.** Each split is a separate line and a separate order. The receipt closes when it balances, so expect several cycles per sale.
{% endhint %}

{% hint style="warning" %}
**Fiscal rules interrupt you.** Adding a payment line can come back asking for a decision (signature-device handling under German cash-register law). If you drive the POS side, handle that instead of treating it as a failure — otherwise cancellations with partial payments fail silently.
{% endhint %}

{% hint style="info" %}
**Cash-journal behaviour is set on the method, not the backend.** Whether a method is counted and carried in the cash book, and how it is skimmed at closing, is configured on the payment method regardless of who fulfils it.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://developer.vario-software.de/documentation/apps/app-backends/pos-payment-methods.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
