Skip to content

Refunds ​

Refund a successful Orange Money payment, fully or partially, with one API call.

How it works ​

  • The money is sent back to the payer's Orange Money number (an Orange Money credit to the wallet that paid). The payment must therefore have a payer phone number; otherwise the refund is refused with refund_payer_phone_missing.
  • The refunded amount is debited from your SOAMTAR balance.
  • Partial refunds are allowed: you can refund a payment several times until the sum of the refunds reaches the payment amount.
  • The refund is submitted to Orange Money in the same request, but the credit is asynchronous: in live mode the refund usually comes back processing and its final status reaches you through the refund.success / refund.failed webhooks (or GET /v1/refunds/{id}).
  • Your API key needs the can_refund scope, and refunds must be enabled on your account.
  • Use a key of the same environment as the payment: a sk_test_ key refunds sandbox payments only, a sk_live_ key live payments only.

Create a refund ​

POST /v1/refunds

Headers ​

  • Authorization: Bearer sk_(test|live)_…
  • Idempotency-Key: <uuid> (strongly recommended — a retried request must never refund twice; see Idempotency)
  • Content-Type: application/json

Body ​

json
{
  "transaction_id": "0e85b3c2-7c4f-4f1d-9b3a-1c8a2d4e5f6a",
  "amount": "40.00",
  "reason": "customer_request",
  "description": "Item returned"
}
FieldTypeRequiredNotes
transaction_idstringyesPayment id (UUID) or reference (TXN-…). payment_id is accepted as an alias.
amountdecimal stringoptionalAmount to refund, at most two decimals. Omit it to refund the full remaining amount.
reasonstring, max 100optionalYour reason code, e.g. customer_request, duplicate, fraudulent.
descriptionstring, max 500optionalFree text kept with the refund.

Response 201 ​

The response carries the refund's current status:

  • sandbox (sk_test_): success — the simulator settles immediately;
  • Orange Money (sk_live_): processing — the credit is on its way; wait for refund.success / refund.failed;
  • failed when Orange Money refused the credit (see failure_reason). The amount is released and you can try again.
json
{
  "data": {
    "id": "9b1f4c2e-5d6a-4e8b-a1c3-2f4e6a8b0c1d",
    "object": "refund",
    "reference": "REF-7K2M9Q4XW8T1B3NC",
    "transaction_id": "0e85b3c2-7c4f-4f1d-9b3a-1c8a2d4e5f6a",
    "amount": "40.00",
    "currency": "SLE",
    "reason": "customer_request",
    "description": "Item returned",
    "status": "processing",
    "fee_refunded": false,
    "fee_refunded_amount": "0.60",
    "tax_refunded_amount": "0.00",
    "failure_reason": null,
    "processed_at": null,
    "failed_at": null,
    "created_at": "2026-10-22T10:04:58+00:00",
    "updated_at": "2026-10-22T10:04:59+00:00"
  }
}
bash
curl -X POST https://app.so-amtar.com/v1/refunds \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"transaction_id":"TXN-1A2B3C4D-ABCDEF123456","amount":"40.00","reason":"customer_request"}'

Retrieve a refund ​

GET /v1/refunds/{id_or_reference}

Accepts the refund id (UUID) or its reference (REF-…). Returns {"data": Refund}, or 404 refund_not_found.

List refunds ​

GET /v1/refunds?status=success&transaction_id=0e85b3c2-...&per_page=25&page=1

ParameterNotes
statuspending, processing, success or failed
transaction_idOnly the refunds of this payment (UUID)
per_page1 to 100, default 25
pagePage number, from 1

Sorted by created_at descending.

json
{
  "data": [ { "id": "9b1f4c2e-...", "object": "refund", "reference": "REF-7K2M9Q4XW8T1B3NC", "status": "success", "...": "..." } ],
  "pagination": { "total": 3, "per_page": 25, "current_page": 1, "last_page": 1 }
}

The refund object ​

FieldTypeDescription
idUUIDRefund identifier
objectstringAlways refund
referencestringREF- followed by 16 characters. Also used as the Orange Money transaction id of the credit.
transaction_idUUIDThe refunded payment
amountdecimal stringRefunded amount, e.g. "400.00"
currencystringCurrency of the payment (SLE)
reason / descriptionstring | nullAs sent on creation
statusenumpending, processing, success, failed
fee_refundedbooleanWhether the SOAMTAR fee on the refunded share was returned to you
fee_refunded_amountdecimal stringPro-rata share of the payment's SOAMTAR fee for this refund
tax_refunded_amountdecimal stringPro-rata share of the payment's tax for this refund
failure_reasonstring | nullWhy the refund failed
processed_atISO 8601 | nullWhen the refund succeeded
failed_atISO 8601 | nullWhen the refund failed
created_at / updated_atISO 8601Timestamps

Statuses ​

StatusMeaning
pendingCreated, not yet submitted to Orange Money
processingCredit submitted to Orange Money, waiting for its confirmation
successThe payer's wallet was credited (final)
failedRefused or not completed (final); the amount is released

A refund that stays processing is checked automatically with Orange Money; a credit that Orange Money never registered is marked failed after 60 minutes.

When a refund succeeds, the payment moves to partially_refunded, or to refunded once refunds total the payment amount.

Webhooks ​

Each refund produces its own events:

OutcomeEvents
Successrefund.success + payment.partially_refunded (or payment.refunded when the payment is now fully refunded)
Failurerefund.failed

refund.* events carry the refund object plus transaction_reference, merchant_reference and is_test; payment.*refunded events carry the transaction payload with the refund in data.refund. See Webhooks → Refund events. Subscribe your endpoint to these events (they are not in the SDKs' default subscription).

Errors ​

CodeHTTPCause
transaction_not_found404Unknown payment id or reference for this merchant
refunds_disabled422Refunds are not enabled on your account (contact support)
refund_environment_mismatch422Test key on a live payment, or live key on a sandbox payment
transaction_not_refundable422The payment is not success or partially_refunded
non_payment_transaction_refund422Only payments can be refunded (not payouts)
provider_refund_unsupported422The payment method does not support refunds
refund_payer_phone_missing422The payment has no payer phone number to credit
amount_must_be_positive422amount is zero or negative
transaction_already_fully_refunded422Nothing left to refund on this payment
refund_exceeds_refundable422amount is above the remaining refundable amount
refund_rejected422The refund could not be created (other business rule)
scope_insufficient403The API key lacks the can_refund scope
refund_not_found404GET /v1/refunds/{id}: unknown refund id or reference

SDKs ​

Every server SDK exposes refunds.create, refunds.retrieve and refunds.list (named according to each language). For example in Node.js:

js
const { data: refund } = await soamtar.refunds.create({
  transaction_id: payment.id,   // omit amount to refund everything left
  amount: '40.00',
  reason: 'customer_request',
}, { idempotencyKey: `refund-${order.id}-1` });
// refund.status: 'success' (sandbox) | 'processing' (Orange Money) | 'failed'

See the SDK pages for the other languages, and the CMS plugins for refunds from WooCommerce, Magento, PrestaShop, Drupal Commerce and Odoo.

SOAMTAR Payment API Documentation.