Issue a refund
Refund a completed payment attempt — full or partial
Execute curls interactively
To run these API calls directly from your browser, open the Payments API reference and use the built-in Try-it playground. Paste your sk_test_… key in the Auth panel, pick Sandbox, and send without leaving the docs.
Refunds in 505pay are issued against a specific payment attempt (not the intent itself), because one intent can have multiple attempts and only the captured one is refundable. The route mirrors that structure: POST /v1/payments/intents/{id}/attempts/{attemptId}/refunds.
Not all methods support refunds
QRIS Dynamic and QRIS Static do not support programmatic refunds — issue a manual bank transfer to the customer instead. The list of method × refund support is in Payment lifecycle.
Find the completed attempt
curl "https://sandbox.505pay.link/v1/payments/intents/$INTENT_ID/attempts" \
-H "Authorization: Bearer $PAYOPS_SECRET_KEY"
# → [{ "id": "018f...", "status": "captured", "amount_idr": 50000, ... }]Pick the attempt with status: "captured".
Create the refund
# Full refund
curl -X POST \
"https://sandbox.505pay.link/v1/payments/intents/$INTENT_ID/attempts/$ATTEMPT_ID/refunds" \
-H "Authorization: Bearer $PAYOPS_SECRET_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: refund-$INTENT_ID-full" \
-d '{"amount_idr": 50000, "reason": "customer_request"}'
# Partial refund — pass any positive amount_idr ≤ remaining
curl -X POST \
"https://sandbox.505pay.link/v1/payments/intents/$INTENT_ID/attempts/$ATTEMPT_ID/refunds" \
-H "Authorization: Bearer $PAYOPS_SECRET_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: refund-$INTENT_ID-partial-1" \
-d '{"amount_idr": 25000, "reason": "partial_fulfillment"}'const refund = await fetch(
`https://sandbox.505pay.link/v1/payments/intents/${intentId}/attempts/${attemptId}/refunds`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.PAYOPS_SECRET_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': `refund-${intentId}-full`,
},
body: JSON.stringify({ amount_idr: 50_000, reason: 'customer_request' }),
},
).then((r) => r.json());refund = requests.post(
f'https://sandbox.505pay.link/v1/payments/intents/{intent_id}/attempts/{attempt_id}/refunds',
headers={
'Authorization': f"Bearer {os.environ['PAYOPS_SECRET_KEY']}",
'Idempotency-Key': f'refund-{intent_id}-full',
},
json={'amount_idr': 50_000, 'reason': 'customer_request'},
).json()$refund = json_decode($client->post(
"https://sandbox.505pay.link/v1/payments/intents/{$intentId}/attempts/{$attemptId}/refunds",
[
'headers' => [
'Authorization' => 'Bearer ' . getenv('PAYOPS_SECRET_KEY'),
'Idempotency-Key' => "refund-{$intentId}-full",
],
'json' => ['amount_idr' => 50000, 'reason' => 'customer_request'],
]
)->getBody(), true);body, _ := json.Marshal(map[string]any{"amount_idr": 50_000, "reason": "customer_request"})
req, _ := http.NewRequest("POST",
fmt.Sprintf("https://sandbox.505pay.link/v1/payments/intents/%s/attempts/%s/refunds", intentID, attemptID),
bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("PAYOPS_SECRET_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "refund-"+intentID+"-full")
resp, _ := http.DefaultClient.Do(req)Response (202 Accepted): the refund is enqueued; final state arrives via webhook.
{
"id": "018f...",
"intent_id": "018f...",
"attempt_id": "018f...",
"amount_idr": 50000,
"reason": "customer_request",
"status": "pending",
"created_at": "2026-05-17T12:30:00Z"
}Confirm via webhook
Listen for payment.refunded (full refund) or watch the intent status flip to partially_refunded (partial). The webhook payload mirrors the response above with status: "succeeded" and a completed_at timestamp.
{
"event": "payment.refunded",
"intent_id": "018f...",
"refund_id": "018f...",
"amount_idr": 50000,
"remaining_refundable_idr": 0
}Refund reasons
| Reason | Use when |
|---|---|
customer_request | Customer asked for a refund |
duplicate | Payment was charged twice |
fraudulent | Suspected fraudulent transaction |
partial_fulfillment | Only part of the order was delivered |
Related
- Payment lifecycle — full state machine including refund states
- API Reference — create refund
- API Reference — list refunds
Was this page helpful?