← EXCOIN / INSIGHTS

Payment integration failure modes to test before launch

An unknown outcome is particularly dangerous in payments. Retries, webhooks and manual repair need stable identifiers and independent status checks.

Distinguish acceptance from completion

A payment flow can confirm several different facts: that an application accepted an intention, a provider received an instruction, the payment was authorized, processed or finally settled. These moments must not collapse into one success status. Before implementation define the state model for the actual provider and product. For each state decide what a customer may see and what the operating system may do next. An HTTP response alone is not proof that money moved. If the customer screen announces completion too early, a later rejection becomes a support and trust problem. Include delayed and unknown states explicitly rather than disguising them as a failure.

Assign a stable internal identifier to the payment and retain the provider's identifier. They are necessary for matching messages, manual investigation and reconciliation. Record amount, currency, fees where relevant and rounding rules. Do not let another attempt for the same business intention silently create a second transaction. Deciding whether an action is a new purchase or a retry belongs to the business process, not only the HTTP client. The application should say what it knows and does not yet know about an operation without turning uncertainty automatically into failure. Make identifiers accessible to authorized support staff without exposing sensitive credentials.

A timeout creates an unknown outcome

Imagine the application sends an instruction and the connection drops just before the reply. The provider may have created the payment while the caller does not know it. Automatically repeating with a new identifier can create a second operation. Distinguish a safe retry with the same idempotency key, a status query and a genuinely new instruction. Stripe documents idempotent requests as a way to retry certain operations safely; confirm the key's scope and lifetime for the actual API. You cannot assume every payment provider gives the same guarantee. After a failure establish what the other side actually performed before acting again.

Show uncertainty to people in understandable terms. A customer need not see the technical timeout but should know confirmation is pending and avoid making another unnecessary payment. An operator needs the internal ID, last confirmed step and safe verification procedure. Temporarily restrict further actions on the same case when they would make its state more confusing. Set a time after which a person investigates or an automatic status check runs. Do not let a pending case disappear because the original HTTP request did not return. Explain when a customer will receive the next update.

Idempotency must survive concurrency and restart

Bind a retry key to the business operation rather than a request timestamp. The recipient should persist evidence that the instruction was processed. Microsoft's idempotent consumer pattern discusses repeated delivery and concurrent copies of a message. A lookup before insertion alone may not be enough if two instances pass the check together; a safe write with a uniqueness constraint is decisive. Define the key's scope and retention. If it is reused later, an old repeated request may appear new. The design must match the actual provider's behavior and the lifetime of the underlying commercial intention.

Idempotency is also needed inside your application. A webhook may update an order, send an email and create an accounting event. If every part uses a different identifier and none records completion, one repeated message may trigger multiple side effects. For each consumer decide what must happen once and what may safely happen again. In testing deliver the same message concurrently twice and again after restart. The result should be one correct business effect and traceable evidence that duplicate delivery was recognized. Check the data store, not only the API response. Include the case where processing fails between two side effects.

Webhooks follow network reality

A webhook is not a synchronous continuation of the original request. It may arrive late, more than once or out of order. Stripe's webhook documentation explains verification and delivery handling; details differ for other providers. Verify a signature or other origin mechanism and restrict what the callback may change. Receive it safely and record the event identifier before processing. If your endpoint replies with an error after a side effect, the provider may deliver the event again. Your design must handle this without another payment or customer confirmation. Do not trust a visible sender address as proof of origin.

A state machine should stop a late message from overwriting a newer final state. Retain original events and decide which transition is allowed. For some providers it is safer to fetch the current object state after notification than assume every event is a complete final picture. Verify that in the particular provider's documentation. Move an unprocessable webhook to a controlled repair path with a reason and a way to retry. Do not lose it in a log that will be deleted in a few days. Support needs to see whether it is waiting, failed or complete. Recheck processing after a deployment that changes the event schema.

Reconciliation repairs what the ordinary flow misses

Even a well-written webhook cannot guarantee that every internal state matches a provider's record. Regularly compare internal payments with provider state, identifiers, amounts, currency and relevant time window. Comparing total revenue alone is insufficient: two wrong transactions can cancel each other in an aggregate. Define tolerance for pending states and the point where a mismatch becomes an exception. Each exception needs an owner, priority, last action and safe repair route. Treat reconciliation results as a product measure rather than a private technical file. Include transactions without a matching record on either side.

A repair may mean fetching state again, attaching a missing event, refunding money or making an opposite accounting entry. Never erase history so the original mismatch cannot be explained. Before a consequential action confirm that the provider has not already performed it. Require second-person approval for sensitive corrections according to actual risk. A reconciliation report should show open differences by age and amount without exposing all sensitive details to every administrator. Measure the time to close an exception and repeated causes. Use these findings to improve the integration rather than repeatedly fixing cases by hand.

Test unpleasant combinations, not just a successful charge

Before launch try success, rejection, timeout before and after submission, double-click, repeated request, two concurrent webhooks, lost acknowledgement and database failure while recording state. For each case specify what the customer sees, what the internal record says, what the provider says and how the case is resolved. Test recovery after a restart. If the provider sandbox cannot create a condition, simulate it in the integration layer and record the limitation. A successful test payment does not show that the application handles unknown outcomes or duplicate notices. Keep test records separate from live customer data.

Probe boundaries: the same key with changed parameters, key expiry, late refund and a webhook about an unknown object. Error messages should lead to a safe decision. If a customer sees a pay again button after timeout with no explanation, the test found a product problem. Go beyond code and ask an operator to resolve the case from the runbook. They should find identifiers, compare states and choose repair without guessing. Add discovered gaps to regression tests and documentation. Repeat these scenarios when changing providers, SDK versions or event subscriptions.

Set responsibilities and limits before production

The payment provider, your application and the accounting system may have different service levels and different moments of finality. Record contacts, support hours, escalation and rules for customer communication. Distinguish technical failure from a business-rule rejection. Decide in advance when to stop an operation, when merely to delay confirmation and who may issue a refund. Regulatory requirements vary by product and jurisdiction; verify them with a specialist. Do not promise zero outages or absolute absence of duplicates. Promise controls that are measurable and usable in operation. Give each unresolved state a person or scheduled check.

EXCOIN can help design and implement a particular payment flow, but correct behavior depends on the counterpart's documentation and contract. An initial stage should provide a state model, data contract, idempotency rules, failure cases and reconciliation. After a pilot measure unknown states, duplicate deliveries, manual repair and time needed to explain a disputed case. A system that can safely say the outcome is not yet known and then establish it may be more valuable than a flawless-looking demonstration of one successful payment. Use the pilot evidence to decide whether the operational risk is acceptable.

Sources

Discuss a project