hamsa software
← All articles
Integrations6 min read

The second click may mean something different

Retrying a purchase, editing its basket and deliberately buying the same thing again can look remarkably similar to a request logger. The intent model has to distinguish them.

A thick geometric ribbon loops across itself and ends beside a small circle, suggesting repeated attempts whose business intention must remain identifiable.

Two identical baskets can be two real orders

A second order for the same paper can be a duplicate or exactly what the customer wanted. One person clicks again because no answer arrived; another buys another pack for a different office. The requests may contain identical values in both cases. Comparing request bodies cannot decide between those intentions because all the submitted values may match. The application needs to remember which purchase the person authorised, not just recognise familiar-looking contents.

A payload hash answers whether the selected fields are the same. It cannot decide whether this is one purchase tried again or two purchases that happen to match. That makes an idempotency key a product decision as well as an API setting. Before generating it, somebody has to choose the point at which a person confirms an intention, what that intention includes and what would count as deliberately starting another one.

AWS’s account of idempotent APIs makes this distinction explicit, including why identical parameters can represent separate intended operations. [1] For a small ordering tool, a useful starting point is a durable checkout intention with a clear confirmation boundary. Its identity survives transport retries. It changes when the person deliberately starts another authorised operation, rather than whenever a network library decides to try again.

Freeze what was actually submitted

In the original teaching example, intention I17 contains one PAPER item, quantity 1, amount 1200 minor units and currency GBP. The submitted payload is recorded with the intention. Its first successful result is order-1. A repeat of I17 with the same payload returns that result. It does not read the current basket and quietly substitute whatever the person has edited since the first request left.

That frozen meaning is important because baskets are working documents. Someone may change quantity from one to two while the original order is still processing. If the retry code serialises the current screen, the same intention now carries a different amount and quantity. Calling it a retry conceals a business revision. The fixture rejects that combination rather than guessing which version the person meant.

Include every field that materially defines the operation, in an agreed representation. Quantity, currency, price basis, destination or recurring terms may matter; a visual note might not. This is not an invitation to hash a convenient subset and assume the rest is harmless. State which fields are frozen and which may change independently. A stable identifier is only useful when the application also preserves what that identifier was intended to mean.

Keep the editable draft separate from the submitted intention and its known or uncertain outcome. Before confirmation, the basket can change freely. Afterwards, the snapshot gives a delayed result something fixed to refer to while the person carries on editing another draft. If quantity 1 was submitted and the screen now shows quantity 2, the old success confirmed quantity 1. Show that submitted amount and quantity in the confirmation. Echoing the current controls can mislead the customer even when the backend has correctly processed one order.

A conflict is information, not a reason to mint a key

In the second example, I17 arrives with quantity 2 and amount 2400 minor units. The sequential model raises a changed-payload conflict and creates nothing, because this no longer matches the submitted instruction. Generating a new key automatically would permit another purchase without establishing what happened to I17 or whether the person authorised a separate order. The conflict needs a lifecycle decision before another attempt is sent.

The changed-payload conflict is a reason to establish what happened to I17. A completed order may need amendment, cancellation and replacement, or a separate purchase under the business rules. A reliably unstarted one may allow a fresh submission. If its outcome remains unknown, another key does not improve our knowledge of it; it permits another order to exist alongside it. The interface should make that choice visible instead of allowing the retry code to make it for the customer.

Stripe’s API v1 documentation provides a concrete provider contract: it compares parameters associated with an idempotency key, returns the recorded result for a matching request and rejects mismatches. It also describes retention limits and when execution results are recorded. [2] Those details should shape retry code for that API; they do not substitute for the local decision about whether the person is amending an order or starting another one.

A provider’s key and the application’s intention record need not have the same lifespan. An old support enquiry may require the original request-to-order relationship after the provider no longer retains its idempotency entry. Keep the business record and provider references for the application’s justified recordkeeping needs. Do not build a recovery assumption around an unlimited remote memory that the provider contract does not promise.

Same contents do not always mean the same purchase

An original sequential checkout model distinguishes retrying intention I17, changing its payload and deliberately placing another identical order as I18.

Scroll sideways to see all columns.

SituationIntent / payloadExpected decisionWhy
Connection retryI17 · PAPER×1 · GBP1200 minorReturn original order-1Same authorised purchase
Basket editedI17 · PAPER×2 · GBP2400 minorConflict; create nothingKey no longer describes same request
Deliberate second purchaseI18 · PAPER×1 · GBP1200 minorCreate order-2Identical contents, new intention
First outcome unresolvedI17 unknown; changed basket proposedReconcile I17 before new effectNew key cannot erase uncertainty
The first three behaviours are reproduced by the downloadable toy program. The unresolved-outcome row is an application decision rule, not a tested provider interaction. Amount 1200 minor means £12.00 in this GBP fixture.

Let a deliberate second purchase be explicit

The third teaching case starts intention I18 with the same one-pack payload as I17. It creates order-2. This is correct because the scenario explicitly authorises a second purchase; no content comparison can establish that authorisation on its own. The interface should make the distinction understandable, for example by returning to a completed order and offering a clearly labelled purchase-again action.

A double-click while the same confirmation is pending should stay attached to the existing intention. Disabling the button can reduce accidental interaction, but it does not replace a durable association: a page reload, another tab or a delayed response can bypass the visual state. Restore the intention’s known status when the person returns, rather than presenting a pristine confirmation screen as if nothing had happened.

Deriving identity from customer, product and date can suppress a legitimate repeat order or change identity as recovery crosses midnight. Generating a random key for every HTTP attempt has the opposite problem: the provider cannot recognise retries. Create the key at the chosen business confirmation boundary and reuse it for attempts belonging to that intention. Uniqueness is required, but so is the durable association with what the person submitted.

The confirmation screen must refer to the submitted basket

I17’s fixed submission and a later editable basket can contain different quantities.

Follow the labelled arrows. On a narrow screen, scroll sideways if needed.

The confirmation screen must refer to the submitted basketSubmit I17: fixed basket snapshot. Preserve PAPER quantity 1 and GBP 1200 minor units. Show those submitted values while processing. Working basket changes to quantity 2. This is a different editable draft. Do not rewrite I17 or resubmit the changed basket under that intention. Delayed I17 success: order-1. The confirmation belongs to the quantity-1 submitted snapshot. Do not echo the current quantity-2 draft as the completed order. Deliberate second purchase: I18. A separate authorised intention can correctly create order-2 even when its contents match the earlier purchase. From Submit I17: fixed basket snapshot to Working basket changes to quantity 2: Later draft editing. From Submit I17: fixed basket snapshot to Delayed I17 success: order-1: Confirms the submitted snapshot. From Working basket changes to quantity 2 to Deliberate second purchase: I18: Separate authorised intention.Later draft editingConfirms the submittedsnapshotSeparateauthorisedintentionSubmit I17: fixedbasket snapshotPreserve PAPER quantity 1 andGBP 1200 minor units. Showthose submitted values whileprocessing.Working basket changesto quantity 2This is a different editabledraft. Do not rewrite I17 orresubmit the changed basketunder that intention.Delayed I17 success:order-1The confirmation belongs tothe quantity-1 submittedsnapshot. Do not echo thecurrent quantity-2 draft asthe completed order.Deliberate secondpurchase: I18A separate authorisedintention can correctlycreate order-2 even when itscontents match the earlierpurchase.
The sequential fixture establishes intention semantics, not concurrent storage or remote-effect guarantees.

Make the distinction inspectable after the click

The downloadable program stores a canonical payload with each intention and returns its saved result for an exact repeat. It makes the submitted snapshot and result available together, so a retry can be answered without consulting a basket that may have changed. The model runs sequentially and makes no external payment call.

Two workers can both read that I17 has no result before either stores it. A real service needs a unique intention constraint and a controlled transition so those observations cannot independently authorise two provider requests. Keep the submitted snapshot and resulting operation linked through the actual provider’s effect and reconciliation rules. The sequential dictionary example defines what a retry means; this shared transition has to preserve that meaning while more than one worker is trying to act.

Expose enough of those meanings to support the person when the operation becomes uncertain. “Order processing; check this order’s status” carries a different promise from “Your request failed; start again.” A support view should connect intention I17, its submitted values, attempts and provider reference, while showing I18 as a separate decision. Otherwise an investigation begins by trying to infer intention from a pile of almost-identical requests.

When support sees two matching baskets, the confirmation history should show whether the customer made two purchases or retried one. That answer needs the submitted values, the intention and its resulting order together. The same record must remain understandable when the screen has changed or the provider’s retry entry has expired, because those are precisely the moments when guessing from request contents would be least helpful.

Working on a workflow like this? Hamsa helps finance and operations teams connect their tools and handle the exceptions, starting with one workflow.

See the Payment Operations Pilot

References & further reading

Primary sources behind this article. Living documentation may change.

  1. AWS Builders Library: caller intent, retries and changed parameters
  2. Stripe: API v1 idempotent-request parameter comparison and retention
Hamsa Jama

Practical engineering. A human perspective.

More about Hamsa Software and the person behind it. Editorial notes.

A few more things
worth a read.

Back to the reading room
Integrations7 min read

A valid signature still needs a trust decision

An Oprindo sample had a matching asset hash and a valid signature, alongside an untrusted credential result. The useful story is in those separate answers.

Integrations6 min read

Should this old instruction still run?

Repairing a consumer makes replay possible. Current state, dependencies and recovery admission decide whether it is still appropriate.

Integrations7 min read

The backfill arrived after the live update

Historical work needs a population boundary, revision policy and crash-safe checkpoint strategy before its final page can mean done.