> ## Documentation Index
> Fetch the complete documentation index at: https://docs.feddi.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Money: Actual vs Promotional

> A Feddi balance holds two classes of value: actual money funded by top-ups and promotional credit that is locked, expirable, clawback-able, and spent first.

## Two classes, never one bucket

A Feddi wallet holds value in two distinct classes. Conflating them is the most common modeling
mistake an integrator makes: it overstates the merchant's real liability. Keep them separate in your
own ledger and in anything you render to a cashier.

<Columns cols="2">
  <Card title="Actual balance (real money)" icon="banknote" horizontal="false">
    Funded by customer top-ups. A real deferred-revenue liability the merchant tracks. Spent on
    purchases after promotional credit is exhausted.
  </Card>

  <Card title="Promotional credit" icon="gift" horizontal="false">
    Cashback, reload bonus, signup bonus. A contingent class: locked until earned, expirable,
    clawback-able, merchant-funded, never cashable. Not a real-money liability.
  </Card>
</Columns>

## The Balance object

Every balance Feddi returns exposes both classes distinctly, never a single summed number. Money is
always integer minor units paired with an explicit ISO-4217 `currency`.

<ResponseField name="actual_minor" field-type="integer" required="true">
  Real money funded by top-ups, in minor units. A real deferred-revenue liability. Spent on purchases
  after promotional credit is exhausted.
</ResponseField>

<ResponseField name="promo_available_minor" field-type="integer" required="true">
  Sum of `remaining_minor` across all released, non-expired promotional grants. Spendable now
  (promo-first). Not real money.
</ResponseField>

<ResponseField name="promo_locked_minor" field-type="integer" required="true">
  Sum of `remaining_minor` across all locked promotional grants. Gated on an activation condition (for
  example wallet signup or first top-up). Not yet spendable.
</ResponseField>

<ResponseField name="pending_topups_minor" field-type="integer" required="true">
  Top-ups in flight, not yet confirmed. Informational only, not spendable.
</ResponseField>

<ResponseField name="currency" field-type="string" required="true">
  ISO-4217 currency code for all minor-unit fields in this balance, for example `QAR`.
</ResponseField>

<ResponseField name="customer_state" field-type="string">
  Wallet identity state. One of `verified` or `pending_proof`. A `pending_proof` wallet may hold an
  actual balance but cannot pay; its cashback-class promo stays `LOCKED`.
</ResponseField>

<ResponseField name="promo_grants" field-type="array">
  Itemized promotional grant breakdown. Each entry carries `source`, `state`, `remaining_minor`, and
  `expires_at`. May be an empty array on a tight payment result object.
</ResponseField>

```json theme={null}
{
  "actual_minor": 9500,
  "promo_available_minor": 500,
  "promo_locked_minor": 200,
  "pending_topups_minor": 0,
  "currency": "QAR",
  "customer_state": "verified",
  "promo_grants": [
    { "source": "CASHBACK", "state": "RELEASED", "remaining_minor": 500, "expires_at": "2026-09-05T00:00:00Z" },
    { "source": "RELOAD_BONUS", "state": "LOCKED", "remaining_minor": 200, "expires_at": "2026-09-05T00:00:00Z" }
  ]
}
```

<Info>
  Grant `state` is uppercase everywhere grants appear (`LOCKED`, `RELEASED`, `CLAWED_BACK`,
  `EXPIRED`), both inside the `promo_grants` array and in the grant ledger read at
  `GET /customers/{customerId}/grants`. Claim states are a separate machine and stay lowercase.
</Info>

## Promo-first debit

When Feddi debits a wallet, it spends promotional credit first (FIFO by expiry across all released
grants), then actual balance. The payment result splits the debit explicitly, and the two parts
always sum to the amount.

<ResponseField name="amount_minor" field-type="integer" required="true">
  Total amount debited, in minor units. Equal to `debited_promo_minor + debited_actual_minor`.
</ResponseField>

<ResponseField name="debited_promo_minor" field-type="integer" required="true">
  Portion drawn from released promo grants (FIFO by expiry). Zero if no promo was available.
</ResponseField>

<ResponseField name="debited_actual_minor" field-type="integer" required="true">
  Portion drawn from the actual (real-money) balance.
</ResponseField>

<CodeGroup>
  ```json 200 Payment result (split) theme={null}
  {
    "amount_minor": 3402,
    "debited_promo_minor": 500,
    "debited_actual_minor": 2902
  }
  ```

  ```json 402 Insufficient funds theme={null}
  {
    "error": {
      "code": "INSUFFICIENT_FUNDS",
      "message": "Released promo plus actual balance cannot cover the amount."
    }
  }
  ```
</CodeGroup>

<Info>
  The invariant `debited_promo_minor + debited_actual_minor == amount_minor` always holds. If actual
  plus released promo cannot cover the amount, the call returns `INSUFFICIENT_FUNDS` (402) and the
  credential is not consumed, so you can route to top-up recovery and retry. See
  [Idempotency and errors](/concepts/idempotency-and-errors).
</Info>

## The promo-grant lifecycle

A promotional grant moves through a small state machine. In the grant ledger, the four states are
uppercase.

```
LOCKED ──(claim-gate: verified customer + merchant condition)──▶ RELEASED ──▶ CLAWED_BACK / EXPIRED
```

<ResponseField name="state" field-type="string" required="true">
  One of `LOCKED`, `RELEASED`, `CLAWED_BACK`, `EXPIRED`.
</ResponseField>

* `LOCKED`: accrued but gated. Cashback accrues on every transaction, including cash and card
  purchases, as locked promo. The customer signs up or tops up to unlock it.
* `RELEASED`: spendable. Released only when the customer is verified and the merchant's claim-gate
  condition is met. See [PENDING\_PROOF and promo release](/concepts/pending-proof).
* `CLAWED_BACK`: reversed. For example, reversing a top-up that earned a bonus claws the bonus back.
  A clawback that hits already-spent promo books the spent portion as a merchant marketing loss; it
  is never pulled from the customer's actual cash. This is a terminal state.
* `EXPIRED`: past its own clock. Promo expires on a merchant-configured schedule (for example, three
  months), independent of actual balance. This is a terminal state.

<Info>
  The grant `source` is one of `CASHBACK`, `RELOAD_BONUS`, `SKU_TOPUP_BONUS`, `GATEWAY_BONUS`, or
  `SIGNUP_BONUS`. Treat the set as open: read `GET /capabilities` and validate against the served
  OpenAPI spec rather than hard-coding it.
</Info>

## Accounting for the two classes

Promotional credit is accounted separately from the real-money float. Actual balance is a real
liability (deferred revenue the merchant owes as goods or services). Promotional credit is a
contingent promotional cost, not a settled obligation, because until it is released the customer
cannot spend it. Treating the two as one bucket would overstate what the merchant owes.

## Where to go next

<Columns cols="2">
  <Card title="PENDING_PROOF and promo release" href="/concepts/pending-proof" icon="lock" horizontal="false">
    How locked promo gets released safely, only on verified phone proof.
  </Card>

  <Card title="Top-up and reload bonus" href="/guides/topup" icon="wallet" horizontal="false">
    Crediting actual balance and accruing reload-bonus promo.
  </Card>

  <Card title="Payments and redemption" href="/guides/payments" icon="credit-card" horizontal="false">
    The promo-first debit endpoint in detail.
  </Card>

  <Card title="Incentives" href="/guides/incentives" icon="gift" horizontal="false">
    Read the promo-grant ledger and claw back a grant.
  </Card>
</Columns>
