A shared device on a busy counter, built to be trusted.
Registers get left unattended, passed between people and used in a rush. So nothing the browser sends can set a price, choose a register or approve a refund.
Principles
The server decides
The register never decides what something costs or whether an order is paid. Your server checks every price, approval and payment.
Card data stays with Stripe
Cards are processed by Stripe, a PCI DSS Level 1 provider. Card details never reach the register, your site or WooCommerce.
Everything is on the record
Approvals, refusals, PIN changes, lockouts and cash movements go into an audit log nobody can edit.
What could go wrong, and what stops it
- Someone changes a price in the browser
- The server prices every line itself. Price changes and discounts need permission or a supervisor's approval, a reason, and are recorded.
- An approval is reused for something else
- Each approval is single-use, lasts 120 seconds, and is tied to the action, the approver, the cashier, the register and the exact amounts shown. Anything different is refused.
- Someone guesses a supervisor's PIN
- PINs are hashed, easy ones are refused, and wrong guesses pause that supervisor: five on one register, twenty across them all. Each lockout within a day doubles the pause, up to a day, and if it keeps happening, managers see a warning in wp-admin.
- A device pretends to be another register
- A supervisor or manager sets each device up as a register, and the server reads which register it is from a secure cookie, never from what the browser sends. Device access expires after 90 days unused and can be revoked.
- Someone signs in another way
- Register staff can only use the register through the normal WordPress login in the browser. Application passwords and token logins are turned off for them.
- A card is charged twice
- Every order, payment and refund carries a one-time key and takes a lock, so a retry can't charge again. Taking cash cancels any card attempt still waiting.
- A card payment doesn't match the sale
- Before taking the money, your site checks the amount, currency and order with Stripe. A mismatch cancels the payment.
- A Stripe message is faked, or comes from another site
- Stripe's messages to your site must be signed and recent, and your site fetches the payment from Stripe again before acting on it. A payment from another site on the same Stripe account, such as a staging site, is ignored and recorded. A message can only ever complete a payment, never cancel one.
- Card details are exposed
- Cards are processed by Stripe, a PCI DSS Level 1 provider. Card details go from the reader to Stripe and are never stored in the register or WooCommerce. The Stripe secret key never reaches the browser.
- A register is left unattended
- The idle lock is kept on the server and locks the whole login, including wp-admin, not just the screen.
- A cashier works out the expected cash
- During a blind count the server withholds the expected amount, including from the Orders list, until the drawer is closed.
- Someone harvests customer details
- Searches need at least three characters, and emails and phone numbers are masked in every result and order. Adding a customer never reveals whether an email address already has an account.
- Someone fakes a receipt printer
- Each register's printer has its own password, shown once and stored only as a hash, and printers connect over HTTPS only. Repeated bad passwords block the address. A print job not collected within 30 seconds is canceled, so a late drawer kick can never open the drawer.
- A script on the page misbehaves
- The register page loads none of your theme's or other plugins' scripts, and a strict content security policy allows only its own code.
- Someone floods the register
- Orders, payments, refunds, approvals and searches are rate limited, and receipt emails and new customers are capped per location each day.
- Nobody knows who did what
- An append-only audit log records approvals, refusals, PIN changes, lockouts, cash movements and failed card attempts.
Your data stays yours
- Orders, products and customers are ordinary WooCommerce data, in your own database.
- Supervisor PINs and store credit codes are stored only as scrambled hashes.
- Deleting the plugin keeps its data unless you turn on Delete all Point of Sale data. It never deletes orders, products or customers.
- The Stripe secret key comes from your wp-config file, an environment variable or the WooCommerce Stripe Gateway settings. The plugin never stores it or sends it to the browser.
Your part
The register is only as safe as the site it runs on. Three things are worth doing.
- Use HTTPS everywhere
- The register and receipt printers only work over HTTPS. Once your whole site is on HTTPS, turn on HSTS at your web server or CDN so a register can't be pushed back to an insecure connection.
- Include the register in your backups
- A normal full database backup already covers it. If your backup tool picks tables, include the plugin's own tables, which are listed in the plugin's documentation.
- Keep Stripe secrets in your config
- Put the Stripe secret key and webhook secret in wp-config or environment variables, so they stay out of the database.
Found a security problem?
Tell us privately through the contact form and leave out anything that would let someone else use it. We’ll look into it and let you know what we find.
Related
- Staff and approvalsRoles that fit the counter, supervisor approval with a PIN, training mode and location access.
- Checkout and paymentsStripe Terminal card payments, cash with change due, split payments, tax-exempt sales and receipts.
- Built on WooCommerceYour products, stock, tax, orders and customers stay in WooCommerce. The register sits on top.
- For developersWordPress-style hooks and a JavaScript API for adding screens and features to the register.
Ring up a sale yourself.
The live demo is the real register, running in your browser with nothing to install. Turn on your screen reader and try a full sale.