# Architecture

WCPOS comes in two halves: a plugin on your WordPress site, and the POS app on each till. The till sells from its own copy of your store and keeps that copy in step with WooCommerce in the background. This page shows where your data lives and what reaches your server, for developers and for store owners who want to know.

Here to fix something rather than read the design?

* **Updating the app:** first [check that the till has sent everything](/de/releases/previous-versions.md#check-sync). See [Sync before updating](#storage).
* **A product is missing on the till:** see [When a product is missing or out of date](/de/manage-store/products/product-sync.md#goes-wrong).
* **A sale hasn't reached your store:** see [Sales waiting to send](/de/sell/offline-mode.md#offline-writes).

## The two halves[​](#two-part-system "Direkter Link zu The two halves")

<!-- -->

1. **The POS app** runs on each till: in a browser at `/pos/` on your site (the address can be changed), in the desktop app, or in the iOS and Android apps. See [Installation](/de/getting-started/installation.md) for what each one needs. Every screen reads and writes a database on the till, so a search or an added item never waits for your server.
2. **The sync engine** inside the app moves changes both ways. It pulls what changed in your store and sends what the till changed.
3. **The WCPOS plugin** runs on your WordPress site and connects the till to WooCommerce. WooCommerce stays the one true copy of every product, customer and order.

## What the till keeps on the device[​](#local-database "Direkter Link zu What the till keeps on the device")

**Health** → **Database** shows the till's copy of your store: one row per kind of record, with how many are on this device (**On device**) and how many your store has (**On server**). What each row's menu does is on [Store Health](/de/troubleshooting/store-health.md#coverage).

![Health, Database tab: the table of records on this till, from Products down to Tax rates, each with its count on the device, the count on the server, coverage and size](/img/2.x/reference/architecture/en/tablet-light-regular--ar-1-on-device.png)12345678

1. **Products** — a starting batch of 100 in the POS's default sort (name, A to Z), then the rest while the till is quiet. See [Download & Sync](/de/manage-store/products/product-sync.md).
2. **Variations** — download with their products.
3. **Orders** — open and recent orders stay on the till. Older ones download when someone opens them.
4. **Refunds** — refunds from the last 92 days, plus any refund on an order you've opened.
5. **Customers** — download a little at a time while the till is quiet.
6. **Categories** — with **Brands** and **Tags** below it.
7. **Coupons** — on the till with or without Pro.
8. **Tax rates** — your store's tax rates.

A red **N stuck** badge on a row means records of that kind need you. See [Records that need attention](/de/troubleshooting/store-health.md#stuck-records).

Above the table, the figure to watch is **changes waiting to send**: sales and edits made on this till that your store hasn't received yet. They stay on the device until it has. Open orders you haven't synced and changes listed in a panel aren't counted, and haven't reached your store either. See [Sales waiting to send](/de/sell/offline-mode.md#offline-writes).

Records kept outside this table

A few things live outside this table. Payment methods, tax classes, shipping methods and order statuses are read from your store each time the app starts, and the last answer is kept for when the till is offline. Register sessions, closures and cash movements have their own records, and their own queue to your store.

### How the database is kept[​](#storage "Direkter Link zu How the database is kept")

Each cashier in each store has their own copy on the till, and in a browser one tab runs the POS for a site at a time. When an app update changes how that copy is stored, the till downloads it again.

Sync before updating

Sales and edits still waiting in the old copy aren't sent. Before you update the app, check in **Health** → **Database** that **changes waiting to send** reads 0 with no panel below the figures, and sync each open order. See [Check that a till has sent everything](/de/releases/previous-versions.md#check-sync).

Developer detail: storage engine, tabs and app updates

* **One database per site, store and cashier.** Two cashiers on one till, or one cashier in two stores, never share local data.
* **SQLite on every platform.** In the browser it is SQLite running over the browser's private file storage, in one worker; in the iOS and Android apps it is the system's SQLite; the desktop app keeps it in SQLite in the app's main process.
* **Kept on disk.** The till's copy survives closing the browser or the app and restarting the device.
* **One live tab per site.** In a browser, one tab runs the POS for a site. Open a second tab and it offers to take over; the first tab stops.
* **Lists are worked out in the database.** Filtering, sorting and paging run inside the database where they can, so a list of thousands of orders hands the screen only the rows it shows.
* **No migration in place.** When an app update changes the database layout, the app opens a fresh database and downloads again from your store. Changes still waiting in the old one are not sent, and open orders don't move across: sync the till before you update the app.

## How the till and your store stay in step[​](#data-synchronization "Direkter Link zu How the till and your store stay in step")

The sync engine doesn't mirror your whole store on a timer. It works from what the till's screens need, and asks your server as little as it can.

* **Checking for changes.** The till asks your store what has changed, as often as its [sync preset](/de/troubleshooting/store-health.md#sync-presets) says (**Eco**, **Balanced** or **Realtime**). Each till keeps its own preset: changing it on one till doesn't change the others.
* **What the screen needs.** Each screen tells the engine what it is showing. The engine answers from the till's database when it already has the records, and asks your store only when it doesn't.
* **In the background.** The first batch of products and the open and recent orders download first. The rest of the products and customers follow while the till is quiet.
* **Sending changes.** Every sale or edit is saved on the till first, then sent to your store. A change your store refuses is never retried on its own: it waits under **N changes never reached your server**, with **Send again** and **Discard**. See [Store Health](/de/troubleshooting/store-health.md#refused-changes).

Developer detail: change feed, request budgets and lanes

* A store with nothing new answers the change check with `304 Not Modified` and no body.
* Writes go through a durable queue on the till, which posts them to `wcpos/v2/push/{collection}`.
* The request budgets, back-off and measured numbers are on [Sync Performance](/de/developers/sync-performance.md). Every lane is on [How the Sync Engine Works](/de/developers/sync-engine.md).

## How checkout records a sale[​](#checkout-architecture "Direkter Link zu How checkout records a sale")

The checkout is part of the POS. The till takes each payment itself and records it against one WooCommerce order, instead of opening WooCommerce's order-pay page for every sale.

Paying offline needs a sale your store already has

A payment can be taken offline only on a sale that reached your store before the connection dropped: its order number shows above the items in [Checkout](/de/getting-started/make-sale.md). A sale started after the connection dropped waits on the till and is paid once the store is back. See [Take a payment offline](/de/sell/offline-mode.md#take-payment-offline).

* **A tile for each method.** The till draws a tile for each payment method switched on for the POS. A method it can't drive shows greyed out with the reason, never hidden. See [When a tile can't be used](/de/settings/checkout/payment-methods.md#unavailable-tiles).
* **One order, many payments.** A split sale stays one WooCommerce order, with one set of items, taxes, stock changes and one order number, so reports count it once. A refund is a normal WooCommerce refund, shared out across the payments it pays back.
* **The store decides.** After each payment, your store answers with what has been paid and what is left, and the till completes the sale on your store's figures, not its own.
* **Safe to retry.** Each payment has an ID the till creates, so a payment sent twice is recorded once.
* **Offline.** On a sale your store already has, Cash still completes when the connection drops: the payment is recorded as **Settles later** and sent when the connection returns. See [Take a payment offline](/de/sell/offline-mode.md#take-payment-offline).

Capture modes and payment routes

`GET wcpos/v2/payment-methods` describes every gateway: its `kind` (`cash`, `card`, `stored_value`, `bank_transfer` or `other`), how it takes the money (`capture`), and what it allows (`capabilities`: partial amounts, change, refunds, tips, offline, void). Each payment is a row in the order's `_wcpos_payments` meta, and after each payment the store answers with the order's `paid` and `balance`.

Each method's `capture.mode` says who takes the money. Every payment call goes to the handler registered for its mode.

| Mode      | How the money is taken                                                                                                                                                                                               | Plugin |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ |
| `manual`  | The cashier takes it and records it. The default for Cash, Card, Cash on delivery, Direct bank transfer and Check payments.                                                                                          | Free   |
| `webview` | The gateway's own payment page, on the checkout's **Legacy** tab. The default for every other gateway. See [When the Legacy tab's payment page won't load](/de/settings/checkout/payment-methods.md#legacy-notices). | Free   |
| `gateway` | The gateway's own `process_payment()` runs with the values the till collected. A gateway opts in through a filter.                                                                                                   | Free   |
| `server`  | Your store asks the card terminal's provider to take the payment.                                                                                                                                                    | Pro    |
| `device`  | The till drives the card reader through the provider's SDK, and the provider confirms the payment.                                                                                                                   | Pro    |

The payment routes hang off the order, by its WooCommerce ID:

* `POST wcpos/v2/orders/{id}/payments` records a payment. The payment's ID makes a retry safe.
* `.../payments/{uuid}/intent`, `/capture`, `/void` and `/refund` (`POST`), and `/status` (`GET`), act on one payment. Each command carries its own command ID.
* `POST wcpos/v2/orders/{id}/close` completes the order.

Every command carries the order's `revision`; a command without one is refused with `428`.

To build a POS-only gateway, start from the [Gateway Template](/de/developers/gateway-template.md).

## Why some website changes don't reach the till[​](#api-extensions "Direkter Link zu Why some website changes don't reach the till")

The till gets products, customers and orders from the WCPOS plugin, which reads them from WooCommerce. A plugin that changes the data WooCommerce sends to apps reaches the till. A plugin that changes only how your website looks, or only the online checkout, doesn't. To check a plugin, start with [Understanding the WooCommerce REST API](/de/developers/woocommerce-rest-api.md).

Developer detail: API versions

The till talks to your store only through the plugin's REST routes, which read from WooCommerce inside your site. The plugin registers two namespaces next to WooCommerce's own `wc/v3`.

* **`wcpos/v2`** is what the app uses: reading records, the change feed, the queue of changes, payments, registers and sessions. Its read routes forward to `wc/v3` inside your site, so a plugin that changes WooCommerce's REST responses changes what the till sees.
* **`wcpos/v1`** still registers. Its data routes (products, variations, orders, customers, coupons, taxes, categories, tags, brands) are frozen. Its service routes (stores, settings, receipts, templates, print jobs and the rest) are the same code as their `wcpos/v2` versions.

### App updates and other sync tools[​](#wcpos-v2-namespace "Direkter Link zu App updates and other sync tools")

The app and the plugin are updated together: a till on an older app than the plugin needs shows [SYNC341](/de/error-codes/SYNC341.md) until the app is updated. POS prices on line items are stored where other sync tools can read them; see [How POS price overrides are stored](/de/developers/discount-records.md#how-pos-price-overrides-are-stored).

Developer detail: requests, sign-in and order IDs

* **Routes load for POS requests.** The plugin registers its routes only on requests aimed at them: a `wcpos/` route, an `X-WCPOS` header or `?wcpos=1`. The rest of your site's REST traffic doesn't load them.
* **The app and the plugin move together.** Each app request carries `X-WCPOS-Protocol`. A plugin that needs a newer protocol answers `426` with `wcpos_update_required`, and the till shows [SYNC341](/de/error-codes/SYNC341.md) until the app is updated.
* **Public routes.** `wcpos/v2/site`, `wcpos/v2/ping` and `wcpos/v2/echo` need no sign-in. The app uses them to find your site and test the connection before anyone signs in. On hosts that hide `/wp-json/` or strip headers, the app falls back to WordPress's `/?rest_route=` address and to query-string forms of its headers (`?wcpos=1`, `wcpos_protocol`). If neither answers, the till shows [AUTH431](/de/error-codes/AUTH431.md).
* **Signing in.** The app signs in through the `/wcpos-auth` page on your site and receives a JWT access token that lasts 30 minutes and a refresh token that lasts 30 days. The filters `wcpos_jwt_access_token_expire` and `wcpos_jwt_refresh_token_expire` change them. Which role reaches which route is on [Role Endpoint Access](/de/developers/role-endpoint-access.md).
* **Orders carry a UUID.** The till creates a UUID for each order, sends it as the `_woocommerce_pos_uuid` meta, and your store keeps it. Order pulls key on the UUID.
* **POS prices on line items** are stored so other sync tools can read them. See [How POS price overrides are stored](/de/developers/discount-records.md#how-pos-price-overrides-are-stored).
* **Stores (Pro).** With several stores, the till sends its store in the `X-WCPOS-Store` header and the plugin passes it to WooCommerce as `store_id`. An order's store comes from the `_pos_store` entry in its meta. Pro's routes for editing stores and pairing a customer display register under both namespaces.

With WCPOS Pro

Pro adds card readers, editing stores, and pairing a customer display. With several stores, each store gets its own prices on the till. See [Setting up stores](/de/manage-store/stores/set-up-stores.md).

## What the design gives and costs[​](#architecture-pros-and-cons "Direkter Link zu What the design gives and costs")

* **Instant search and cart.** Both read the till's own database.
  <!-- -->
  * **The cost:** the till's copy has to be kept in step with your store. That is the sync engine's whole job.
* **Selling through a dropped connection.** Sales and edits wait on the till and send when the connection returns. Cash still completes on a sale your store already has.
  <!-- -->
  * **The cost:** a sale started after the connection dropped is paid only once the store is back. See [Take a payment offline](/de/sell/offline-mode.md#take-payment-offline).
* **Apps built for a till.** Desktop, iOS and Android apps, not a web page in a frame.
  <!-- -->
  * **The cost:** the POS sees your store only through the REST API. WordPress themes and hooks can't restyle it, and a plugin that changes only the web checkout or your theme doesn't reach the till. See [Why don't some plugins work in the POS?](/de/developers/woocommerce-rest-api.md#why-dont-some-plugins-work-in-the-pos)

## Related Documentation[​](#related-documentation "Direkter Link zu Related Documentation")

[How the Sync Engine WorksLanes, the change feed and the queue of changes, in detail.](/de/developers/sync-engine.md)

[Store HealthSee the till's database, sync presets and changes waiting to send.](/de/troubleshooting/store-health.md)

[Understanding the WooCommerce REST APIWhat the REST API is, and why some plugins don't reach the POS.](/de/developers/woocommerce-rest-api.md)
