All field notes
Mobile Apps84 signal · Hassania Agadir match tickets

Event Ticket App Payments: Recover Failed Purchases Without Duplicates

A payment timeout does not always mean a ticket purchase failed. Design idempotent checkout, safe retries and clear order states to protect buyers and inventory.

OSIS Engineering
October 3, 2026 · 6 min read

A buyer taps Pay for a match ticket, sees a spinner, then gets an error. Did the payment fail, or did the app lose the confirmation? If the buyer tries again while the first request is still processing, the system may create duplicate orders, charge twice or reserve inventory ambiguously. Coverage of Hassania Agadir offering tickets for a match provides a timely context for this durable product question; it does not establish anything about the club’s payment systems. For a hypothetical ticketing app, the engineering goal is to recover from uncertainty without repeating a financial action. That requires explicit order states, idempotent requests and messages that tell buyers what to do next.

01 / 03

Separate payment status from the buyer’s screen

A mobile client cannot reliably infer payment outcome from a timeout, network error or app closure. The request may never have reached the provider, or the provider may have completed the charge while its response was lost. Treating every client-side error as a failed payment encourages unsafe retries. Instead, model the order and payment as related but distinct records: the order tracks ticket allocation and fulfilment, while the payment record tracks authorization, capture, failure and reconciliation. The interface should show a pending state whenever the server cannot yet confirm an outcome. This makes uncertainty an explicit product state rather than a misleading error message.

Define a small state machine before implementation. An order might move from draft to awaiting payment, confirmed, expired or cancelled; a payment attempt might be initiated, processing, succeeded, failed or unknown. Specify which transitions are permitted and which system owns each decision. For example, a client may request a retry, but only the backend should decide whether the existing attempt is terminal and whether inventory can be released. A pending payment should not silently become a new order. Write down how late provider notifications, delayed responses and app relaunches affect the state, then test those paths as carefully as the successful checkout.

  • Checklist: identify the authoritative order state, payment state, inventory hold expiry and source of final confirmation.
  • Show buyers a pending message with a safe next step; do not label an unknown outcome as a definite failure.
02 / 03

Make retries safe with idempotency and reconciliation

Give each intended purchase a stable order identifier and each payment operation an idempotency key. The app should reuse the same key when it retries the same operation, rather than generating a fresh payment request after a timeout. On the server, persist the key and the outcome so repeated requests return the original result instead of initiating another charge. Scope keys carefully: a buyer deliberately starting a new purchase is different from retrying one whose outcome is uncertain. The payment provider’s idempotency behaviour and retention rules must be verified during integration; do not assume that a client-generated key alone prevents duplicate processing.

Plan for outcomes that remain unknown. The backend can query the provider where supported, consume signed asynchronous notifications, and reconcile outstanding attempts against provider records. Make these operations safe when the same notification arrives more than once, and record timestamps, provider references and state transitions for support teams. If the provider confirms success after the app displayed a pending message, update the existing order and deliver the ticket once. If it confirms failure, release inventory according to the documented hold policy and let the buyer start a new attempt. Avoid automatic repeated charges as a recovery strategy.

  • Checklist: define key creation, persistence, retry scope, provider lookup, notification verification and duplicate-event handling.
  • Set an operational path for unresolved payments, including monitoring, reconciliation and a support-visible audit trail.
03 / 03

Design the recovery journey and test the hard cases

For a hypothetical match-ticket app, suppose a buyer selects two seats and the payment provider processes the charge, but the mobile connection drops before the app receives the response. On reopening checkout, the app should retrieve the existing order from the backend and say that payment is being checked—not ask the buyer to pay again. Once confirmed, show a clear receipt and ticket status; if the attempt is verified as failed, offer a retry that reuses the order but starts a properly defined new payment attempt. If the seats cannot remain held, explain that plainly and show whether the buyer must choose seats again. The flow should never imply a ticket is confirmed before the authoritative confirmation arrives.

Test the workflow across app and backend boundaries: double taps, slow responses, app termination, reconnects, duplicate provider notifications, delayed success, expired inventory holds and a buyer opening checkout on another device. Verify that each scenario yields one understandable order outcome and that payment records can be reconciled. Include accessibility and localization in the status design: error meaning and next action should not depend on colour or a transient toast. Before launch, agree on support language for pending orders and define who investigates cases that exceed the normal reconciliation window. These decisions affect integration scope, QA effort and operational ownership—not only checkout screens.

  • Release checklist: simulate uncertain outcomes with the payment provider’s test environment and verify both backend records and buyer-facing states.
  • Agree in advance on pending-order monitoring, escalation ownership and the wording support staff can use.

Payment recovery is a state-management and integration problem as much as a checkout-design problem. Keep the order, payment attempt and inventory allocation distinct; make repeated requests idempotent; and let verified server-side evidence determine whether a buyer should wait, retry or receive a ticket. For a ticketing MVP, document these rules alongside provider capabilities and failure-path tests before estimating the work. A clear pending state may feel less immediate than an error, but it prevents the app from asking buyers to guess whether they have already paid.