Payment & Withdrawal Management

Overview

This skill enables AI agents and applications to initiate and manage withdrawals and beneficiaries using Eclipse APIs.

It supports:

  • Initiating payments and withdrawals — e.g. ATM withdrawals, EFT withdrawals
  • Retrieving withdrawal history
  • Displaying withdrawal details, e.g. tokens (OTP), expiry, and amount
  • Creating, listing, updating, and deleting payment beneficiaries

All responses shown below use anonymised sample data.

1. Initiate ATM Withdrawal

Create a withdrawal request and generate a token (OTP) for collection.

Notes

  • externalUniqueId must be unique per transaction
  • Duplicate requests with the same externalUniqueId will be rejected

Request

POST {baseUrl}/eclipse-conductor/rest/v1/tenants/{tenant_id}/wallets/{wallet_id}/withdrawals

Example Request

{
  "amount": 100,
  "description": "ATM withdrawal",
  "type": "ZA_PAYCORP_ATM",
  "externalUniqueId": "uuid-redacted",
  "deliverToPhone": "27XXXXXXXXX"
}

Example Response

{
  "withdrawalId": 50001,
  "externalUniqueId": "uuid-redacted",
  "status": "PENDING",
  "token": "123456",
  "expires": "2026-03-30T10:00:00.000Z",
  "amount": 100.00,
  "currency": "ZAR",
  "gateway": "ZA_PAYCORP_ATM",
  "type": "ATM",
  "subType": "NORMAL",
  "walletId": 20001,
  "fee": 0.00,
  "created": "2026-03-24T10:00:00.000Z",
  "description": "ATM withdrawal",
  "deliverToPhone": "27XXXXXXXXX",
  "lastModified": "2026-03-24T10:00:00.000Z",
  "tracingContext": "trace-redacted"
}

API Reference: POST /wallets/{walletId}/withdrawals

2. Get Withdrawals

Retrieve withdrawals for a wallet within a date range.

Request

GET {baseUrl}/eclipse-conductor/rest/v1/tenants/{tenant_id}/wallets/{wallet_id}/withdrawals?dateFromIncl={ISO_DATE}&dateToExcl={ISO_DATE}&limit={limit}&offset={offset}

Notes

  • Dates must be in ISO 8601 format:
    2026-03-01T00:00:00.000Z

Example Response

[
  {
    "withdrawalId": 50001,
    "externalUniqueId": "uuid-redacted",
    "status": "PENDING",
    "token": "123456",
    "expires": "2026-03-30T10:00:00.000Z",
    "amount": 100.00,
    "currency": "ZAR",
    "gateway": "ZA_PAYCORP_ATM",
    "type": "ATM",
    "subType": "NORMAL",
    "walletId": 20001,
    "fee": 0.00,
    "created": "2026-03-24T10:00:00.000Z",
    "extraInfo": "{}",
    "description": "ATM withdrawal",
    "deliverToPhone": "27XXXXXXXXX",
    "lastModified": "2026-03-24T10:00:00.000Z",
    "tracingContext": "trace-redacted"
  },
  {
    "withdrawalId": 50002,
    "externalUniqueId": "uuid-redacted-2",
    "status": "TIMEOUT",
    "errorDescription": "Expired",
    "token": "654321",
    "expires": "2026-03-20T10:00:00.000Z",
    "amount": 100.00,
    "currency": "ZAR",
    "gateway": "ZA_PAYCORP_ATM",
    "type": "ATM",
    "subType": "NORMAL",
    "walletId": 20001,
    "fee": 0.00,
    "created": "2026-03-18T10:00:00.000Z",
    "extraInfo": "{}",
    "description": "ATM withdrawal",
    "deliverToPhone": "27XXXXXXXXX",
    "lastModified": "2026-03-20T10:00:00.000Z",
    "tracingContext": "trace-redacted"
  }
]

3. EFT Withdrawal

Trigger: "process withdrawal", "send money to bank", "EFT payment", "withdraw funds to bank account"

Required inputs: tenantId, walletId, bank account details (or existing beneficiaryId), amount, currency

EFT withdrawals send funds from an Eclipse wallet to an external bank account via a payment beneficiary.

Step 1 — Check Wallet Balance

GET {baseUrl}/eclipse-conductor/rest/v1/tenants/{tenant_id}/wallets/{wallet_id}

Verify availableBalance is sufficient before proceeding.

Step 2 — Create Beneficiary (if not already on file)

Optionally, save the destination bank account as a beneficiary for future reference. Retrieve existing beneficiaries via GET {baseUrl}/rest/v1/beneficiaries?walletId={walletId}.

POST {baseUrl}/rest/v1/beneficiaries
{
  "walletId": 1092847,
  "accountHolderName": "Sipho Dlamini",
  "bankName": "STANDARD_BANK",
  "branchCode": "051001",
  "bankAccountNumber": "123456789",
  "accountType": "CURRENT",
  "reference": "Salary Payout",
  "provider": "RTC"
}

Response:

{
  "beneficiaryId": 4829104,
  "walletId": 1092847,
  "accountHolderName": "Sipho Dlamini",
  "bankName": "STANDARD_BANK",
  "bankAccountNumber": "123456789",
  "branchCode": "051001",
  "accountType": "CURRENT",
  "provider": "RTC",
  "status": "ACTIVE",
  "created": "2026-05-19T10:00:00.000Z"
}

Step 3 — Create Withdrawal

Bank account details are passed directly in the withdrawal request — there is no beneficiaryId field on the withdrawal.

POST {baseUrl}/eclipse-conductor/rest/v1/tenants/{tenantId}/wallets/{walletId}/withdrawals
{
  "amount": 1200.00,
  "type": "ZA_SB_EFT",
  "externalUniqueId": "eft-9f8e7d6c-5b4a-3210-fedc-ba9876543210",
  "accountNumber": "123456789",
  "branchCode": "051001",
  "bank": "STANDARD_BANK",
  "accountName": "Sipho Dlamini",
  "reference": "Salary Payout",
  "description": "EFT payout"
}

Response:

{
  "withdrawalId": 9384716,
  "walletId": 1092847,
  "amount": 1200.00,
  "currency": "ZAR",
  "status": "PENDING",
  "created": "2026-05-19T10:05:00.000Z"
}

Step 4 — Check Withdrawal Status

GET {baseUrl}/eclipse-conductor/rest/v1/tenants/{tenantId}/wallets/{walletId}/withdrawals/{withdrawalId}

Response:

{
  "withdrawalId": 9384716,
  "status": "SUCCESSFUL",
  "lastModified": "2026-05-19T10:07:30.000Z"
}

Notes

  • Possible statuses: BUILDING, PENDING, SUCCESSFUL, ERROR, TIMEOUT, CANCELLED, REVERSED, HOLD.
  • provider values: RTC (near-instant), DTB (same-day), EFT (1–2 business days). Confirm which providers are enabled for the tenant.
  • Never retry with the same externalUniqueId. If uncertain, query withdrawal status first.

API Reference: GET /wallets/{walletId} · POST /beneficiaries · POST /withdrawals

4. Beneficiary Management

Trigger: "add beneficiary", "create payment destination", "add bank account", "manage beneficiaries", "list saved accounts"

Required inputs: full bank account details for creation

Beneficiaries are saved bank accounts or mobile money destinations used when initiating EFT withdrawals and payments.

4.1 Create Beneficiary

POST {baseUrl}/rest/v1/beneficiaries
Authorization: Bearer {jwt}
Content-Type: application/json
{
  "walletId": 1092847,
  "accountHolderName": "Nomsa Khumalo",
  "bankName": "ABSA",
  "branchCode": "632005",
  "bankAccountNumber": "987654321",
  "accountType": "SAVINGS",
  "reference": "Loan repayment",
  "provider": "RTC"
}

Response:

{
  "beneficiaryId": 4829104,
  "walletId": 1092847,
  "accountHolderName": "Nomsa Khumalo",
  "bankName": "ABSA",
  "bankAccountNumber": "987654321",
  "branchCode": "632005",
  "accountType": "SAVINGS",
  "reference": "Loan repayment",
  "provider": "RTC",
  "status": "ACTIVE",
  "created": "2026-05-19T10:00:00.000Z"
}

4.2 List Beneficiaries

GET {baseUrl}/rest/v1/beneficiaries
    ?walletId={walletId}
    &limit=20
    &offset=0
Authorization: Bearer {jwt}

Additional filter query params: customerId, organisationId, bankAccountNumber, mobileNumber.

4.3 Get a Beneficiary

GET {baseUrl}/rest/v1/beneficiaries/{beneficiaryId}
Authorization: Bearer {jwt}

4.4 Update Beneficiary

PUT {baseUrl}/rest/v1/beneficiaries/{beneficiaryId}
Authorization: Bearer {jwt}
Content-Type: application/json
{ "beneficiaryId": 4829104, "reference": "Updated reference — June 2026" }

4.5 Delete Beneficiary

DELETE {baseUrl}/rest/v1/beneficiaries/{beneficiaryId}
Authorization: Bearer {jwt}

Returns 204 No Content on success.

Notes

  • bankName is a free-text string — common values: ABSA, STANDARD_BANK, FNB, NEDBANK, CAPITEC.
  • accountType values: CURRENT, SAVINGS, TRANSMISSION.
  • provider values: RTC (near-instant), DTB (same-day), EFT (1–2 business days). Availability depends on tenant configuration.
  • status values: ACTIVE, BLOCKED, PENDING, REJECTED.
  • onlyCheck: true validates the bank account via AVS without creating a beneficiary record.
  • saveOnPass: true creates the beneficiary only if AVS validation passes.
  • Before creating a beneficiary, check whether one already exists: GET /rest/v1/beneficiaries?walletId={walletId} and match by bankAccountNumber to avoid duplicates.

API Reference: POST /beneficiaries · GET /beneficiaries · PUT /beneficiaries/{id} · DELETE /beneficiaries/{id}


5. Pick n Pay / Boxer Cash In (Top-up)

Trigger: "cash in at PnP", "top up at Pick n Pay", "deposit at Boxer", "PnP cash top-up", "retailer cash deposit"

Required inputs: tenantId, walletId, amount (whole ZAR only), externalUniqueId

The customer receives a token from Eclipse, presents it along with cash to any Pick n Pay or Boxer teller, and the wallet is credited when the teller confirms the deposit.

Prerequisites:

  • Tenant config pnp.config must be enabled for the tenant (contact EFT Corporation to configure)
  • Currency must be ZAR
  • Amount must be a whole number — cents are not accepted by default
  • Minimum deposit: R50. Default daily limit: R3,000. Default monthly limit: R24,000

Step 1 — Request a cash-in token

POST {baseUrl}/eclipse-conductor/rest/v1/tenants/{tenantId}/wallets/{walletId}/topups
Authorization: Bearer {jwt}
Content-Type: application/json

{
  "amount": 200,
  "externalUniqueId": "unique-uuid-per-request",
  "type": "ZA_PNP_CASH"
}

Response (200 OK):

{
  "topupId": 74012,
  "completionToken": "152050197",
  "status": "PENDING"
}

Display the completionToken to the customer — this is the reference number they present at the teller. The token expires at the end of the day after it was generated.

Step 2 — Customer deposits at the teller

Guide the customer through the Pick n Pay / Boxer teller steps:

  1. Select Value Added Services from the menu
  2. Select Mobile
  3. Select Topup
  4. Select EFT Corporation
  5. Provide the amount and the token/reference number

Step 3 — Confirm top-up status

Eclipse credits the wallet as soon as the teller confirms the deposit. Poll the top-up until status is no longer PENDING:

GET {baseUrl}/eclipse-conductor/rest/v1/tenants/{tenantId}/wallets/{walletId}/topups/{topupId}
Authorization: Bearer {jwt}

Status values:

StatusMeaning
PENDINGToken issued; customer has not yet deposited
SUCCESSFULDeposit confirmed at teller; wallet credited
EXPIREDToken expired before the customer deposited
CANCELLEDToken cancelled
📘

Note

If fraud is detected when the deposit is confirmed, a 24-hour reservation is applied — the funds are visible but unavailable until the reservation lifts.

Sandbox simulation: Use amount: 101 to trigger an automatic SUCCESSFUL callback after 10 seconds. Use amount: 102 to simulate an EXPIRED callback after 10 seconds.


6. Pick n Pay / Boxer Cash Out (Withdrawal)

Trigger: "cash out at PnP", "withdraw at Pick n Pay", "PnP cash withdrawal", "withdraw at Boxer", "retailer cash withdrawal"

Required inputs: tenantId, walletId, amount (whole ZAR only), externalUniqueId

The customer receives a withdrawal token from Eclipse, presents it to any Pick n Pay or Boxer teller, and receives cash. The wallet is debited when the token is issued.

Prerequisites:

  • Tenant config pnp.config must be enabled for the tenant
  • Currency must be ZAR
  • Amount must be a whole number — cents are not accepted by default
  • Minimum withdrawal: R50. Maximum per transaction: R3,000

Step 1 — Check wallet balance

GET {baseUrl}/eclipse-conductor/rest/v1/tenants/{tenantId}/wallets/{walletId}
Authorization: Bearer {jwt}

Verify availableBalance is sufficient before proceeding.

Step 2 — Request a cash-out token

POST {baseUrl}/eclipse-conductor/rest/v1/tenants/{tenantId}/wallets/{walletId}/withdrawals
Authorization: Bearer {jwt}
Content-Type: application/json

{
  "amount": 200,
  "type": "ZA_PNP_CASH",
  "externalUniqueId": "unique-uuid-per-request",
  "description": "PnP cash withdrawal"
}

Response (200 OK):

{
  "withdrawalId": 2761462,
  "externalUniqueId": "unique-uuid-per-request",
  "status": "PENDING",
  "token": "144745417",
  "expires": "2026-06-19T21:59:59.000Z",
  "amount": 200,
  "currency": "ZAR",
  "gateway": "ZA_PNP",
  "type": "CASH",
  "subType": "NORMAL",
  "walletId": 1371283,
  "fee": 10,
  "created": "2026-06-18T10:00:00.000Z",
  "description": "PnP cash withdrawal"
}

Display the token to the customer. The wallet is debited immediately when the token is issued.

By default, tokens expire at the end of the day after generation. You can set an earlier expires in the request, but it cannot exceed the default end-of-next-day limit.

Step 3 — Customer collects cash at the teller

Guide the customer through the Pick n Pay / Boxer teller steps:

  1. Select Value Added Services from the menu
  2. Select Mobile
  3. Select Withdrawals
  4. Select EFT Corporation
  5. Provide the reference number/token

Step 4 — Check withdrawal status

GET {baseUrl}/eclipse-conductor/rest/v1/tenants/{tenantId}/wallets/{walletId}/withdrawals/{withdrawalId}
Authorization: Bearer {jwt}
StatusMeaning
PENDINGToken issued; customer has not yet collected
SUCCESSFULCash collected at teller
TIMEOUTToken expired before the customer collected; wallet is refunded
CANCELLEDToken cancelled; wallet is refunded

Notes

  • externalUniqueId must be unique per request — never reuse. Query status before retrying if uncertain.
  • Only whole ZAR amounts are accepted. Submitting a decimal amount returns a 400 error.
  • ZA_PNP_TENDER follows the same flow as ZA_PNP_CASH but the token settles a purchase at a Pick n Pay till instead of dispensing cash.
  • Tokens for both cash-in and cash-out are redeemable at all Pick n Pay and Boxer stores in South Africa.

Common Patterns for AI Agents

1. Initiate Withdrawal Flow

  1. Capture:
    • Amount
    • Phone number
  2. Generate unique externalUniqueId
  3. Initiate withdrawal
  4. Display:
    • Token (OTP)
    • Amount
    • Expiry time

2. Display Active Withdrawal Token

  1. Fetch withdrawals
  2. Filter:
    • status = PENDING
  3. Display:
    • token
    • amount
    • expiry

3. Handle Expired Withdrawals

  • Status = TIMEOUT indicates expired token
  • Prompt user to initiate a new withdrawal

4. PnP / Boxer Cash In Flow

  1. POST to /topups with type: ZA_PNP_CASH → receive completionToken
  2. Display token to customer with teller instructions
  3. Poll GET .../topups/{topupId} until status is SUCCESSFUL or EXPIRED
  4. On EXPIRED: inform customer the token expired; offer to generate a new one

5. PnP / Boxer Cash Out Flow

  1. Verify availableBalance via GET .../wallets/{walletId}
  2. POST to /withdrawals with type: ZA_PNP_CASH → receive token
  3. Display token to customer with teller instructions (wallet is debited immediately)
  4. Poll GET .../withdrawals/{withdrawalId} until status is SUCCESSFUL or TIMEOUT
  5. On TIMEOUT: inform customer the token expired; the wallet is refunded automatically

Error Handling

Typical responses include:

  • 400 – Invalid request
  • 401 – Authentication failure
  • 403 – Permissions error

Example Error Response

[
  {
    "type": "BUSINESS",
    "severity": "LOW",
    "description": "JWT has expired",
    "code": "ARCH014",
    "traceId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "spanId": "xxxxxxxxxxxxxxxx",
    "environment": "sandbox-environment"
  }
]

Best Practices

  • Always generate a unique externalUniqueId per request
  • Never reuse tokens or identifiers
  • Clearly display token expiry to users
  • Avoid exposing sensitive data beyond masked formats
  • Retry only with a new externalUniqueId to prevent duplicates