Skip to content

fix(rest): emit discount totals[] entry as a negative amount#132

Merged
damaz91 merged 3 commits into
Universal-Commerce-Protocol:mainfrom
vishkaty:fix/discount-total-sign
Jul 16, 2026
Merged

fix(rest): emit discount totals[] entry as a negative amount#132
damaz91 merged 3 commits into
Universal-Commerce-Protocol:mainfrom
vishkaty:fix/discount-total-sign

Conversation

@vishkaty

Copy link
Copy Markdown
Contributor

What

The Python Flower Shop server appends the applied discount to totals[] with a positive amount. Booting the server and creating a checkout for bouquet_roses (3500) with discounts.codes: ["10OFF"] returns:

"totals": [
  {"type": "subtotal", "amount": 3500},
  {"type": "discount", "amount": 350},
  {"type": "total", "amount": 3150}
]

Two problems with the +350:

  1. Schema violationtotal.json constrains discount/items_discount amounts with exclusiveMaximum: 0 (they must be strictly negative).
  2. The receipt doesn't reconcile3500 + 350 ≠ 3150. Per discount.md ("Allocation Details"): "the applied[].amount describes the magnitude of the applied discount (always positive); the corresponding totals[] entry amount represents its signed effect on the receipt (negative for discounts)."

Fix

Negate only the totals[] entry (amount=-discount_amount). applied[].amount and the allocation stay positive, per the spec's magnitude-vs-signed-effect split. After the fix the same request returns {"type": "discount", "amount": -350} and subtotal + discount == total.

Tests

  • New: test_discount_total_is_negative_and_receipt_reconciles — asserts the discount totals entry is strictly negative, applied[].amount stays positive, and the receipt reconciles.
  • Corrected: test_discount_code_matches_case_insensitively asserted the old positive value ([100][-100]).
  • Full rest/python/server suite passes.

@damaz91 damaz91 added the status:needs-triage Signal that the PR is ready for human triage label Jul 15, 2026
@damaz91 damaz91 added status:under-review and removed status:needs-triage Signal that the PR is ready for human triage labels Jul 16, 2026

@damaz91 damaz91 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! The fix is correct and the new integration test verifies the behavior.

I have a couple of suggestions:

  1. Regenerate Sample Output: Since the sign of the discount total has changed, the sample interaction log rest/python/client/flower_shop/sample_output/happy_path_dialog.md is now out of sync. Could you please regenerate it? You can do this by running the script rest/python/client/flower_shop/extract_json_dialog.sh (make sure your server is running or the script will start one) and then copying the generated file to the sample_output directory.

    Specifically, the totals section in the sample output should show a negative discount:

        {
          "type": "discount",
          "amount": -650
        }
  2. Node.js Server Bug: I noticed that the Node.js implementation has the exact same bug. In rest/nodejs/src/api/checkout.ts around line 363:

              checkout.totals.push({ type: "discount", amount: discountAmount });

    It should be -discountAmount.

    If you'd like, you can fix it in this PR as well, or we can address it in a separate issue/PR.

Otherwise, this looks good to go.

@vishkaty

Copy link
Copy Markdown
Contributor Author

Thanks for the review! Both addressed:

  1. Sample output — updated the discount totals[] entries to -650 (3d3fb10). I kept it scoped to the discount lines: rather than a full extract_json_dialog.sh regen, which I found also sweeps in ~800 lines of unrelated modernization because the committed happy_path_dialog.md is still on the 2026-01-23 profile shape and predates the 2026-04-08 upgrade (fix(rest/python): update UCP version to 2026-04-08 #129) — single-transport services object, old spec URLs. Happy to bring the whole dialog current in a separate PR so this one stays focused on the discount fix; let me know if you'd prefer it here instead.

  2. Node.js server — fixed the same bug (de0a7cb): totals[] discount is now -discountAmount, with applied[].amount left positive as the magnitude. Added a test driving recalculateTotals against an in-memory catalog (discount total negative, receipt reconciles, applied amount stays positive) — tsx --test is green (5/5), tsc clean.

vishkaty added 3 commits July 16, 2026 11:51
The flower-shop checkout appended the discount to totals[] with a positive
amount, so the receipt did not reconcile (subtotal + discount != total) and
the entry violated total.json, which constrains discount/items_discount
amounts with exclusiveMaximum: 0. Per discount.md the applied[].amount is the
magnitude (always positive) while the totals[] entry is its signed effect on
the receipt (negative for a discount).

Negate only the totals[] entry; applied[].amount and the allocation stay
positive. Adds a test asserting the discount total is negative, applied
amounts stay positive, and subtotal + discount == total; corrects the
existing case-insensitive test, which asserted the old positive value.
Regenerates the discount totals[] entries in the happy-path dialog to match
the corrected sign (now -650). The applied[].amount entries stay positive
(the magnitude). Scoped to the discount lines only; the file is otherwise
still on the 2026-01-23 profile shape (predates Universal-Commerce-Protocol#129) and is out of scope
for this PR.
The Node.js server had the same sign bug as the Python server: the discount
was pushed to totals[] with a positive amount, so the receipt did not
reconcile and the entry violated total.json (exclusiveMaximum: 0). Negate
only the totals[] entry; applied[].amount and the allocation stay positive
(the magnitude, per discount.md).

Adds a test driving recalculateTotals against an in-memory catalog: the
discount total is negative, the receipt reconciles (subtotal + discount ==
total), and applied[].amount stays positive.
@vishkaty
vishkaty force-pushed the fix/discount-total-sign branch from de0a7cb to 922f776 Compare July 16, 2026 15:52
@damaz91
damaz91 merged commit b372060 into Universal-Commerce-Protocol:main Jul 16, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants