Refunds
This guide covers how to handle refunds in ToffeePay, including initiating refunds and handling refund webhooks.
Refund Flow
Create a Refund
Send a server-side request to initiate a refund for a paid session.
POST /pay.v1.RefundService/CreateRefund
Authorization: Bearer <your_api_key>
Content-Type: application/json
{
"payment_id": "pay_xyz789",
"reason": "Customer request"
}
Parameters:
payment_id: The original payment ID to refundreason: Optional reason for the refund
Response
{
"id": "ref_xyz789"
}
Refund Statuses
Refunds can have the following statuses:
pending: Refund created but not yet processedsucceeded: Refund has been successfully processed and funds returnedfailed: Refund failed (e.g., insufficient funds, payment method issues)cancelled: Refund was cancelled before completion
Status Flow:
- Refund starts as
pending - Refund resolves to either
succeeded,failed, orcancelled
Timestamp Fields
Refunds include relevant timestamp fields based on their final status:
created_at: When the refund was initiatedsucceeded_at: When the refund completed successfully (if applicable)failed_at: When the refund failed (if applicable)cancelled_at: When the refund was cancelled (if applicable)
Webhook Events
ToffeePay sends webhooks for the following refund-related events:
Refund Events
refund.created: When a refund is created and processing beginsrefund.succeeded: When a refund has been processed successfullyrefund.failed: When a refund attempt failsrefund.cancelled: When a refund is cancelled
See the Webhooks page for implementation details and signature verification.
Handle Refund Notifications
When a refund is processed, ToffeePay sends a signed webhook to your backend.
Sample Payload:
{
"id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"event": "refund.succeeded",
"timestamp": "2023-06-01T12:10:00Z",
"data": {
"refund_id": "ref_xyz789"
}
}
For webhook signature verification, see the Webhooks page.
Check Refund Status
You can check the status of a refund at any time:
POST /pay.v1.RefundService/GetRefund
Authorization: Bearer <your_api_key>
Content-Type: application/json
{
"id": "ref_xyz789"
}
Response:
{
"id": "ref_xyz789",
"payment_id": "pay_xyz789",
"status": "succeeded",
"reason": "Customer request",
"created_at": "2023-06-01T12:00:00Z",
"succeeded_at": "2023-06-01T12:05:00Z"
}
List Refunds
Retrieve historical refunds for audit and analytics:
POST /pay.v1.RefundService/ListRefunds
Authorization: Bearer <your_api_key>
Content-Type: application/json
{
"payment_id": "pay_xyz789",
"status": "succeeded",
"limit": 50,
"offset": 0
}
Parameters:
payment_id(optional): Filter refunds for a specific paymentstatus(optional): Filter by refund status (pending,succeeded,failed,cancelled)limit(optional): Number of refunds to return (default 50)offset(optional): Number of refunds to skip for pagination
Response:
{
"refunds": [
{
"id": "ref_xyz789",
"payment_id": "pay_xyz789",
"status": "succeeded",
"reason": "Customer request",
"created_at": "2023-06-01T12:00:00Z",
"succeeded_at": "2023-06-01T12:05:00Z"
}
],
"total": 1,
"has_more": false
}
Best Practices
- Idempotency: Use Idempotency-Key header to prevent duplicate refunds
- Revoke Items: When processing a refund webhook, ensure you revoke the purchased items from the user's account
- User Communication: Keep users informed about refund status through your game's UI
- Audit Trail: Log all refund requests and completions for auditing purposes
- Timeout Handling: Refunds may take several business days depending on the payment method
Error Handling
Common refund errors:
payment_not_found: The payment doesn't existalready_refunded: Payment has already been refundedrefund_not_allowed: Payment method doesn't support refundsinsufficient_funds: Merchant account has insufficient funds for the refund