This endpoint requires a valid JWT Bearer token. Accessible via the API gateway at /v1/commerce/*.
Refunds API
Process refunds, voids, handle chargebacks, and manage return policies.
Refund and void operations process real financial transactions. All operations are audit-logged, require manager authorization above configurable thresholds, and cannot be reversed once completed. Ensure refund policies are properly configured before processing.
Overview
| Attribute | Value |
|---|---|
| Base Path | /api/v1/refunds |
| Authentication | Bearer Token |
| Required Roles | staff, finance, restaurant_manager, tenant_admin, platform_admin, system_admin, super_admin |
Refunds
List Refunds
GET /api/v1/refunds
Query Parameters
| Parameter | Type | Description |
|---|---|---|
location_id | uuid | Filter by location |
status | string | pending, completed, failed |
type | string | full, partial, void |
start_date | date | Period start |
end_date | date | Period end |
employee_id | uuid | Filter by processor |
Response
{
"refunds": [
{
"id": "refund_001",
"order_id": "ord_12345",
"order_number": "42",
"type": "partial",
"status": "completed",
"amount": 25.50,
"original_amount": 85.50,
"reason": "item_quality",
"reason_detail": "Steak overcooked",
"items_refunded": [
{
"item_id": "item_001",
"name": "Ribeye Steak",
"quantity": 1,
"amount": 25.50
}
],
"payment_method": {
"type": "card",
"brand": "visa",
"last4": "4242"
},
"processed_by": {
"id": "emp_005",
"name": "Manager Mike"
},
"processed_at": "2026-01-24T19:30:00Z"
}
],
"summary": {
"total_count": 15,
"total_amount": 425.50,
"by_reason": {
"item_quality": 8,
"customer_changed_mind": 4,
"wrong_item": 3
}
}
}
Create Refund
POST /api/v1/refunds
Request Body
{
"order_id": "ord_12345",
"type": "partial",
"items": [
{
"order_item_id": "oi_001",
"quantity": 1,
"amount": 25.50
}
],
"reason": "item_quality",
"reason_detail": "Steak was overcooked, customer complained",
"refund_to": "original_payment",
"notify_customer": true,
"manager_pin": "1234"
}
Response
{
"id": "refund_001",
"order_id": "ord_12345",
"type": "partial",
"status": "completed",
"amount": 25.50,
"refund_breakdown": {
"items": 23.50,
"tax": 2.00,
"tip_adjustment": 0
},
"payment_refund": {
"payment_id": "pay_abc123",
"refund_id": "re_stripe_xyz",
"method": "card",
"status": "succeeded",
"estimated_arrival": "3-5 business days"
},
"receipt_url": "https://...",
"processed_at": "2026-01-24T19:30:00Z"
}
Get Refund
GET /api/v1/refunds/{refund_id}
Response
{
"id": "refund_001",
"order_id": "ord_12345",
"order_number": "42",
"type": "partial",
"status": "completed",
"amount": 25.50,
"original_order": {
"subtotal": 78.50,
"tax": 6.28,
"tip": 15.70,
"total": 100.48
},
"refund_breakdown": {
"items": 23.50,
"tax": 2.00,
"tip_adjustment": 0,
"total": 25.50
},
"items_refunded": [
{
"order_item_id": "oi_001",
"item_id": "item_001",
"name": "Ribeye Steak",
"original_price": 35.00,
"quantity_ordered": 1,
"quantity_refunded": 1,
"refund_amount": 23.50,
"modifiers_refunded": [
{"name": "Medium Rare", "amount": 0}
]
}
],
"reason": "item_quality",
"reason_detail": "Steak was overcooked, customer complained",
"reason_category": "food_quality",
"customer": {
"id": "cust_abc",
"name": "John Smith",
"email": "john@example.com",
"notified": true
},
"payment_details": {
"original_payment_id": "pay_abc123",
"refund_transaction_id": "re_stripe_xyz",
"method": "card",
"brand": "visa",
"last4": "4242",
"status": "succeeded",
"refunded_at": "2026-01-24T19:30:05Z"
},
"processed_by": {
"employee_id": "emp_005",
"name": "Manager Mike",
"role": "shift_manager"
},
"approval": {
"required": true,
"approved_by": "emp_005",
"approval_method": "manager_pin"
},
"audit": {
"created_at": "2026-01-24T19:30:00Z",
"ip_address": "192.168.1.100",
"device": "POS Terminal 1"
},
"receipt_url": "https://..."
}
Full Refund
POST /api/v1/refunds/full
Request Body
{
"order_id": "ord_12345",
"reason": "customer_dissatisfied",
"reason_detail": "Overall experience was poor",
"refund_tip": true,
"manager_pin": "1234",
"notify_customer": true
}
Voids
Void Order
POST /api/v1/refunds/void
Request Body
{
"order_id": "ord_12346",
"reason": "duplicate_order",
"reason_detail": "Order was entered twice by mistake",
"manager_pin": "1234"
}
Response
{
"id": "void_001",
"order_id": "ord_12346",
"type": "void",
"status": "completed",
"amount": 45.50,
"payment_void": {
"payment_id": "pay_def456",
"status": "voided",
"note": "Payment authorization released"
},
"voided_at": "2026-01-24T19:35:00Z"
}
Void Item
POST /api/v1/refunds/void-item
Request Body
{
"order_id": "ord_12345",
"order_item_id": "oi_002",
"reason": "wrong_item",
"reason_detail": "Server entered wrong item",
"manager_pin": "1234"
}
Chargebacks
Chargebacks have strict response deadlines imposed by card networks. Failing to respond before the deadline date will result in an automatic loss. Monitor the deadline field and submit evidence promptly.
List Chargebacks
GET /api/v1/refunds/chargebacks
Query Parameters
| Parameter | Type | Description |
|---|---|---|
status | string | open, won, lost, pending |
start_date | date | Period start |
Response
{
"chargebacks": [
{
"id": "chbk_001",
"order_id": "ord_11111",
"amount": 125.50,
"reason_code": "fraudulent",
"reason": "Cardholder claims transaction not authorized",
"status": "open",
"deadline": "2026-02-15",
"payment_details": {
"payment_id": "pay_xyz789",
"brand": "visa",
"last4": "1234"
},
"evidence_submitted": false,
"opened_at": "2026-01-20T00:00:00Z"
}
],
"summary": {
"open": 2,
"pending": 1,
"won_ytd": 5,
"lost_ytd": 2,
"total_disputed_ytd": 2500.00
}
}
Get Chargeback
GET /api/v1/refunds/chargebacks/{chargeback_id}
Response
{
"id": "chbk_001",
"order_id": "ord_11111",
"amount": 125.50,
"currency": "USD",
"reason_code": "fraudulent",
"reason": "Cardholder claims transaction not authorized",
"status": "open",
"deadline": "2026-02-15T23:59:59Z",
"order_details": {
"order_number": "125",
"date": "2026-01-15",
"items": [
{"name": "Ribeye Steak", "quantity": 2, "price": 70.00},
{"name": "Wine Bottle", "price": 45.00}
],
"subtotal": 115.00,
"tax": 10.50,
"total": 125.50
},
"customer": {
"name": "Jane Doe",
"email": "jane@example.com"
},
"payment_details": {
"payment_id": "pay_xyz789",
"brand": "visa",
"last4": "1234",
"cardholder_name": "JANE DOE"
},
"evidence": {
"submitted": false,
"available_documents": [
"signed_receipt",
"customer_communication",
"delivery_confirmation"
]
},
"timeline": [
{
"event": "chargeback_opened",
"timestamp": "2026-01-20T00:00:00Z",
"details": "Chargeback received from card network"
}
],
"recommendation": {
"action": "submit_evidence",
"reason": "Order has signed receipt and matches cardholder name",
"win_likelihood": "high"
}
}
Submit Chargeback Evidence
POST /api/v1/refunds/chargebacks/{chargeback_id}/evidence
Request Body
{
"evidence_type": "dispute_response",
"documents": [
{
"type": "signed_receipt",
"file_id": "file_001"
},
{
"type": "customer_signature",
"file_id": "file_002"
}
],
"statement": "Customer dined in and signed receipt. Transaction is legitimate.",
"additional_info": {
"customer_ip": "192.168.1.50",
"service_date": "2026-01-15",
"server_name": "Sarah Johnson"
}
}
Accept Chargeback
POST /api/v1/refunds/chargebacks/{chargeback_id}/accept
Request Body
{
"reason": "Unable to provide sufficient evidence"
}
Refund Policies
Get Refund Policies
GET /api/v1/refunds/policies
Response
{
"policies": [
{
"id": "policy_001",
"name": "Standard Refund Policy",
"type": "default",
"rules": {
"max_refund_age_hours": 24,
"require_manager_approval": {
"always": false,
"threshold_amount": 50.00,
"threshold_percent": 50
},
"allowed_reasons": [
"item_quality",
"wrong_item",
"customer_changed_mind",
"service_issue",
"other"
],
"tip_refund": {
"allowed": true,
"auto_adjust": true
},
"partial_refunds": {
"allowed": true,
"min_amount": 1.00
}
},
"restrictions": {
"no_refund_items": ["gift_cards", "alcohol"],
"final_sale_categories": []
},
"active": true
}
]
}
Update Refund Policy
PUT /api/v1/refunds/policies/{policy_id}
Reports
Get Refund Report
GET /api/v1/refunds/reports
Query Parameters
| Parameter | Type | Description |
|---|---|---|
location_id | uuid | Filter by location |
start_date | date | Period start |
end_date | date | Period end |
group_by | string | day, reason, employee |
Response
{
"period": {
"start": "2026-01-01",
"end": "2026-01-24"
},
"summary": {
"total_refunds": 85,
"total_voids": 12,
"total_amount": 2450.00,
"refund_rate": 0.015,
"avg_refund_amount": 28.82
},
"by_reason": [
{
"reason": "item_quality",
"count": 35,
"amount": 980.00,
"percent": 40
},
{
"reason": "wrong_item",
"count": 25,
"amount": 720.00,
"percent": 29.4
},
{
"reason": "customer_changed_mind",
"count": 15,
"amount": 450.00,
"percent": 18.4
},
{
"reason": "service_issue",
"count": 10,
"amount": 300.00,
"percent": 12.2
}
],
"by_employee": [
{
"employee_id": "emp_001",
"name": "Sarah Johnson",
"refunds_processed": 12,
"amount": 340.00
}
],
"trends": {
"vs_last_period": "+5%",
"highest_day": "2026-01-15",
"highest_day_amount": 185.00
},
"action_items": [
{
"type": "high_refund_item",
"item": "Ribeye Steak",
"refund_count": 8,
"recommendation": "Review preparation consistency"
}
]
}
Webhooks
| Event | Description |
|---|---|
refund.created | New refund processed |
refund.completed | Refund completed |
refund.failed | Refund failed |
void.created | Order voided |
chargeback.opened | New chargeback received |
chargeback.resolved | Chargeback resolved |
Error Responses
| Status | Code | Description |
|---|---|---|
| 400 | refund_exceeds_amount | Refund exceeds available amount |
| 400 | order_already_refunded | Order already fully refunded |
| 403 | manager_approval_required | Manager approval needed |
| 404 | order_not_found | Order not found |
| 409 | payment_not_refundable | Payment cannot be refunded |
| 422 | refund_window_expired | Outside refund window |
Related Documentation
- Orders API - Order management
- Payments API - Payment processing
- Audit Logs API - Refund audit trail