Skip to content

Redemptions ​

Submit a voucher, check its status, or list earlier redemptions.

Redemption object ​

All redemption endpoints return this shape:

FieldTypeNotes
idintegerRedemption ID. Use it with GET /redemptions/{id} to poll pending attempts.
providerstringott, onevoucher, fnb, 4all, bluvoucher, standard_bank, nedbank, capitec.
voucher_code_last4stringLast four digits of the voucher code. The API does not return the full code.
face_valuefloatValue redeemed. For 4ALL, this is the verified remaining balance, which may be less than the original voucher value.
platform_feefloatFee charged for the redemption.
net_amountfloatVoucher value after provider and platform fees. This is not always a wallet credit; see wallet_credit_amount.
settlement_modestringplatform: the voucher was redeemed on a Fluxr upstream account and wallet_credit_amount was credited to your wallet. vendor_owned: it was redeemed on your own upstream account, which receives the funds directly; no wallet credit and no platform fee.
wallet_credit_amountfloatAmount credited to your Redemption wallet for this redemption. 0 for vendor_owned and for unsuccessful redemptions.
currencystringISO currency code. Currently always ZAR.
statusstringpending, success, failed, reversed.
provider_referencestring|nullProvider transaction reference when available.
created_atstring|nullISO-8601 UTC timestamp.
error_codestringOnly present when status is failed. See Error Codes.
error_messagestring|nullOnly present when status is failed.

POST/api/v1/redemptions ​

Redeem a voucher.

Request body ​

FieldTypeRequiredNotes
voucher_codestringYesThe API removes spaces and dashes. Retail vouchers must contain 12 to 16 digits. FNB and 4ALL voucher PINs with an explicit provider of fnb or 4all must contain exactly 16 digits. Bank cash-out vouchers with an explicit provider must contain 8 to 16 digits.
customer_phonestringOne of the twoEnd-customer phone in international or local format. It must contain 9 to 15 digits and may start with +. Required unless you send customer_id.
customer_idstringOne of the twoA stable reference from your system. Maximum 64 characters using letters, numbers, _, -, ., or :. Required unless you send customer_phone.
providerstringBank cash-out onlystandard_bank, nedbank, or capitec to redeem a bank voucher. May also pin ott, bluvoucher, onevoucher, or fnb explicitly, skipping auto-detection. Send fnb when redeeming an FNB voucher. Send 4all for Lesaka 4ALL. 4ALL requires operator enablement after staging UAT.
pinstringBank cash-out onlyVoucher PIN with 4 to 10 characters. Required when provider is standard_bank, nedbank, or capitec.

Why customer identity is required

Providers mandate a tracking identifier for the end customer redeeming the voucher. Send customer_phone when you have it. Otherwise, send a stable customer_id from your system. Fluxr derives the provider identifier without storing or forwarding the raw phone number.

Lesaka 4ALL ​

4ALL is disabled by default pending staging UAT. Its provider switch in the gateway is the only setting: callers send nothing extra. Once enabled, submit provider: "4all", a 16-digit voucher_code, and the usual customer identity. The gateway checks status and redeems the full remaining ZAR balance. Client partial amounts are not supported. Alternatively, omit provider: 4ALL is tried after a definitive FNB not-found verdict, like any other brand in the chain. An allow_4all field from older callers is ignored. For eligible 16-digit automatic requests, a read-only 4ALL status lookup runs alongside the initial 1Voucher redemption and Blu lookup, or alongside the initial Blu lookup for other PINs. The existing provider order is unchanged. A slow 4ALL lookup does not delay another provider's success or uncertain outcome; only the 4ALL fallback waits for it. The same status result supplies the full remaining amount, without another lookup. No two redemptions run in parallel. A timeout, authentication failure or uncertain outcome never permits fallback. If the 4ALL status check fails before any 4ALL redemption is sent, the attempt stays pending with a verification retry (retry.available_at, 45 seconds later). It is never reported as INVALID_VOUCHER, and each failed status retry can earn another. Existing receipts and confirmed-credit recovery remain available after disablement. Turning the switch off stops new 4ALL calls, including routing remembered for a PIN and retries already granted.

After an uncertain redemption, the API holds the attempt for reconciliation. Waiting or changing the caller reference does not authorize another submission. The saved provider reference, amount, and timestamp remain unchanged. Changing or omitting provider cannot route a pending 4ALL request elsewhere. Another vendor cannot replay its redemption request. Recovery can settle an already recorded provider success once, without calling Lesaka again. Recovery of a lost provider response still requires reconciliation; no automatic Lesaka replay is exposed by the API.

How the provider is detected ​

When you omit provider, Fluxr selects it from the voucher code:

  • A 16-digit code starting with 1 goes to 1Voucher.
  • Any other 16-digit code goes to BluVoucher.
  • Fluxr checks a 12-digit code with BluVoucher first. If BluVoucher does not recognise it or cannot be reached, Fluxr routes the code to OTT.
  • If BluVoucher does not recognise a 16-digit code, Fluxr attempts it as an FNB voucher. Send provider: "fnb" explicitly to skip the BluVoucher step.

A code that matches none of these formats fails validation.

Example request ​

bash
curl --request POST "{{BASE_URL}}/api/v1/redemptions" \
  --header "Accept: application/json" \
  --header "Content-Type: application/json" \
  --header "Authorization: Bearer {{TOKEN}}" \
  --data '{
    "voucher_code": "123456789012",
    "customer_phone": "+27821234567"
  }'
bash
curl --request POST "{{BASE_URL}}/api/v1/redemptions" \
  --header "Accept: application/json" \
  --header "Content-Type: application/json" \
  --header "Authorization: Bearer {{TOKEN}}" \
  --data '{
    "voucher_code": "140408195012",
    "provider": "nedbank",
    "pin": "9782",
    "customer_id": "cust-58201"
  }'
php
<?php

use Illuminate\Support\Facades\Http;

$response = Http::baseUrl(env('FLUXR_API_BASE_URL', 'https://redemption.fluxr.co.za'))
    ->withToken(env('FLUXR_API_TOKEN'))
    ->acceptJson()
    ->asJson()
    ->post('/api/v1/redemptions', [
        'voucher_code' => '123456789012',
        'customer_phone' => '+27821234567',
    ]);

$status = $response->status();
$body = $response->json();

Success: 201 for new, 200 for repeat ​

A new successful redemption returns 201. Submitting an already completed voucher returns 200 with the same redemption record. Both responses include a wallet snapshot, so you do not need another request for the balance.

json
{
  "success": true,
  "message": "Voucher redeemed.",
  "data": {
    "id": 123,
    "provider": "ott",
    "voucher_code_last4": "9012",
    "face_value": 100.0,
    "platform_fee": 2.0,
    "net_amount": 98.0,
    "settlement_mode": "platform",
    "wallet_credit_amount": 98.0,
    "currency": "ZAR",
    "status": "success",
    "provider_reference": "OTT-REF-123",
    "created_at": "2026-08-19T10:00:00.000000Z",
    "wallet": {
      "currency": "ZAR",
      "balance": 98.0,
      "pending_balance": 0.0,
      "on_hold_balance": 0.0
    }
  }
}

Pending confirmation: 409 ​

The provider has not confirmed the outcome. This can happen when a request times out during redemption. Do not submit the voucher again because the provider may already have used it. Poll GET /api/v1/redemptions/{id} until status changes to success or failed. Fluxr also checks unresolved attempts in the background.

json
{
  "success": false,
  "message": "Processing / pending confirmation. Don’t retry yet.",
  "errors": {
    "provider": ["PENDING_CONFIRMATION"]
  }
}

When the 4ALL status check failed before anything was sent to 4ALL, the same 409 says The voucher provider was unavailable. Please try again shortly. instead. The receipt carries a verification retry that becomes available 45 seconds later.

Voucher failure: 422 ​

A failed voucher returns 422 with a code from error codes and the customer message for that code:

json
{
  "success": false,
  "message": "This voucher has already been redeemed. If you did not use it, please contact support.",
  "errors": {
    "provider": ["ALREADY_REDEEMED"]
  }
}

Fluxr keeps the result for ALREADY_REDEEMED, EXPIRED, and CANCELLED. A repeat submission returns that result without calling the provider. Fluxr checks INVALID_VOUCHER and INACTIVE again on a later submission because those states can change.

Provider unavailable: 422 or 502 ​

An unavailable or disabled provider returns 422 with PROVIDER_DOWN. An unexpected failure in the redemption process returns 502 with the same code. Retry either response after a short delay.

A 422 response looks like this. The 502 response uses the message Redemption failed.

json
{
  "success": false,
  "message": "We could not redeem this voucher right now. Please try again in a moment.",
  "errors": {
    "provider": ["PROVIDER_DOWN"]
  }
}

Validation error: 422 ​

Request validation returns Laravel's standard response without a success key:

json
{
  "message": "customer_phone or customer_id is required to identify the end customer.",
  "errors": {
    "customer_phone": [
      "customer_phone or customer_id is required to identify the end customer."
    ]
  }
}

GET/api/v1/redemptions ​

List your redemptions, newest first. Results are always scoped to the authenticated vendor.

Query parameters ​

ParameterTypeRequiredNotes
statusstringNopending, success, failed, reversed
providerstringNoonevoucher, fnb, ott, bluvoucher, standard_bank, nedbank, capitec
fromdateNoRequested-at start date (inclusive)
todateNoRequested-at end date (inclusive)
per_pageintegerNoDefault 15, max 100

Example request ​

bash
curl --request GET "{{BASE_URL}}/api/v1/redemptions?status=success&provider=ott&from=2026-08-01&to=2026-08-31&per_page=20" \
  --header "Accept: application/json" \
  --header "Authorization: Bearer {{TOKEN}}"
php
<?php

use Illuminate\Support\Facades\Http;

$response = Http::baseUrl(env('FLUXR_API_BASE_URL', 'https://redemption.fluxr.co.za'))
    ->withToken(env('FLUXR_API_TOKEN'))
    ->acceptJson()
    ->get('/api/v1/redemptions', [
        'status' => 'success',
        'provider' => 'ott',
        'from' => '2026-08-01',
        'to' => '2026-08-31',
        'per_page' => 20,
    ]);

$body = $response->json();

Success response: 200 ​

json
{
  "success": true,
  "message": "OK",
  "data": {
    "items": [
      {
        "id": 123,
        "provider": "ott",
        "voucher_code_last4": "9012",
        "face_value": 100.0,
        "platform_fee": 2.0,
        "net_amount": 98.0,
        "settlement_mode": "platform",
        "wallet_credit_amount": 98.0,
        "currency": "ZAR",
        "status": "success",
        "provider_reference": "OTT-REF-123",
        "created_at": "2026-08-19T10:00:00.000000Z"
      },
      {
        "id": 124,
        "provider": "onevoucher",
        "voucher_code_last4": "3344",
        "face_value": 50.0,
        "platform_fee": 1.0,
        "net_amount": 49.0,
        "settlement_mode": "platform",
        "wallet_credit_amount": 0.0,
        "currency": "ZAR",
        "status": "failed",
        "provider_reference": null,
        "created_at": "2026-08-19T11:00:00.000000Z",
        "error_code": "INVALID_VOUCHER",
        "error_message": "Invalid voucher code. Check and try again."
      }
    ],
    "meta": {
      "current_page": 1,
      "last_page": 1,
      "per_page": 20,
      "total": 2
    }
  }
}

GET/api/v1/redemptions/{id} ​

Fetch one redemption by ID. Poll this endpoint after a 409 PENDING_CONFIRMATION response.

Vendors can only fetch their own redemptions; an ID that isn't yours returns 404.

Example request ​

bash
curl --request GET "{{BASE_URL}}/api/v1/redemptions/123" \
  --header "Accept: application/json" \
  --header "Authorization: Bearer {{TOKEN}}"
php
<?php

use Illuminate\Support\Facades\Http;

$response = Http::baseUrl(env('FLUXR_API_BASE_URL', 'https://redemption.fluxr.co.za'))
    ->withToken(env('FLUXR_API_TOKEN'))
    ->acceptJson()
    ->get('/api/v1/redemptions/123');

$body = $response->json();

Success response: 200 ​

json
{
  "success": true,
  "message": "OK",
  "data": {
    "id": 123,
    "provider": "ott",
    "voucher_code_last4": "9012",
    "face_value": 100.0,
    "platform_fee": 2.0,
    "net_amount": 98.0,
    "settlement_mode": "platform",
    "wallet_credit_amount": 98.0,
    "currency": "ZAR",
    "status": "success",
    "provider_reference": "OTT-REF-123",
    "created_at": "2026-08-19T10:00:00.000000Z"
  }
}

Not found: 404 ​

Returned when the redemption doesn't exist or belongs to another vendor.