Transactioncomplete: `balanceDue` is always 0 and `paymentMethods[].amount` is the applied amount, not the amount tendered (POS UI Extensions 2026-07)

On a cash sale with change due, the transactioncomplete event reports
balanceDue.amount === 0 and a paymentMethods entry equal to the order total,
even though POS itself displays the change on its own sale-complete screen. The
amount tendered and the change due appear nowhere in the payload. This
contradicts two explicit statements in the field documentation.

Environment

  • @shopify/ui-extensions 2026.7.0 , api_version = "2026-07"
  • Target: pos.app.ready.data, subscribed via
    shopify.addEventListener('transactioncomplete', …)
  • POS iOS app - 11.13.0

What the documentation says

From the app-background target reference,
SaleCompleteEvent:

paymentMethods (Payment[]) — An array of all payment methods used to
complete this transaction. Each payment entry specifies the payment type (for
example, cash, credit card), amount tendered, and currency. Multiple
entries indicate split payments where the customer paid using multiple methods
(for example, part cash, part credit card). The sum of all payment amounts
should equal or exceed the grandTotal.

balanceDue (Money) — The remaining balance still owed on this
transaction as a Money object. Typically zero for fully paid transactions. A
positive balance indicates partial payment or layaway scenarios. A negative
balance indicates overpayment, where change should be returned to the
customer.
Calculated as: grandTotal minus sum of all payment amounts.

What actually happens

Test A — exact payment (control). Order total ¥33, customer paid ¥33.

grandTotal:     { amount: 33, currency: 'JPY' }
paymentMethods: [ { amount: 33, currency: 'JPY', type: 'Cash' } ]
balanceDue:     { amount: 0,  currency: 'JPY' }

Consistent with the docs — but it cannot distinguish the two readings, because
tendered and applied are the same number. This is why the problem is easy to
miss.

Test B — over-tender. Order total ¥15, customer handed over ¥100. POS
displayed (Change due: ¥85) on its own sale-complete screen.

grandTotal:              { amount: 15, currency: 'JPY' }
paymentMethods:          [ { amount: 15, currency: 'JPY', type: 'Cash' } ]
balanceDue:              { amount: 0,  currency: 'JPY' }
tipAmount:               undefined
cashRoundingAdjustment:  undefined

Neither 100 (tendered) nor 85 (change) appears anywhere in the payload —
I logged the whole event object, not just these fields.

Both fields are present and populated. They simply carry the amount applied to
the order
rather than the amount tendered, which is the same semantics as
the Admin API’s OrderTransaction.amountSet. Under that reading:

  • paymentMethods[].amount can only ever equal grandTotal, never exceed it,
    so the documented “equal or exceed” case is unreachable.
  • balanceDue is then always exactly 0 for a completed sale, so the documented
    “negative balance indicates overpayment” case is also unreachable..

Minimal repro

export default async function background() {
  shopify.addEventListener('transactioncomplete', (event) => {
    console.log(JSON.stringify(event, null, 2));
  });
}
api_version = "2026-07"

[[extensions.targeting]]
module = "./src/Background.ts"
target = "pos.app.ready.data"

Then ring up any item and pay cash with more than the total. Reproducible on
every over-tendered cash sale.

Questions

  1. Is the applied-amount behaviour intended, making the “amount tendered” and
    “negative balanceDue” wording inaccurate? Or are the values wrong?
  2. If it is a defect, is there a tracking issue I can follow?
  3. Is there any supported way for an extension to read the cash amount
    tendered and the change due for a completed sale?
    I have already ruled out
    the Admin API: Order exposes no tendered/change field,
    OrderTransaction.amountSet is the applied amount, and Order.receiptJson
    came back empty for POS orders in my testing. The ¥85 also does not appear on
    the emailed or SMS receipt — only on the POS completion screen — which
    suggests POS computes it for display without persisting it anywhere.
  4. Are tipAmount and cashRoundingAdjustment ever populated for JPY cash
    sales, or only under specific configuration?

Hey @tunnandaaung -

Thanks for the detailed repro. I confirmed this is a mismatch in the POS transactioncomplete payload, not an issue with your extension. Cash payments currently report the amount applied after change, while balanceDue is calculated after checkout has settled. That conflicts with the 2026-07 contract.

We have a fix under review, but I don’t have a release version or timeline to share yet. In the current POS version, there isn’t a supported way to recover the tendered amount or change from this event or the Admin API.

cashRoundingAdjustment only appears when an actual cash-rounding adjustment is applied, and tipAmount only appears when a tip was added. Thanks for flagging this.

@Alan_G
Thanks for confirming, and for the cashRoundingAdjustment / tipAmount details — that closes out the open questions on my side.

No worries @tunnandaaung - let me know if we can help with anything else on our end here :slight_smile: