# Designing Payment Statuses That Support Teams Can Explain

A payment provider says a deposit succeeded. The player’s balance has not changed. Support sees “pending,” while the finance export already includes the transaction.

Which system is right?

Each system may be describing a different part of the transaction. The problem is that the platform has presented those different facts as one ambiguous payment status.

For a casino platform, a useful payment model needs to connect external processing, internal wallet accounting and operator reporting. When those layers disagree, the system should make the disagreement visible and actionable.

This article proposes a design for doing that. The examples are illustrative, rather than a description of a particular platform’s production architecture.

## 1\. Give each status a precise meaning

A single field such as `status: "success"` often carries too much responsibility.

Does it mean the provider accepted the request? The payment was confirmed? The wallet was credited? The funds appeared in settlement?

Those are separate facts.

Start with three dimensions:

| Dimension | Question it answers | Example values |
| --- | --- | --- |
| Payment processing | What has the external provider reported? | Created, pending, confirmed, failed, reversed |
| Wallet posting | Has the corresponding internal accounting entry been committed? | Not posted, posted, compensated |
| Reconciliation | Have the internal and external records been matched? | Unmatched, matched, exception |

These values are an example vocabulary. Each provider needs an explicit mapping because its definition of “confirmed” may differ.

A deposit can be externally confirmed and internally posted while still awaiting reconciliation. That does not automatically mean the player should wait for access to the balance.

The product needs a documented rule for when funds become available.

## 2\. Model the payment separately from its ledger entries

Here is a simplified deposit record:

```json
{
  "paymentId": "dep_demo_001",
  "providerAccountId": "merchant_demo",
  "providerPaymentId": "provider_payment_demo_001",
  "walletId": "wallet_demo_001",
  "amountMinor": "2500",
  "currency": "EUR",
  "paymentStatus": "confirmed",
  "walletPostingStatus": "posted",
  "reconciliationStatus": "unmatched",
  "ledgerTransactionId": "ledger_demo_001"
}
```

In this example, `"2500"` represents €25.00. The amount is serialized as a string so applications can parse it into an integer or exact decimal representation without relying on floating-point arithmetic.

Do not assume every currency or asset has two decimal places. For crypto assets, define the network, asset identifier and precision explicitly.

The payment record describes the business operation. The ledger records its accounting effect.

A ledger transaction should preserve balanced entries under the chosen accounting model. It should also retain the payment reference so an investigator can move from the deposit to the associated entries.

If a posted payment is later reversed, create a linked compensating transaction. Preserve the original entry and the reason for the correction.

## 3\. Separate webhook receipt from payment processing

A webhook is a notification that needs verification and interpretation.

For example, Stripe’s [webhook documentation](https://docs.stripe.com/webhooks) describes signature verification, duplicate deliveries and events arriving out of order. These are useful delivery concerns to account for when designing integrations; check the equivalent documentation for your chosen provider.

A proposed receiving flow is:

1. Verify the signature using the provider’s documented method.
2. Validate the event structure and merchant context.
3. Persist the event in a durable inbox.
4. Acknowledge receipt after durable storage succeeds.
5. Process the event asynchronously.

The inbox makes processing recoverable. If a worker stops after receipt, it can resume from the stored event.

If the event cannot be stored, the receiver should not acknowledge successful durable acceptance. Return the appropriate failure response so the provider’s retry mechanism can operate.

Track inbox processing separately from payment status. “Webhook received” does not mean “wallet credited.”

## 4\. Protect against two kinds of duplicate

There are two distinct duplication problems.

**Delivery duplication:** The same provider event arrives more than once.

**Business duplication:** Different events or retries attempt to credit the same deposit more than once.

An inbox uniqueness constraint can handle the first:

```text
UNIQUE (
  provider,
  provider_account_id,
  provider_event_id
)
```

A separate business constraint protects the wallet effect:

```text
UNIQUE (
  payment_id,
  posting_type
)
```

These constraints assume one initial credit per deposit. A system supporting partial captures, split payments or multiple adjustments needs a more detailed operation identity.

The database transaction that creates the ledger entries should also update the corresponding posting state. If separate services own these records, design a durable coordination process with idempotent consumers and a clear recovery path.

For outbound requests, reuse the same idempotency key when retrying the same operation, where the provider supports it. Stripe’s [idempotent request documentation](https://docs.stripe.com/api/idempotent_requests) illustrates this pattern.

A new key on every retry defeats that protection.

## 5\. Treat a timeout as an unknown outcome

Suppose a withdrawal request reaches the payment provider, but the connection closes before the response reaches your backend.

The request may have failed. It may also have succeeded.

Immediately sending a new withdrawal with a new identity risks paying twice.

Represent this uncertainty explicitly. The system should query the provider using the original reference or retry under the provider’s documented idempotency rules.

This is also why a generic “failed” label can be dangerous. Distinguish:

- A provider explicitly rejecting an operation.
- A local validation failure before submission.
- A timeout after submission with an unresolved outcome.

Each requires a different next action.

## 6\. Build a timeline that support can use

Support needs more than a status badge.

For each payment, show:

| Timeline event | Operational meaning |
| --- | --- |
| Request created | The platform recorded the intended operation. |
| Submitted to provider | An external request was attempted. |
| Provider confirmation received | The provider reported the relevant confirmation state. |
| Ledger transaction committed | The internal wallet effect was recorded. |
| Reconciliation matched | The expected external and internal records were matched. |

Store both the provider’s event time and your system’s receipt time. They answer different questions.

A support view might say:

**Deposit confirmed; wallet credit awaiting processing.**

Then show the last successful step, elapsed time, relevant references and the team or automated process responsible for recovery.

Restrict sensitive payloads and personal information by role. Most support investigations need references and readable explanations, not unrestricted access to raw payment data.

## 7\. Give withdrawals their own workflow

A withdrawal is not simply a deposit with a negative amount.

The platform may need to reserve funds, review the request, submit it externally and resolve the outcome.

A possible workflow is:

**Requested → Funds reserved → Review → Submitted → Paid**

Rejection or cancellation before external submission may release the reservation. An unresolved result after submission needs investigation before funds are released or another payout is attempted.

The reservation step should prevent concurrent spending of the same funds. It must be enforced by the underlying balance and transaction controls, rather than only by disabling a button in the interface.

Also define “paid” against the provider’s actual semantics. Request acceptance and final delivery are not necessarily the same event.

## 8\. Reconcile records, not just totals

Matching daily totals is useful, but totals alone can hide individual errors.

Start by matching transactions using stable identifiers. Then compare amounts, currencies, fees and relevant dates.
