Wallet Redeem Codes

APay provides built-in wallet redeem codes (redeem_codes and redeem_redemptions) that credit a user's cash balance pool.

  • Currency alignment: Every redeem code's currency is locked to the site's accounting currency (getTopupRules().accountingCurrency, backed by settings.currency). Both code creation and redemption reject mismatched currencies with HTTP 409.
  • Idempotency & concurrency gate: redeem_redemptions.code_id (unique) acts as the concurrency gate so each code can only be claimed once; redeem_redemptions.event_id (redeem:<codeId>) is the deterministic ledger idempotency key; (batch_id, user_id) enforces one redemption per user within the same non-null batch.
  • Wallet backend routing: Local wallet sites (APAY_WALLET_BACKEND=local) credit via changeBalance (actionType: 'redeem', balanceType: 'cash'); external wallet sites (APAY_WALLET_BACKEND=ainode) credit via the theme's walletAdapter (transaction.credit, kind: 'redeem').

User Redemption (POST /api/users/redeem)

Requires an authenticated user session. Rate-limited to 10 requests per minute per user.

Request Body

FieldTypeRequiredDescription
codestringyesRedeem code string (whitespace is stripped and normalized to uppercase)
{
  "code": "RC-2345-6789-ABCD"
}

Response

{
  "code": 0,
  "message": "兑换成功,已到账 10 USD",
  "data": {
    "amount": 10,
    "currency": "USD",
    "balance": 25.5,
    "alreadyProcessed": false
  }
}

If the same user submits a code they have already completed redeeming, the endpoint short-circuits without calling downstream wallet services and returns alreadyProcessed: true.

Admin Endpoints

Protected by the redeem (redeem:view / redeem:edit) admin permission.

List Redeem Codes (GET /api/admin/redeem-codes)

Query parameters: page, pageSize (max 200), status (all | active | used | disabled), batchId, keyword. Effective status, usedBy, and usedAt are reconciled with redeem_redemptions.

Batch Generate Redeem Codes (POST /api/admin/redeem-codes)

Requires redeem:edit.

FieldTypeRequiredDescription
countnumberyesNumber of codes to generate (1–1000)
amountnumberyesFace value per code (> 0) in the site's accounting currency
currencystringnoOptional; if provided, must match the site's accounting currency
batchIdstringnoOptional batch identifier (limits each user to 1 code per batch)
expiresAtstringnoOptional expiration timestamp; timezone-less strings are parsed in settings.timezone
remarkstringnoOptional operator note (max 200 chars)

Disable Redeem Code (DELETE /api/admin/redeem-codes/:id)

Requires redeem:edit. Disables an unused (active) redeem code. Returns HTTP 409 if the code has already been claimed in redeem_redemptions or marked used.