#!/usr/bin/env bash
# Refund against a settled transaction (full or partial, repeatable up to the
# original amount). Only once its batch has CLOSED — see
# snippets/shared/void-or-refund-batch-rule.mdx. Retry the SAME request_id on
# a timeout/5xx.
set -euo pipefail
API_BASE="${RADIUMONE_API_BASE:-https://api-sandbox.radiumone.io/gateway}"
: "${RADIUMONE_ACCESS_TOKEN:?set RADIUMONE_ACCESS_TOKEN to a Bearer access token}"
: "${RADIUMONE_TRANSACTION_ID:?set RADIUMONE_TRANSACTION_ID to the CAPTURED transaction to refund}"
curl -sS -X POST "$API_BASE/v1/transactions/refund/$RADIUMONE_TRANSACTION_ID" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $RADIUMONE_ACCESS_TOKEN" \
-d @request.json
#!/usr/bin/env python3
"""Refund against a settled transaction, only once its batch has CLOSED — see
snippets/shared/void-or-refund-batch-rule.mdx.
"""
import json
import os
import random
import time
from pathlib import Path
import requests
API_BASE = os.environ.get("RADIUMONE_API_BASE", "https://api-sandbox.radiumone.io/gateway")
def backoff_seconds(attempt: int) -> float:
"""Exponential backoff with jitter: attempt 1 waits ~0.25-0.5s, doubling
each attempt, capped at 4s -- avoids hammering the gateway in a loop."""
base = min(0.25 * 2 ** (attempt - 1), 4.0)
return base + random.random() * base
def refund_transaction(max_attempts: int = 3) -> dict:
body = json.loads((Path(__file__).parent / "request.json").read_text())
transaction_id = os.environ["RADIUMONE_TRANSACTION_ID"]
headers = {"Authorization": f"Bearer {os.environ.get('RADIUMONE_ACCESS_TOKEN', '')}"}
for attempt in range(1, max_attempts + 1):
try:
resp = requests.post(
f"{API_BASE}/v1/transactions/refund/{transaction_id}", json=body, headers=headers, timeout=30
)
except requests.exceptions.Timeout:
if attempt == max_attempts:
raise
time.sleep(backoff_seconds(attempt))
continue
if resp.status_code >= 500:
if attempt == max_attempts:
raise RuntimeError(f"server error {resp.status_code} after {attempt} attempts")
time.sleep(backoff_seconds(attempt))
continue
payload = resp.json()
if not resp.ok:
# 409 tx:refund-on-open-batch — the batch is still open; void instead.
code = payload.get("type") or payload.get("code")
raise RuntimeError(f"refund failed: {code} ({resp.status_code})")
return payload
raise RuntimeError("unreachable")
if __name__ == "__main__":
print(json.dumps(refund_transaction(), indent=2))
Refund - Payments API
Refund all or part of a settled transaction. Each refund is a new transaction that you can void or reverse, and you can repeat it up to the allowed amount.
#!/usr/bin/env bash
# Refund against a settled transaction (full or partial, repeatable up to the
# original amount). Only once its batch has CLOSED — see
# snippets/shared/void-or-refund-batch-rule.mdx. Retry the SAME request_id on
# a timeout/5xx.
set -euo pipefail
API_BASE="${RADIUMONE_API_BASE:-https://api-sandbox.radiumone.io/gateway}"
: "${RADIUMONE_ACCESS_TOKEN:?set RADIUMONE_ACCESS_TOKEN to a Bearer access token}"
: "${RADIUMONE_TRANSACTION_ID:?set RADIUMONE_TRANSACTION_ID to the CAPTURED transaction to refund}"
curl -sS -X POST "$API_BASE/v1/transactions/refund/$RADIUMONE_TRANSACTION_ID" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $RADIUMONE_ACCESS_TOKEN" \
-d @request.json
#!/usr/bin/env python3
"""Refund against a settled transaction, only once its batch has CLOSED — see
snippets/shared/void-or-refund-batch-rule.mdx.
"""
import json
import os
import random
import time
from pathlib import Path
import requests
API_BASE = os.environ.get("RADIUMONE_API_BASE", "https://api-sandbox.radiumone.io/gateway")
def backoff_seconds(attempt: int) -> float:
"""Exponential backoff with jitter: attempt 1 waits ~0.25-0.5s, doubling
each attempt, capped at 4s -- avoids hammering the gateway in a loop."""
base = min(0.25 * 2 ** (attempt - 1), 4.0)
return base + random.random() * base
def refund_transaction(max_attempts: int = 3) -> dict:
body = json.loads((Path(__file__).parent / "request.json").read_text())
transaction_id = os.environ["RADIUMONE_TRANSACTION_ID"]
headers = {"Authorization": f"Bearer {os.environ.get('RADIUMONE_ACCESS_TOKEN', '')}"}
for attempt in range(1, max_attempts + 1):
try:
resp = requests.post(
f"{API_BASE}/v1/transactions/refund/{transaction_id}", json=body, headers=headers, timeout=30
)
except requests.exceptions.Timeout:
if attempt == max_attempts:
raise
time.sleep(backoff_seconds(attempt))
continue
if resp.status_code >= 500:
if attempt == max_attempts:
raise RuntimeError(f"server error {resp.status_code} after {attempt} attempts")
time.sleep(backoff_seconds(attempt))
continue
payload = resp.json()
if not resp.ok:
# 409 tx:refund-on-open-batch — the batch is still open; void instead.
code = payload.get("type") or payload.get("code")
raise RuntimeError(f"refund failed: {code} ({resp.status_code})")
return payload
raise RuntimeError("unreachable")
if __name__ == "__main__":
print(json.dumps(refund_transaction(), indent=2))
Authorizations
Bearer access token from POST /v1/auth/token. Treat it as an opaque string — do not depend on its internal encoding, which has changed before and isn't part of the contract.
Path Parameters
UUID of the transaction being refunded against.
Body
Refund a transaction whose settlement batch has closed.
While the batch is still open, void the transaction instead. The refund is created
as its own transaction, so it carries a request_id idempotency key, just like a
sale, rather than an operation_id.
You don't send card details: the refund goes back to the card used on the original transaction.
Refund amount as a {currency, value} money object (≤ original amount − prior refunds). currency must match the transaction being refunded.
Show child attributes
Show child attributes
Merchant idempotency key for the refund transaction.
8 - 64Optional human-readable reason for the refund.
200Response
Successful Response
Standard success envelope. Every successful response has this shape, with the operation's own payload under data.
The operation's result. Its shape is documented per operation; omitted on responses that carry no payload.
Show child attributes
Show child attributes
Optional human-readable note. Omitted from the response when not set, which is the case for every payment operation today. Never parse it.
Correlation ID for this HTTP request, for logs and support. Send your own in the X-Request-Id header (letters, digits and hyphens, up to 36 characters -- other characters are stripped) or the gateway generates one. This is NOT the request_id idempotency key you send in a transaction body; the two are unrelated.
Always ok on a successful (2xx) response. Errors use a different body shape entirely (RFC 9457 problem details), so branch on the HTTP status code, not on this field.