Core ConceptsMoney: Actual vs Promotional

Money: Actual vs Promotional

A Feddi balance holds two distinct classes of value. Real money and promotional credit are never conflated. Promo 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, and it is wrong in both directions: it overstates the merchant's real liability and it hides the acquisition flywheel.

The Balance object

Every balance Feddi returns exposes both classes distinctly, never a single summed number:

actual_minorinteger

Real money funded by top-ups. A real deferred-revenue liability.

promo_available_minorinteger

RELEASED promotional credit (spendable now). Expirable, non-cashable, clawback-able.

promo_locked_minorinteger

LOCKED promotional credit. Accrued but gated on wallet signup or first top-up. Not yet spendable.

pending_topups_minorinteger

Top-ups in flight. Informational only, not spendable.

promo_grantsarray

Per-grant breakdown: source, state, remaining_minor, expires_at.

{
  "actual_minor": 9500,
  "promo_available_minor": 500,
  "promo_locked_minor": 200,
  "pending_topups_minor": 0,
  "currency": "QAR",
  "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" }
  ]
}

Promo-first debit

When Feddi debits a wallet, it spends promotional credit first (FIFO by expiry across all RELEASED grants), then actual balance. This keeps real money intact for the customer and makes promo use-it-or-lose-it.

The payment result splits the debit explicitly, and the two parts always sum to the amount:

{ "amount_minor": 3402, "debited_promo_minor": 500, "debited_actual_minor": 2902 }

debited_promo_minor + debited_actual_minor == amount_minor, always. 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 & Errors.

The PromoGrant lifecycle

A promotional credit moves through a small state machine:

LOCKED ──(claim-gate: VERIFIED + merchant condition)──▶ RELEASED ──▶ CLAWED_BACK / EXPIRED
  • LOCKED: accrued but gated. Cashback accrues on every transaction, even cash and card purchases, as locked promo. The customer signs up or tops up to unlock it. This is the acquisition flywheel: "money waiting for you" pulls customers into the wallet.
  • RELEASED: spendable. Released only when the customer is VERIFIED and the merchant's claim-gate condition is met. See The PENDING_PROOF Invariant.
  • 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.
  • EXPIRED: past its own clock. Promo expires on a merchant-configured schedule (for example, 3 months), independent of actual balance.

source is one of CASHBACK, RELOAD_BONUS, SKU_TOPUP_BONUS, GATEWAY_BONUS, SIGNUP_BONUS.

Why the distinction matters for the merchant

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 actually owes.

Where to go next