fix(checkout): emit Payment-Receipt header on MPP success - #50
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
payment_receipt_headersymmetric to the existingpayment_response_headeronSettleOutcome+MppxComposeOutcome.make_mppx_compose_hookserializes the upstream pympp Receipt (Receipt.to_payment_receipt()) and threads it onto the outcome. Defensivegetattrso older pympp versions without the helper leave itNone.Checkoutechoesoutcome.payment_receipt_headeras thepayment-receiptHTTP header on success, mirroringpayment-responsefor x402.$0zero-settle carve-outs explicitly passNone(x402 uses payment-response only).Surfaced when smoke-testing live
agents.martinestate.comviatempo request: every successful Tempo purchase printedWarning: 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
Noneskips the echo).Test plan
tempo requestagainst live martin (postagentscore-commerce@2.0.1bump) and confirm the warning disappears