Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,35 @@ func main() {
| [charge-hash](./examples/charge-hash/) | Push-mode charge flow with a hash credential, available in both one-command and separate-process layouts |
| [charge-fee-payer](./examples/charge-fee-payer/) | Sponsored Tempo charge flow where the server co-signs as a fee payer, available in both one-command and separate-process layouts |

## Idempotent POST Charges

For paid POST routes or agent tool calls, bind each logical operation to a
stable `ExternalID`. The value is included in the MPP charge request and echoed
back in the `Payment-Receipt`, so servers can distinguish a safe retry from a
different paid operation.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clarify that ExternalID does not enforce idempotency

For paid POST retries, ExternalID is only serialized into the challenge/receipt; server.Mpp.Charge does not persist or reject previously seen IDs, so a retry after the paid operation succeeds but the response is lost can still run the POST side effect again unless the application keeps its own idempotency record keyed by Idempotency-Key. This wording makes the new section sound like passing the header through is enough to make retries safe, so it should explicitly mention the required app-side idempotency/cache check before performing the operation.

Useful? React with 👍 / 👎.


If your API already accepts an `Idempotency-Key` header, pass that value through
as `ChargeParams.ExternalID` when constructing the charge:

```go
externalID := r.Header.Get("Idempotency-Key")
if externalID == "" {
http.Error(w, "missing Idempotency-Key", http.StatusBadRequest)
return
}

result, err := payment.Charge(r.Context(), server.ChargeParams{
Authorization: r.Header.Get("Authorization"),
Amount: "0.50",
Description: "Create report",
ExternalID: externalID,
})
```

Use the same `ExternalID` for retries of the same operation. Do not generate a
new value for each retry, and avoid reusing one value across different paid
operations.

## Web Frameworks

The most common Go HTTP stacks today are `net/http`, Gin, Echo, Chi, and Fiber.
Expand Down