Skip to content

fix(checkout): emit Payment-Receipt header on MPP success - #50

Merged
vvillait88 merged 2 commits into
mainfrom
fix-payment-receipt-header-emission
May 15, 2026
Merged

fix(checkout): emit Payment-Receipt header on MPP success#50
vvillait88 merged 2 commits into
mainfrom
fix-payment-receipt-header-emission

Conversation

@vvillait88

Copy link
Copy Markdown
Contributor

Summary

  • Add payment_receipt_header symmetric to the existing payment_response_header on SettleOutcome + MppxComposeOutcome.
  • make_mppx_compose_hook serializes the upstream pympp Receipt (Receipt.to_payment_receipt()) and threads it onto the outcome. Defensive getattr so older pympp versions without the helper leave it None.
  • Checkout echoes outcome.payment_receipt_header as the payment-receipt HTTP header on success, mirroring payment-response for x402.
  • x402 + the two $0 zero-settle carve-outs explicitly pass None (x402 uses payment-response only).

Surfaced when smoke-testing live agents.martinestate.com via tempo request: every successful Tempo purchase printed Warning: missing Payment-Receipt on successful paid charge response. paymentauth.org §5 mandates the header so spec-strict MPP clients can lift tx_hash + source from headers without parsing the JSON body. We had the symmetric path for x402; just never wired the MPP one.

Additive change. Existing consumer code keeps working (default None skips the echo).

Test plan

  • python: 1199 tests pass (4 new — checkout + make_mppx_compose_hook receipt round-trip + omitted-helper fallback)
  • node (parallel PR): 1150 tests pass (3 new)
  • ruff + ty clean
  • After merge + publish, re-run tempo request against live martin (post agentscore-commerce@2.0.1 bump) and confirm the warning disappears

vvillait88 and others added 2 commits May 15, 2026 13:38
…h.org §5)

Spec-strict MPP clients (tempo CLI, mppx Receipt.from) expect a
`Payment-Receipt` HTTP header on successful charge responses so the
agent can lift tx_hash + source from headers without parsing the JSON
body. We had the symmetric path for x402's PAYMENT-RESPONSE header
(handleMppx → SettleOutcome.payment_response_header → response headers)
but never wired the MPP equivalent — the receipt was captured into
MppxComposeOutcome.raw and harvested for tx_hash / signer fields, but
the serialized header value was dropped on the floor.

* Add `payment_receipt_header: str | None` to `SettleOutcome` +
  `MppxComposeOutcome` (additive; default None preserves existing
  consumer code).
* `make_mppx_compose_hook` calls `receipt.to_payment_receipt()` and
  threads the result onto the outcome (no-op for pympp versions that
  don't expose the helper — defensive `getattr`).
* `Checkout._build_success` echoes `outcome.payment_receipt_header` as
  the `payment-receipt` response header, mirroring the existing
  `payment-response` echo for x402.
* `_handle_x402` + the two `$0` zero-settle carve-outs explicitly set
  `payment_receipt_header=None` (x402 uses payment-response only).
* Tests: receipt-header round-trips through compose_mppx → response,
  null/omitted outcomes don't emit an empty header, and the
  make_mppx_compose_hook factory serializes pympp Receipts (with a
  parallel test confirming older receipts without the helper produce
  None).

Surfaced when smoke-testing live martin via `tempo request`: every
successful purchase printed `Warning: missing Payment-Receipt on
successful paid charge response`. Confirmed mppx (node) + pympp
(python) both implement the spec; the gap was our SDK never echoing it.

Bumps to 2.0.1 — additive change, no consumer migration required.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Custom ``compose_mppx`` hooks (the common pattern for merchants needing
per-rail config, per-order recipients, or Stripe-multichain shims)
return their own ``raw`` instead of letting ``make_mppx_compose_hook``
build the outcome. The first cut of this fix only wired the auto-built
hook, so every custom-hook merchant still saw clients warn ``missing
Payment-Receipt`` despite the SDK 2.0.1 bump.

Close the loophole: when ``compose_mppx`` omits ``payment_receipt_header``
but ``raw`` contains a pympp ``Receipt`` (directly, as the second element
of a ``(credential, receipt)`` tuple, in a ``raw['receipt']`` key, or on
a ``raw.receipt`` attribute), the SDK extracts via ``to_payment_receipt()``
and threads it onto the response. Explicit ``payment_receipt_header``
still wins; failing extracts (older pympp without the helper, malformed
receipts) silently fall through to no header rather than emitting a
malformed value.

This means consumers don't need to change any code — the next ``2.0.2``
bump fixes the warning everywhere ``compose_mppx`` returns the pympp
Receipt in its ``raw``, regardless of whether they use the auto-built
hook or hand-rolled their own.

Tests: 4 new auto-extract paths (dict shape, tuple shape, explicit
override wins, throws fall through) on top of the 4 from 2.0.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vvillait88
vvillait88 merged commit fc83b04 into main May 15, 2026
7 checks passed
@vvillait88
vvillait88 deleted the fix-payment-receipt-header-emission branch May 15, 2026 21:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant