diff --git a/rest/nodejs/src/api/checkout.ts b/rest/nodejs/src/api/checkout.ts index 2eca398..6643192 100644 --- a/rest/nodejs/src/api/checkout.ts +++ b/rest/nodejs/src/api/checkout.ts @@ -744,12 +744,17 @@ export class CheckoutService { return c.json({ detail: "Checkout session not found" }, 404); } - // Validate Fulfillment is complete - const hasFulfillment = checkout.fulfillment?.methods?.every( - (method) => - method.selected_destination_id && - method.groups?.every((group) => group.selected_option_id) - ); + // Validate Fulfillment is complete. Require at least one method: an empty + // methods array must not satisfy the gate via [].every(...) === true. + const methods = checkout.fulfillment?.methods; + const hasFulfillment = + Array.isArray(methods) && + methods.length > 0 && + methods.every( + (method) => + method.selected_destination_id && + method.groups?.every((group) => group.selected_option_id) + ); if (!hasFulfillment) { return c.json( diff --git a/rest/nodejs/test/fulfillment.test.ts b/rest/nodejs/test/fulfillment.test.ts index 7b05c7a..2065ebe 100644 --- a/rest/nodejs/test/fulfillment.test.ts +++ b/rest/nodejs/test/fulfillment.test.ts @@ -265,3 +265,37 @@ test("a checkout with fulfillment fully selected can be completed", async () => assert.equal(body.status, "completed"); assert.ok(body.order?.id, "completion must assign an order id"); }); + +test("empty fulfillment methods array blocks completion", async () => { + const app = buildApp(); + // Distinct from "no fulfillment at all": the request carries an explicit + // fulfillment object whose methods array is empty. The completion gate must + // not treat [].every(...) === true as "all methods satisfied". + const created = await app.request("/checkout-sessions", { + method: "POST", + headers: JSON_HEADERS, + body: JSON.stringify({ + currency: "USD", + line_items: LINE_ITEMS, + payment: {}, + buyer: KNOWN_BUYER, + fulfillment: { methods: [] }, + }), + }); + assert.equal(created.status, 201); + const checkout = (await created.json()) as Checkout; + assert.ok( + Array.isArray(checkout.fulfillment?.methods) && + checkout.fulfillment!.methods!.length === 0, + "the empty methods array is what makes the gate a vacuous truth" + ); + + const res = await app.request(`/checkout-sessions/${checkout.id}/complete`, { + method: "POST", + headers: JSON_HEADERS, + body: JSON.stringify(SUCCESS_PAYMENT), + }); + assert.equal(res.status, 400); + const body = (await res.json()) as { detail: string }; + assert.match(body.detail, /fulfillment/i); +});