Appearance
Refunds
Automatic refunds
IIMMPACT creates refunds automatically.
| Reason | Amount | When |
|---|---|---|
failed_fulfillment | Sum of failed units' frozen sale prices, rounded to two decimals per unit; excludes customer fees. | One aggregate refund against the order's winning payment, after all units finish. |
duplicate_collection | Entire extra collection, including its customer fee. | A separately confirmed duplicate collection; never fulfills the order again. |
If the RM60 item succeeds and the RM40 item fails, the order gets one RM40 refund. If every item fails, IIMMPACT refunds the full item subtotal; the customer's fee share is not refunded. For quantity three with one failed unit, IIMMPACT refunds one unit's sale price. No refund is created when all units succeed.
Each duplicate collection gets its own refund. Refunds appear in the order's refunds array, in refund reads and in refund events, all in the schema below.
CARD refunds return to the card. FPX refunds, and card refunds the card rejects, go to requires_action until the customer claims them to a bank account.
A failed_fulfillment refund is deducted from your account balance before the customer is repaid. If your balance is too low, the refund stays pending and retries automatically. duplicate_collection refunds are not deducted from your balance.
Refund schema
| Field | Type | Description |
|---|---|---|
refund_id | string | Stable identifier through processing, claim and review. |
order_id | string | Order receiving the refund. |
payment_id | string | Original collection being repaid; not a per-item allocation. |
reason | string | failed_fulfillment or duplicate_collection. |
amount | string | Positive two-place refund amount. |
currency | string | MYR. |
status | string | pending, requires_action, requires_review, or successful. See refund progress. |
failure | object | Only while requires_action after the bank rejected a transfer. |
failure.code | string | destination_rejected. |
failure.message | string | For example The bank rejected the transfer to Maybank xxxx9012. |
failure.destination | object | The rejected account, masked: type and display. |
destination | object | After a claim, while pending or successful: the receiving account, masked, with type (bank_account) and display. |
action | object | Only on order and refund reads while requires_action; never in webhooks. |
action.type | string | redirect. |
action.url | string | Claim page for the customer. |
action.expires_at | string | Link expiry, 15 minutes after the read. Read again for a new link. |
created_at | string | UTC refund creation time. |
updated_at | string | UTC time of the latest public refund change. |
Refund example
json
{
"refund_id": "01M4WJ0665EFKXS1ECZR7PBTM8",
"order_id": "01M4WHY4KZVJ0APG07TFH7PM0W",
"payment_id": "01M4WHY4T2VHJSJJXNFYA99Q0B",
"reason": "failed_fulfillment",
"amount": "10.00",
"currency": "MYR",
"status": "requires_action",
"created_at": "2026-10-14T06:36:07.236Z",
"updated_at": "2026-10-14T06:36:07.737Z",
"action": {
"type": "redirect",
"url": "https://pay.iimmpact.com/refunds/claim#<token>",
"expires_at": "2026-10-21T05:52:14.396Z"
}
}Refund progress
| Status | Meaning |
|---|---|
pending | Waiting for your balance, queued, or awaiting bank or provider confirmation. |
requires_action | The customer must choose a bank account. There is no deadline. After a bank rejection, failure explains why. |
requires_review | The result is uncertain; IIMMPACT is reviewing it. It moves to successful, or back to requires_action if the customer was not paid. |
successful | Customer repayment is verified. |
Order completed does not mean repayment finished; wait for refund successful, and do not refund a customer yourself while a refund is under review. The winning payment's merchant fee remains billable even when every unit fails.
Customer refund claims
When you receive refund.requires_action, the customer must choose the bank account for the refund. Either:
- Use the IIMMPACT claim page: when the customer opens your refund screen, read the refund and redirect them to
action.urlat once. The link expires after 15 minutes, so do not email it. Or - Build your own screen with the claim endpoints.
The account must belong to the payer: IIMMPACT gets the holder name from the bank and compares it with the payer's name from the payment provider. Only when the provider reports none is the customer.name you sent used, so send the customer's real name. The full refund amount is always paid. After a claim the refund is pending until the bank confirms; a rejected transfer returns it to requires_action.
Get a refund
http
GET https://api.iimmpact.com/v2/refunds/{refund_id}Returns the refund in data, including action while it is requires_action. Errors: 404 refund_not_found, 400 invalid_refund_id.
List refunds
http
GET https://api.iimmpact.com/v2/refunds?status=requires_action&limit=20Optional query fields: order_id, payment_id, status, limit (1–100, default 20) and cursor (the previous page's next_cursor). Returns data.items newest first, without action, and data.next_cursor when more exist.
Claim endpoints
All use API Key + HMAC. customer_external_id is the customer's external_id from the payment. Never log full account numbers.
| Operation | Endpoint |
|---|---|
| List banks | GET /v2/banks |
| List saved accounts | GET /v2/refund-accounts?customer_external_id={id} |
| Check an account | POST /v2/refund-accounts/lookup |
| Claim the refund | POST /v2/refunds/{refund_id}/claim |
| Remove a saved account | DELETE /v2/refund-accounts/{refund_account_id}?customer_external_id={id} (204) |
GET /v2/banks returns a plain array of { "bank_name", "bank_code" }, without a data envelope.
Saved accounts are created by successful claims and returned in data as an array, newest first, each with refund_account_id, type, bank_code, display (masked), account_holder_name and is_default.
Check an account and show the returned holder name to the customer. account_number is 4–34 digits:
json
{
"refund_id": "01M4WJ0665EFKXS1ECZR7PBTM8",
"destination": { "type": "bank_account", "bank_code": "MBBEMYKL", "account_number": "123456789012" }
}json
{
"data": {
"account_holder_name": "AMIRA BINTI HASSAN",
"destination": { "type": "bank_account", "display": "Maybank xxxx9012" }
}
}Claim with { "destination": { ... } } (same shape, without refund_id) or { "refund_account_id": "..." }, never both. The bank check runs again on every claim. The response is data with refund_id, status: "pending" and the masked destination.
Each refund allows 10 bank checks per 24 hours, claims included.
| HTTP | Code | Action |
|---|---|---|
| 400 | invalid_destination | Fix the destination or account ID. Names and amounts are not accepted. |
| 400 | invalid_customer_external_id | Send the customer's external_id. |
| 404 | refund_not_found, refund_account_not_found | Not found, removed, or another customer's. |
| 409 | refund_not_claimable | The refund no longer needs customer action; read its status. |
| 422 | payer_mismatch | The holder does not match the payer. Ask for the customer's own account. |
| 422 | destination_not_found | The bank cannot find this account. |
| 429 | lookup_limit_reached | Bank-check limit reached; try again later. |
| 503 | lookup_unavailable | Bank check temporarily unavailable; retry later. |
