Cancel Payment Flow
Overview
The cancel endpoint (PATCH /v2/payments/{paymentId}/cancel) allows merchants to void or cancel an in-progress or authorized payment. Cancellation triggers a Stripe void or reversal depending on the current payment state.
Cancellation Eligibility
| Payment State | Cancel Allowed? | Stripe Action |
|---|---|---|
AUTHORIZED | ✅ | Void the PaymentIntent |
ACCEPTED | ✅ | Reverse the ACH transaction |
PENDING | ✅ | Cancel before processing |
INITIATED | ✅ | Cancel before processing |
PROCESSING | ❌ | Wait for terminal state |
COMPLETED | ❌ | Use refund (POST /v2/refunds) instead |
FAILED | ❌ | Already terminal |
CANCELLED | ❌ | Already terminal |
Cancellation Reasons
| Value | When to Use |
|---|---|
DUPLICATE | Merchant identified this as a duplicate payment |
FRAUDULENT | Suspected fraudulent activity |
REQUESTED_BY_CUSTOMER | Customer asked to cancel |
ABANDONED | Customer did not complete (e.g., 3DS timeout) |
API Request
curl -X PATCH "https://api-stg.uhg.com/api/financial/commerce/nonprodcheckout/v2/payments/770e8400-e29b-41d4-a716-446655440000/cancel" \
-H "Authorization: Bearer <token>" \
-H "X-Merchant-Id: b955db5e-aef2-47de-bbb9-c80b9cc16e8f" \
-H "X-Upstream-Env: dev" \
-H "Content-Type: application/json" \
-d '{
"paymentCancellationReason": "REQUESTED_BY_CUSTOMER",
"paymentCancellationMessage": "Customer changed their mind before shipment"
}'
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
paymentCancellationReason | string (enum) | Yes | One of: DUPLICATE, FRAUDULENT, REQUESTED_BY_CUSTOMER, ABANDONED |
paymentCancellationMessage | string | No | Free-text context (for audit trail) |
API Response — 202 Accepted
{
"url": "https://api-stg.uhg.com/.../v2/payments/770e8400-e29b-41d4-a716-446655440000",
"data": {
"id": "770e8400-e29b-41d4-a716-446655440000",
"merchantTransactionId": "preauth-20260404-001",
"amount": 25000,
"status": "CANCEL_INITIALIZED",
"paymentAllocations": [
{
"id": "cccc1111-2222-3333-4444-555566667777",
"amount": 25000,
"status": "CANCEL_INITIALIZED"
}
]
}
}
GET — After Cancellation (200 OK)
{
"data": {
"id": "770e8400-e29b-41d4-a716-446655440000",
"amount": 25000,
"status": "CANCELLED",
"paymentAllocations": [
{
"id": "cccc1111-2222-3333-4444-555566667777",
"amount": 25000,
"status": "CANCELED"
}
]
}
}
Webhook — PAYMENT_CANCELLED
{
"name": "PAYMENT_CANCELLED",
"source": "merchant-portal",
"payload": {
"id": "770e8400-e29b-41d4-a716-446655440000",
"merchantTransactionId": "preauth-20260404-001",
"amount": 25000,
"status": "CANCELLED",
"paymentAllocations": [
{
"id": "cccc1111-2222-3333-4444-555566667777",
"amount": 25000,
"status": "CANCELED"
}
]
}
}
Error Scenarios
| Scenario | HTTP Status | Error Title | Detail |
|---|---|---|---|
Payment in COMPLETED state | 422 | UNPROCESSABLE_ENTITY | Payment cannot be cancelled in COMPLETED state |
Payment in FAILED state | 422 | UNPROCESSABLE_ENTITY | Payment already failed |
| Payment not found | 404 | NOT_FOUND | paymentId not found |
Missing paymentCancellationReason | 400 | INVALID_REQUEST | paymentCancellationReason is required |
| Invalid cancellation reason value | 400 | INVALID_REQUEST | Invalid enum value |
| Stripe void failed | 422 | PAYMENT_ERROR | Cancellation processing failed |