Skip to content

Instantly share code, notes, and snippets.

@kilbot
Created July 10, 2026 16:35
Show Gist options
  • Select an option

  • Save kilbot/e059beecbed065503c72a6498ef9487e to your computer and use it in GitHub Desktop.

Select an option

Save kilbot/e059beecbed065503c72a6498ef9487e to your computer and use it in GitHub Desktop.
WCPOS receipt price and savings semantics research

WCPOS receipt price and savings semantics

Research asset for Establish price and savings semantics, part of Map: Restore POS price-reduction visibility in receipts.

Executive conclusion

Observed: For WCPOS 1.8.11 through 1.9.x, _woocommerce_pos_data.price and _woocommerce_pos_data.regular_price are the only stable order-line snapshots that preserve the pre-coupon selling price and the recorded regular/reference price. The current catalog product must not be used as a historical fallback.

Observed: Since 1.9.0, the stored WooCommerce line subtotal equals the WCPOS selling price. Consequently, subtotal - total and WooCommerce discount_total represent coupon discount only. This is intentional WooCommerce sale-price parity, not missing order data. WCPOS v1.9 documentation, server PR with design intent, current receipt builder.

Observed: The metadata cannot distinguish a catalog sale from a cashier price override. Both produce the same observable pair, such as regular_price=20, price=16. If a cashier reduces an already-on-sale item from 16 to 14, the final pair is regular_price=20, price=14; the original 16 sale price is not retained. Therefore the contract may truthfully expose recorded regular/reference price, pre-coupon selling price, and pre-coupon savings, but not a universal cashier-only discount.

Decision input: The next ticket must choose whether combined catalog-sale and cashier savings is the desired product meaning. A cashier-only figure would require new metadata from the client and would not be recoverable for existing orders.

Sources of truth

Concept Authoritative source Confidence and limitations
Recorded regular/reference unit price Numeric _woocommerce_pos_data.regular_price Observed. Snapshotted from the product/variation when added, but the edit form also lets the cashier change it. It is not an immutable catalog price.
Pre-coupon selling unit price Numeric _woocommerce_pos_data.price Observed. Starts as WooCommerce's current product price and is overwritten by a till price edit. WooCommerce documents price as the current effective product price and regular_price separately: REST product properties.
Pre-coupon price reduction max(regular_price - price, 0) per unit, multiplied by positive quantity Inferred from observed producer and coupon-sale logic. The same comparison is used to treat WCPOS-reduced items as on sale. It includes catalog sale savings and till overrides together.
Coupon discount after 1.9.0 Stored Woo line subtotal - total, with the corresponding subtotal/total tax difference; order totals use WooCommerce discount totals Observed. The 1.9 client sends subtotal = price × qty; server tests assert coupons use that base.
Coupon discount before 1.9.0 Metadata selling-price line amount minus final Woo line total Inferred and reconstructible when metadata and tax conversion are valid. The old Woo subtotal - total conflates regular-to-selling savings and coupons.
Final charged line amount Woo line total and total_tax Observed. Already exposed by Receipt Data.
Catalog-sale component vs cashier-override component No authoritative stored source Not recoverable. sale_price is not snapshotted, and price is overwritten on edit.
Historical fallback without valid metadata None Not recoverable. Current product prices may have changed and must not rewrite history.

Client evidence: the product/variation converter snapshots price and regular_price into _woocommerce_pos_data; line edits write both values back; splitting clones all metadata except the UUID. See metadata producer, line editing and splitting, and edit form.

Version boundary

Before 1.9.0

Observed: The client calculated subtotal = regular_price × quantity and total = price × quantity before coupons. Thus existing Receipt Data discounts represented regular-to-selling savings plus any coupon allocation.

Example: regular 20, selling 16, then 10% coupon:

  • Woo subtotal: 20
  • Woo final total: 14.40
  • Existing discounts: 5.60, conflating 4.00 pre-coupon savings and 1.60 coupon savings
  • Metadata reconstruction: pre-coupon savings 4.00; coupon savings 1.60

From 1.9.0

Observed: The client change made both the pre-coupon subtotal and total use price × quantity; coupon application then reduces total only. The companion server change removed the fragile subtotal filters. Client parity commit, server PR.

The same example now stores:

  • Woo subtotal: 16
  • Woo final total: 14.40
  • Existing discounts: 1.60, coupon-only
  • Metadata reconstruction: pre-coupon savings 4.00; coupon savings 1.60

Conclusion: New fields derived from metadata can be stable across the version boundary; existing subtotal-derived fields cannot be redefined without changing their meaning for old orders.

Tax semantics

Observed: Metadata prices remain in the store's configured entered-price basis. Woo line subtotal/total values are stored excluding tax, with tax in separate fields. The order records whether prices were entered including tax.

For a taxable line with a non-zero stored subtotal, derive an effective historical conversion ratio from the stored pre-coupon subtotal, rather than looking up today's tax rates:

  • subtotal_incl = subtotal_excl + subtotal_tax
  • If prices were entered including tax:
    • metadata raw value is the inclusive value
    • value_excl = value_raw × subtotal_excl / subtotal_incl
  • If prices were entered excluding tax:
    • metadata raw value is the exclusive value
    • value_incl = value_raw × subtotal_incl / subtotal_excl
  • For tax status none, inclusive and exclusive values are equal.

Inferred: This ratio works on both sides of 1.9 because the tax rate is proportional even though the stored subtotal basis changed from regular price to selling price. It also avoids using mutable current tax configuration and accommodates compound rates through the amounts actually stored on the order.

Limitation: If the stored subtotal is zero while regular_price is positive (for example, a free 100% reduction created on 1.9), the opposite tax basis is not provable from the line's amounts (0/0). The raw entered-price-basis value is still known. Looking up current tax rates would be historically unsafe. The public contract must choose an explicit unavailable/fallback policy for this edge case.

Rounding: Calculate at WooCommerce internal precision, then round only the published/display values. Scaling already-rounded line tax ratios can produce small cent-level differences; this requires tests in inclusive, exclusive, compound, and fractional-quantity scenarios.

Scenario matrix

Amounts below omit tax for clarity.

Scenario Metadata (regular, price) Woo values Reliably reportable
Full-price item 20, 20 subtotal 20; total 20 Reference 20; selling 20; pre-coupon savings 0
Till reduction 20, 16 1.9 subtotal 16; total 16 Reference 20; selling 16; pre-coupon savings 4
Catalog sale, no till edit 20, 16 Same as till reduction Same values; cause cannot be distinguished
Catalog sale 16, then till edit to 14 20, 14 subtotal 14; total 14 Reference 20; selling 14; combined savings 6; catalog/till split unknown
Till reduction plus 10% coupon 20, 16 subtotal 16; total 14.40 Pre-coupon savings 4; coupon savings 1.60; final 14.40
Quantity 3 20, 16 subtotal 48; total 48 Unit savings 4; line savings 12
Split quantity 20, 16 on every split row Separate rows Same unit semantics per split row; metadata is cloned
Price increase 20, 25 subtotal 25; total 25 Reference 20; selling 25; savings 0; signed adjustment would be -5 if exposed
Free item 20, 0 subtotal 0; total 0 Raw-basis reference 20 and savings 20; opposite tax-basis values may be unavailable
Misc product Values present in metadata Normal line totals Same semantics as a catalog product
Missing/malformed/non-numeric metadata Unusable Line totals only Final/subtotal values only; no historical reference or pre-coupon savings should be invented
Refunded order Original item retains metadata Refund lines link to original item Original sale values remain reportable. Refund-specific reference/savings requires joining the refund row to its original line; it should not be inferred from refund totals alone.
Non-positive quantity Metadata may exist Receipt builder normalizes quantity to zero Do not calculate unit or line savings; negative-quantity behavior is outside this effort

Required validation cases for implementation

  1. Pre-1.9-shaped order: no coupon and coupon-stacked.
  2. 1.9-shaped order: no coupon and coupon-stacked.
  3. Tax-exclusive and tax-inclusive stores.
  4. Multiple/compound tax rates and rounding-at-subtotal modes.
  5. Quantity greater than one, fractional quantity, and split rows.
  6. Catalog sale indistinguishable from till override.
  7. Price increase and zero price.
  8. Misc product.
  9. Missing, malformed, partial, and non-numeric _woocommerce_pos_data.
  10. Refund receipt with original-line linkage.

Behavior changes / regressions

  • Observed: No production behavior was changed by this research.
  • Risk for the next decision: Naming the value cashier_discount or pos_discount would overstate the evidence because catalog sale savings are included.
  • Risk for implementation: Falling back to the current product's regular price would make historical receipts change when catalog prices change.
  • Risk for implementation: Reusing existing lines[].discounts as pre-coupon savings would break its coupon-only meaning for 1.9 orders and preserve conflation for older orders.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment