Skip to content

fix(rest/nodejs): quote one fulfillment group per method - #181

Open
yingzhehu-TT wants to merge 1 commit into
Universal-Commerce-Protocol:mainfrom
yingzhehu-TT:fix/nodejs-empty-fulfillment-groups
Open

fix(rest/nodejs): quote one fulfillment group per method#181
yingzhehu-TT wants to merge 1 commit into
Universal-Commerce-Protocol:mainfrom
yingzhehu-TT:fix/nodejs-empty-fulfillment-groups

Conversation

@yingzhehu-TT

Copy link
Copy Markdown

Summary

Quote one fulfillment group per method, per fulfillment.md, so a method the
merchant cannot quote options for reports its line items with an empty option
list rather than no group at all.

Motivation

fulfillment.md "Business Response Behavior" is a MUST:

When supports_multi_group: false (default):

  • Business MUST consolidate all items into a single group per method
  • Response still uses array structure: methods[].groups[] with groups.length === 1

recalculateTotals only ever created that group inside
method.type === "shipping" && method.selected_destination_id && method.destinations,
and then only when the destination resolved to a country. Every other method —
any non-shipping type, or a shipping method whose destination the merchant does
not serve — was returned with groups: [].

That is also a correctness bug, not just a schema deviation. The completion gate
reads method.groups?.every((group) => group.selected_option_id), and
[].every(...) is true, so such a checkout completed with no option ever
selected, no fulfillment charged, and an order whose fulfillment.expectations
was empty — the merchant had nothing telling them what to ship or where.

This is the same vacuous-truth hazard #155 fixed one level up, where the comment
already reads "an empty methods array must not satisfy the gate via
[].every(...) === true"
. The nested array had no equivalent guard.

The Python reference server was never affected: checkout_service.py gates on
if method.groups:, and an empty list is falsy in Python. This change brings the
Node sample in line on that case.

Scope

One behaviour change, in recalculateTotals. The completion gate is deliberately
not touched: once every method carries a group, the gate's existing
selected_option_id check is what refuses completion, and hardening it further
would add an unreachable branch. The if (!method.groups || …) block inside the
quoting path becomes dead and is folded into the assignment it guarded.

line_item_ids is seeded with ?? [] because fulfillment_group.json lists it
as required — a repaired group should not itself be schema-invalid.

Known consequence, stated deliberately: this mock merchant quotes options for
shipping only, so a pickup method now returns a group with options: [] and
stays uncompletable. Refusing is the correct outcome — silently completing was
the bug — but it means the sample advertises a pickup method it cannot serve.
Quoting real pickup options is a new merchant capability rather than part of this
fix; happy to add it here if maintainers prefer.

Validation

npm test 134 passed / 0 failed (133 before). tsc --noEmit exit 0.
prettier --check clean.

The new test fails against main with expected: 1, actual: 0 — no group is
produced — and passes with the change.

Totals were diffed across create / option-selection / completion, pickup,
multi-method, unknown buyer, no destination, digital-only, empty methods, and a
PUT without fulfillment: no totals value changes. The only response
difference is groups: [] becoming a single group carrying the method's line
items.

@damaz91 damaz91 added status:needs-triage Signal that the PR is ready for human triage status:under-review and removed status:needs-triage Signal that the PR is ready for human triage labels Aug 13, 2026
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.

2 participants