From 8f7facbc3b2a0a9e658b198c7da1ef8d2627ea6a Mon Sep 17 00:00:00 2001 From: XiaolongZhang Date: Mon, 3 Aug 2026 11:05:32 +0800 Subject: [PATCH] fix(rest/nodejs): express discount allocation path as a JSONPath discount.json types allocation.path as a JSONPath (RFC 9535) pointing at the allocation target (e.g. "$.line_items[0]"). The Node.js sample emitted the bare label "subtotal", which a platform cannot resolve. The Python sample already emits "$.totals[?(@.type=='subtotal')]". Use that same JSONPath in all three discount code branches of recalculateTotals so the allocation target is resolvable, matching the Python reference and the schema. Adds a regression test asserting each applied discount's allocation path is rooted at "$." (a JSONPath), which fails on the old "subtotal" value. --- rest/nodejs/src/api/checkout.ts | 21 ++++++++++++++++++--- rest/nodejs/test/discount.test.ts | 30 ++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/rest/nodejs/src/api/checkout.ts b/rest/nodejs/src/api/checkout.ts index 6643192a..fff57256 100644 --- a/rest/nodejs/src/api/checkout.ts +++ b/rest/nodejs/src/api/checkout.ts @@ -412,7 +412,12 @@ export class CheckoutService { code, title: "10% Off", amount: discountAmount, - allocations: [{ path: "subtotal", amount: discountAmount }], + allocations: [ + { + path: "$.totals[?(@.type=='subtotal')]", + amount: discountAmount, + }, + ], }); checkout.totals.push({ type: "discount", amount: -discountAmount }); } else if (upperCode === "WELCOME20") { @@ -422,7 +427,12 @@ export class CheckoutService { code, title: "Welcome 20% Off", amount: discountAmount, - allocations: [{ path: "subtotal", amount: discountAmount }], + allocations: [ + { + path: "$.totals[?(@.type=='subtotal')]", + amount: discountAmount, + }, + ], }); checkout.totals.push({ type: "discount", amount: -discountAmount }); } else if (upperCode === "FIXED500") { @@ -432,7 +442,12 @@ export class CheckoutService { code, title: "$5.00 Off", amount: discountAmount, - allocations: [{ path: "subtotal", amount: discountAmount }], + allocations: [ + { + path: "$.totals[?(@.type=='subtotal')]", + amount: discountAmount, + }, + ], }); checkout.totals.push({ type: "discount", amount: -discountAmount }); } diff --git a/rest/nodejs/test/discount.test.ts b/rest/nodejs/test/discount.test.ts index b9bc8ca0..bc975c47 100644 --- a/rest/nodejs/test/discount.test.ts +++ b/rest/nodejs/test/discount.test.ts @@ -132,3 +132,33 @@ test("an applied discount's allocations sum to its amount", () => { } assert.ok(checked > 0, "expected at least one discount carrying allocations"); }); + +// An allocation target must be expressed as a JSONPath (discount.json: +// allocation.path), so a platform can resolve where the discount applied — +// not a bare label like "subtotal". +test("an applied discount's allocation path is a JSONPath", () => { + const checkout = checkoutWithCodes(["10OFF"]); + new CheckoutService()["recalculateTotals"](checkout); + + const applied = ( + checkout as unknown as { + discounts: { + applied: Array<{ + allocations?: Array<{ path: string; amount: number }>; + }>; + }; + } + ).discounts.applied; + + let checked = 0; + for (const a of applied) { + for (const alloc of a.allocations ?? []) { + checked += 1; + assert.ok( + alloc.path.startsWith("$."), + `allocation path "${alloc.path}" must be a JSONPath rooted at "$."` + ); + } + } + assert.ok(checked > 0, "expected at least one allocation to check"); +});