Split Tender Refunds API
Split Tender Refund allows merchants to refund a split tender transaction β either in full across both cards, or partially against a specific card. Each card refund is processed independently. This guide covers how to initiate refunds, monitor status, and handle errors and webhook notifications.
Single-card refunds? If the original payment used only one allocation, standard refund processing applies β see Refunds. No
PARTIAL_SUCCESSor per-allocation logic is involved.
- Refund model: Full Refund and Per-payment allocation β each allocation is refunded independently
- Partial refunds: Supported per card; merchant must specify card and amount
- No rollback on partial failure: Unlike payments, if one card refund fails, the other is not reversed β result is
PARTIAL_SUCCESS - Status: Rolled up from both card refund statuses
When to Useβ
Use Split Tender Refund when:
- The original payment was a split tender transaction (two cards charged)
- A full refund is needed β both cards should be refunded for their original amounts
- A partial refund is needed β a specific card should be refunded for a specific amount
- A subsequent refund is needed after a previous partial refund on the same transaction
Note: Each refund allocation is processed independently. If one card fails (e.g., expired or disputed), the other card's refund still proceeds, resulting in
PARTIAL_SUCCESS. Unlike payments, there is no automatic rollback.
For business context, eligibility, scenarios see Split Tender Refunds β Business Document.
- API Endpoint:
POST v2/refundsβ create a refundGET v2/refunds/{refundId}β poll refund status
- Scope: All operations are at the
merchantscope. - API Specs: Create Refund | Get Refund
- Webhook Specs: Refund Webhooks
- API:
v2 - Testing: Use Test Cards to simulate refund scenarios. See Test Cards
- β οΈ Error Handling: See Refund Error Codes for split tenderβspecific errors and resolution steps
Refund Processing Flowβ
Both cards are refunded independently. If one card fails, the other still proceeds, resulting in a PARTIAL_SUCCESS.
Refund Flow
Refund Processing Steps:
- Request received β CCG receives split tender refund request.
- Eligibility check β CCG validates refund eligibility for each card.
- Independent processing β Each card refund is processed separately (in parallel).
- Outcome determination:
- Both succeed β
REFUND_SUCCESSwebhook (status:COMPLETED) - One fails β
REFUND_PARTIAL_SUCCESSwebhook (status:PARTIAL_SUCCESS) - Both fail β
REFUND_FAILEDwebhook (status:FAILED)
- Both succeed β
- Webhook β Single event emitted at parent refund level, not per card.
- Manual action (if needed) β For
PARTIAL_SUCCESS, merchant may need to resolve failed allocation manually.
API Requestβ
Sample Request β POST v2/refunds
curl -X POST "https://api-stg.uhg.com/api/financial/commerce/checkout/v2/refunds" \
-H "Authorization: <token>" \
-H "X-Merchant-Id: <merchantId>" \
-H "Content-Type: application/json" \
-d '{
"reason": "REQUESTED_BY_CUSTOMER",
"paymentId": "620bba81-10d1-4abc-9e4a-c2c1752da349",
"merchantTransactionId": "28402815-8048-4725-b092-809f5fd28470",
"refundAllocations": [
{
"paymentAllocationId": "1ff243ca-e0f3-436f-a5b2-53c205715619",
"amount": 1000
},
{
"paymentAllocationId": "37cf8ca3-4e54-4599-ab36-2c9fd2ad42f4",
"amount": 100
}
]
}'
Poll Refund Statusβ
Refund processing is asynchronous β the API returns INITIATED immediately. Poll GET v2/refunds/{refundId} until the status settles to a terminal state (COMPLETED, PARTIAL_SUCCESS, or FAILED).
Sample Request β GET v2/refunds/refundId
curl -X GET "https://api-stg.uhg.com/api/financial/commerce/checkout/v2/refunds/<refundId>" \
-H "Authorization: <token>" \
-H "X-Merchant-Id: <merchantId>" \
-H "Accept: application/json"
API Responseβ
The complex object is trimmed for brevity. Refer to the API Spec for the full response structure.
- The
refundAllocationsarray contains one entry per card, each with its own status and error details if failed. - The
paymentMethodobject in each allocation is trimmed for brevity (onlyid,type,last4, andcardBrandshown). - The parent refund status is computed from both allocations (see Status Handling).
- For
PARTIAL_SUCCESS, check each allocation for error details and take manual action if needed. - Top-level
statusis always the parent refund status; allocation statuses may differ. - For all error codes and troubleshooting, see Refund Error Codes.
Final Response β COMPLETEDβ
Sample Response β COMPLETED (HTTP 200)
{
"url": "http://api-stg.uhg.com:443/v2/refunds/be7149e8-f286-4c7d-a0ea-a715966965ae",
"data": {
"id": "be7149e8-f286-4c7d-a0ea-a715966965ae",
"status": "COMPLETED",
"reason": "DUPLICATE",
"merchantTransactionId": "e4a42f8b-c668-4fb8-ae09-47c80e0f3cd5",
"metadata": {},
"payment": {
"amount": 1000,
"capturedAmount": 1000,
"authorizedAmount": 0,
"description": "PartialSplitRefundSuccess",
"id": "4266d148-7406-441e-a523-9a2ac81b39d5",
"merchantTransactionId": "9104ea96-5afb-4ea9-a40a-09661afdd706",
"paymentDateUtc": "2026-03-20T00:28:05"
},
"merchant": {
"id": "b955db5e-aef2-47de-bbb9-c80b9cc16e8f"
},
"refundAllocations": [
{
"id": "6f90c43d-7904-4744-9741-290a93b9bb95",
"amount": 150,
"status": "COMPLETED",
"paymentAllocation": {
"id": "ae59cc49-56cc-436c-a99f-4520c82d6263",
"paymentMethod": {
// refer complete payment method in API Spec
}
}
},
{
"id": "f1e46dee-65b2-4d54-a42b-75f7125dbd1b",
"amount": 100,
"status": "COMPLETED",
"paymentAllocation": {
"id": "f7b29404-497c-4447-910b-be25c8eb19e1",
"paymentMethod": {
// refer complete payment method in API Spec
}
}
}
]
}
}
Final Response β PARTIAL_SUCCESSβ
Sample Response β PARTIAL_SUCCESS (HTTP 207)
{
"url": "http://api-stg.uhg.com:443/v2/refunds/6ddd4069-2096-48f1-8394-1fe13953aa5f",
"data": {
"id": "6ddd4069-2096-48f1-8394-1fe13953aa5f",
"status": "PARTIAL_SUCCESS",
"reason": "REQUESTED_BY_CUSTOMER",
"merchantTransactionId": "28402815-8048-4725-b092-809f5fd28470",
"metadata": {},
"payment": {
"amount": 2000,
"capturedAmount": 2000,
"authorizedAmount": 0,
"description": "PartialSplitRefundSuccess",
"id": "620bba81-10d1-4abc-9e4a-c2c1752da349",
"merchantTransactionId": "3c5c2899-fe71-44d8-be9b-bd520e5e6539",
"paymentDateUtc": "2026-03-20T00:27:36"
},
"merchant": {
"id": "b955db5e-aef2-47de-bbb9-c80b9cc16e8f"
},
"refundAllocations": [
{
"id": "f56b5fb8-578b-4ea2-bcf2-66bb4350a6aa",
"amount": 100,
"status": "COMPLETED",
"paymentAllocation": {
"id": "37cf8ca3-4e54-4599-ab36-2c9fd2ad42f4",
"paymentMethod": {
// refer complete payment method in API Spec
}
}
},
{
"id": "b1cc58d4-0e0c-4388-a92f-1869fccb7667",
"amount": 1000,
"status": "FAILED",
"error": {
"title": "REFUND_ERROR",
"detail": "A previous attempt to refund this payment has failed"
},
"paymentAllocation": {
"id": "1ff243ca-e0f3-436f-a5b2-53c205715619",
"paymentMethod": {
// refer complete payment method in API Spec
}
}
}
]
}
}
Final Response β FAILED (HTTP 422)β
Sample Response β FAILED (HTTP 422)
{
"title": "REFUND_ERROR",
"detail": "Refund allocation processing failed for all records. Check individual records for error details",
"status": 422,
"refund": {
"id": "2aa92c90-3d54-4fb1-8655-19b18eb2e13c",
"status": "FAILED",
"reason": "DUPLICATE",
"merchantTransactionId": "bbc12922-9ebb-4647-869a-63a29e90ddfa",
"metadata": {
"orderId": "123496",
"purpose": "failedRefund"
},
"payment": {
"amount": 1000,
"capturedAmount": 1000,
"authorizedAmount": 0,
"description": "PartialSplitRefundSuccess",
"id": "4266d148-7406-441e-a523-9a2ac81b39d5",
"merchantTransactionId": "9104ea96-5afb-4ea9-a40a-09661afdd706",
"paymentDateUtc": "2026-03-20T00:28:05"
},
"merchant": {
"id": "b955db5e-aef2-47de-bbb9-c80b9cc16e8f"
},
"refundAllocations": [
{
"id": "3d62cf0c-89ea-424d-a637-aa887c5b9fab",
"amount": 2,
"status": "FAILED",
"error": {
"title": "REFUND_ERROR",
"detail": "This payment is already refunded"
},
"paymentAllocation": {
"id": "ae59cc49-56cc-436c-a99f-4520c82d6263",
"paymentMethod": {
// refer complete payment method in API Spec
}
}
},
{
"id": "b7316cf6-8c00-4d30-bcca-a5874e114efe",
"amount": 1,
"status": "FAILED",
"error": {
"title": "REFUND_ERROR",
"detail": "This payment is already refunded"
},
"paymentAllocation": {
"id": "f7b29404-497c-4447-910b-be25c8eb19e1",
"paymentMethod": {
// refer complete payment method in API Spec
}
}
}
]
}
}
Status Handlingβ
Polling Guidelinesβ
Refund processing is asynchronous β the API returns INITIATED immediately. Poll GET v2/refunds/{refundId} until the status settles to a terminal state (COMPLETED, PARTIAL_SUCCESS, or FAILED).
- β
Supported:
- Poll the parent refund using
refundIdonly. - Get real-time status for all allocations in a single response.
- See per-card error details when a card refund fails.
- Poll the parent refund using
- β Not Supported:
- Polling by
merchantTransactionId(not available). - Polling individual allocation (child) refund IDs.
- Component-level webhook subscriptions per card.
- Polling by
Always use the parent refund refundId for polling. Polling by merchantTransactionId or allocation IDs is not supported and will result in API errors.
Status Definitionsβ
Both the parent refund and each allocation (card) carry their own status. The parent status is computed from both allocation outcomes.
Refund Status Definitions
| Level | Status | Description |
|---|---|---|
| Parent Refund | INITIATED | Refund request accepted; async validation and submission started |
PENDING | At least one allocation is being processed by the payment processor | |
COMPLETED | All allocations succeeded; total refund amount returned | |
FAILED | All allocations failed; no funds returned | |
PARTIAL_SUCCESS | One allocation succeeded, one failed β no rollback | |
| Allocation (R1/R2) | INITIATED | Allocation created; async validation and submission to processor started |
PENDING | Processor is actively processing this allocation | |
COMPLETED | Allocation processed successfully; funds returned to card | |
FAILED | Allocation failed; no funds returned for this card |
Note:
PENDINGis optional β allocations may transition directly fromINITIATED β COMPLETEDorINITIATED β FAILED.
Status Mapping: R1 + R2 β Parentβ
| R1 (Card 1) | R2 (Card 2) | Parent Status | Description |
|---|---|---|---|
| β³ INITIATED | β³ INITIATED | β³ INITIATED | Both allocations queued for processing |
| β³ PENDING | β³ PENDING | β³ PENDING | Both allocations actively processing |
| β COMPLETED | β COMPLETED | β COMPLETED | All funds returned to both cards |
| β COMPLETED | β FAILED | β οΈ PARTIAL_SUCCESS | One allocation refunded; one failed β no rollback |
| β FAILED | β COMPLETED | β οΈ PARTIAL_SUCCESS | One allocation refunded; one failed β no rollback |
| β FAILED | β FAILED | β FAILED | No funds returned |
What is PARTIAL_SUCCESS?β
When one card refund succeeds and the other fails (e.g., expired, closed, or disputed card, or transient processor error), the parent status is PARTIAL_SUCCESS. The successful refund is not rolled back. The failed allocation includes an error object with details. Review the refundAllocations array, communicate with the customer if needed, and issue a manual/unlinked refund for the failed card if required. See Partial Success Refund Response. Refer Recovery Steps for handling.
Status Lifecycleβ
Allocation Lifecycleβ
Each refund allocation independently moves through these states:
Overall Refund Lifecycleβ
The parent status is derived from both allocation outcomes once processing completes:
Error Handlingβ
For the complete list of error codes, status codes, and error details, see Refund Error Codes.
Split TenderβSpecific Errorsβ
Beyond standard refund errors, split tender refunds have these specific failure modes:
- Partial failure β no rollback: Unlike payments, if one card refund fails, the other card's refund is not rolled back. The result is
PARTIAL_SUCCESSand the merchant must resolve the failed allocation separately. - Already refunded: If a
paymentAllocationIdhas already been fully refunded, the allocation returnsREFUND_ERROR: This payment is already refunded. - Expired or closed card: Refunds to expired or closed cards fail at the processor level β the other card's refund still proceeds, resulting in
PARTIAL_SUCCESS.
Webhooksβ
CCG sends one webhook per refund request β not per card. Subscribe to these events to get real-time refund outcomes without polling.
Webhook Eventsβ
- onRefundSuccess β full payload schema for success events
- onRefundFailed β full payload schema for failure events
| Event | Trigger | Notes |
|---|---|---|
REFUND_SUCCESS | Both cards refunded successfully | Refund fully settled |
REFUND_PARTIAL_SUCCESS | One payment refunded, one failed | Check per-refund allocation status |
REFUND_FAILED | All cards failed to refund | No funds returned |
Webhook Status Mapping (Internal)β
| Card 1 | Card 2 | Refund Status | Webhook Event |
|---|---|---|---|
| COMPLETED | COMPLETED | COMPLETED | REFUND_SUCCESS |
| COMPLETED | FAILED | PARTIAL_SUCCESS | REFUND_PARTIAL_SUCCESS |
| FAILED | COMPLETED | PARTIAL_SUCCESS | REFUND_PARTIAL_SUCCESS |
| FAILED | FAILED | FAILED | REFUND_FAILED |
Stripe(Payment Processor) Considerationsβ
- Timing: Card refunds typically process in 1β3 business days; each card may complete at different times depending on issuer
- Stripe allows refunds on payments for 180 days
Retry Handlingβ
Split tender refunds do not support retries in the same way as payments. Each refund request is a distinct operation:
- Full refund: Submit once; cannot re-submit if both cards are fully refunded.
- Partial failure (
PARTIAL_SUCCESS): The failed refund allocation is not retried automatically. The merchant must handle this manually:- Submit a new individual refund request for only the failed payment using its
paymentAllocationId. - Consider issuing an unlinked/manual refund for the failed card if the card is expired, closed, or disputed.
- Submit a new individual refund request for only the failed payment using its
- Already refunded: If a
paymentAllocationIdhas already been fully refunded, it cannot be refunded again β submitting will result inREFUND_ERROR: This payment is already refunded.
Recovery Steps for PARTIAL_SUCCESSβ
| Situation | Action |
|---|---|
| Card expired/closed | Issue unlinked refund or manual credit outside CCG |
| Card disputed | Refund the undisputed card only; contact support for disputed card |
| Processor error (transient) | Retry only the failed allocation with a new merchantTransactionId |
| Already refunded | Adjust the refund amount to the remaining balance |
V1 vs V2 API Comparisonβ
Split Tender Refund is a new capability in v2 that extends the single-payment refund model. This section helps to understand the differences and migrate from v1 to v2.
Downloadable Field-by-Field Comparisonβ
A comprehensive comparison workbook is available for detailed field mapping:
What's included:
| Tab | Coverage |
|---|---|
| Request | POST /refunds (v1) vs POST /v2/refunds (v2) β all request fields |
| Response (Success) | Success responses with all mandatory and optional fields |
| Response (Error) | Error responses by HTTP status: 400, 401, 403, 404, 422, 500 |
| Webhook (Success) | REFUND_PENDING, REFUND_SUCCESS, REFUND_PARTIAL_SUCCESS events |
| Webhook (Error) | REFUND_FAILED event payload |
How to read the workbook
Each tab displays v1 JSON on the left and v2 JSON on the right. Field values use type labels (string, int, boolean, null, array, object) rather than actual data.
| Badge | Meaning |
|---|---|
π΄ <REMOVED> | Field exists in v1 but not in v2 |
π’ <ADDED> | Field exists in v2 but not in v1 |
| No badge | Field exists in both versions |
What to look for:
- π΄ Removed fields β Stop sending these in v2 requests
- π’ Added fields β New split tender capabilities (e.g.,
refundAllocationsarray) - Nested structure changes β v2 may nest or flatten fields differently
- Type changes β Field may exist in both but with different type or format
Version-Specific Documentationβ
FAQβ
Q: How do I refund only one card in a split tender transaction?
- Specify the
paymentAllocationIdof the card to refund along with theamountinrefundAllocations. Omit the other allocation.
Q: What happens if one card refund fails?
- The other card's refund still proceeds. The result is
PARTIAL_SUCCESSβ unlike payments, there is no rollback. Handle the failed allocation manually.
Q: Can I refund the full amount without specifying allocations?
- Yes. Omit
refundAllocationsentirely for a full refund. CCG will refund both cards for their original amounts.
Q: Can I poll individual allocation status?
- No. Always use the parent
refundIdormerchantTransactionIdto poll. Individual allocation IDs are not queryable directly.
Q: When is a refund status final?
COMPLETED,PARTIAL_SUCCESS, andFAILEDare all terminal states.INITIATEDandPENDINGare transient.
Q: How do I correlate webhook events with refund records?
- Use the
refundIdormerchantTransactionIdin both the API response and webhook payload to match events.
Q: What if I don't receive a webhook?
- Poll
GET v2/refunds/{refundId}to get the current status. Check your endpoint logs and ensure it returns a2xxstatus. See Webhook Configuration.
Integration Checklistβ
- Merchant account is configured for split tender payments
- API endpoints and authentication are set up
- Refund request specifies correct
paymentIdandmerchantTransactionId - For partial refunds:
refundAllocationsincludespaymentAllocationIdandamount - Status polling uses parent
refundIdβ not allocation IDs -
PARTIAL_SUCCESShandling is implemented (manual resolution for failed card) - Webhook endpoint is configured and returns
2xx - Webhook handler is idempotent and logs all payloads
- All error codes and
PARTIAL_SUCCESSscenarios are handled - Integration tested with test cards and simulated partial failures