Plugin screenshot thumbnail 1/7
Plugin screenshot thumbnail 2/7
Plugin screenshot thumbnail 3/7
Plugin screenshot thumbnail 4/7
Plugin screenshot thumbnail 5/7
Plugin screenshot thumbnail 6/7
Plugin screenshot thumbnail 7/7

Waver

Wave accounting for Craft Commerce. Every order lands in Wave once, balanced, with the tax split out and the discounts booked where an accountant would put them.

  • Lite — free. One balanced money transaction per completed order, plus backfill and retry from the console.
  • Pro — $99 one-off, $49/year renewal. Invoices, refunds, per-gateway and per-store routing, the connection log.

Requires Craft CMS 5.3+, Craft Commerce 5.0+ and PHP 8.2+.


What it does

Waver takes a completed Commerce order and writes it into Wave as a money transaction: money into the account it actually landed in, split across the accounts it belongs to.

Order #A1B2C3                                      DEPOSIT  $178.45  ->  Business Checking
  Items                          Sales                                       credit   150.00
  Shipping                       Shipping Income                             credit     9.95
  Sales tax                      Sales Tax                                   credit    30.50
  Discounts                      Discounts                     debit  12.00

The line items balance the deposit exactly, because Wave rejects an entry where they do not — and, much worse, accepts one that balances for the wrong reason.

Why not just use Zapier

Zapier can move an order across. What it cannot do is account for one:

Waver
Tax included in the pricetaken back out of income and booked as a liability
Discountsbooked as an increase on a contra-income account, which is how Wave records them
Sub-cent roundingabsorbed onto a rounding account, or refused if it is more than rounding
A retried syncthe same order, once — the external id is derived from the order, never random
An unconfirmed sendsurfaced for a person, never silently re-sent
An unmapped accounta named blocker before anything is sent, not a rejected mutation afterwards

Getting started

  1. Create a Wave application in the developer portal and generate a full access token.
  2. Put it in an environment variable — the token reaches every business on your Wave account:

    WAVE_ACCESS_TOKEN="..."
    
  3. In Settings → Waver, set the access token to $WAVE_ACCESS_TOKEN and press Test connection. It lists the businesses the token can reach, with their ids.

  4. Paste the business id in, press Check business, then Refresh from Wave to load the chart of accounts.
  5. Map the accounts. At minimum: Payment, Sales, and Sales tax if you charge any.
  6. Open any completed order and press Preview entry.

Or do the same from the command line:

php craft waver/wave/check                 # token, businesses, and whether the business can be posted to
php craft waver/wave/accounts --anchors    # the accounts Wave accepts as a payment account
php craft waver/sync/order 1234 --dry-run  # the exact entry, without sending it

Accounts

SettingWhat lands there
PaymentThe anchor — the bank, clearing or card account the money arrived in
SalesThe goods, with any included tax taken back out
ShippingShipping charged to the customer. Falls back to Sales
Sales taxTax collected on someone else's behalf. Use a Sales Tax subtype
DiscountsA contra-income account, subtype Discount
FeesNegative custom adjustments — a fee absorbed out of the same payment
Other incomePositive custom adjustments — a surcharge
RoundingSub-cent residuals. Falls back to Sales
RefundsThe income account a refund is taken back out of. Falls back to Sales

Invoice mode (Pro)

Instead of a ledger entry, create a Wave invoice — a document you can send — optionally approved, marked paid, and emailed by Wave.

It is the right choice less often than it looks, and Waver is direct about why:

  • Every Wave invoice line must name a product. productId is required; Wave has no free-text line. So invoice mode needs a Wave product per SKU. Waver creates and maps them, but Wave products carry no SKU of their own — the map is the only durable join, and clearing it will duplicate your catalogue.
  • Wave has no order-level charge, so shipping cannot be represented on an invoice at all. Waver refuses the order and says so rather than quietly dropping the shipping from the total.
  • Wave computes the tax itself, from the sales taxes on each line, so the invoice total can differ from Commerce's by a cent.
  • Wave allows one invoice discount, so several Commerce discounts collapse into one line.

Transaction mode has none of these limits. Use invoice mode when you want the document.

Refunds (Pro)

A successful Commerce refund becomes its own withdrawal in Wave, taken back out of the refunds account. The sale and the refund are two facts and stay two entries — Waver never edits or deletes the original.

When Waver refuses

By design, it would rather do nothing than do something wrong:

  • The order is not paid in full — a money transaction says cash arrived. Recorded as skipped with the shortfall. (Turn off Only record orders paid in full if you invoice on terms.)
  • An account is not mapped — named, per account, before anything is sent.
  • The entry does not balance beyond the rounding tolerance — refused, with the drift.
  • The business is on Wave's classic accounting — moneyTransactionCreate does not work there, and the settings screen says so up front.

Records that are in doubt

Wave's API cannot read a money transaction back. There is no transactions query, and Wave's Transaction type exposes exactly one field: an id. So if Waver sends a transaction and never hears back, nothing can determine whether it landed.

Waver does not guess. The record sits as pending with the attempt counted, and it is never re-sent automatically — not by the queue, not by waver/sync/retry. The record screen offers:

  • Ask Wave — works for invoices, which can be found again by number. For a transaction it tells you plainly that it cannot.
  • Mark as recorded — you looked in Wave and it is there.
  • Force resend — you looked in Wave and it is not.

Both of the latter need the separate Force a resend, or mark a record as recorded by hand permission, because both can put a merchant's books wrong.

Console commands

php craft waver/sync/order <id> [--dry-run] [--force]
php craft waver/sync/backfill [--limit=50] [--since=2026-01-01] [--dry-run]
php craft waver/sync/retry [--limit=50]      # failed records only, never doubtful ones
php craft waver/sync/status <orderId>

php craft waver/wave/check
php craft waver/wave/accounts [--anchors] [--flush]
php craft waver/wave/taxes [--flush]

php craft waver/log/tail [limit] [--level=error]
php craft waver/log/prune [--days=30]
php craft waver/log/clear

Twig

craft.waver is read-only — a template render is not a place from which books should change.

{% if craft.waver.isRecorded(order) %}
    Recorded in Wave.
{% endif %}

{% set record = craft.waver.recordForOrder(order) %}
{{ record.status }} {{ record.waveId }}

{% set entry = craft.waver.previewEntry(order) %}
{% for row in entry.toRows() %}
    {{ row.account }} {{ row.debit }} {{ row.credit }}
{% endfor %}

Permissions

PermissionAllows
View Wave recordsThe Records screens and the order panel
↳ Send orders to WaveThe Send to Wave button and Ask Wave
↳ Force a resend, or mark a record as recorded by handThe two actions that can double or falsify an entry
View the connection logThe log screens (Pro)
↳ Clear the connection logClear the log on the log screen

Lite and Pro

LitePro
PriceFree$99, $49/year renewal
Money transactions, balanced, deduplicated✓✓
Automatic on order completion✓✓
Account mapping, customer matching✓✓
Records screen, preview, manual send✓✓
Connection log7 days, no payloadsfull, with payloads
Invoice mode✓
Refunds✓
Trigger on an order status✓
Per-gateway payment accounts✓
Per-store Wave businesses✓
Sales tax map✓

The Wave API

Everything Waver does was verified against Wave's own schema rather than inferred. docs/wave-api.md records the contract, including the curl that reads the developer portal when its website turns bots away.

License

Proprietary. See LICENSE.md.

Lite

Pro

Plus $49/year after one year.

Installation Instructions

To install this plugin, copy the command above to your terminal.

Reviews

This plugin doesn't have any reviews.

Active Installs
1
Version
5.0.1
License
Craft
Compatibility
Craft 5
Last release
October 4, 2026
Activity (30 days)
0
Closed Issues
0
Open Issues
0
Merged PRs
0
Open PRs