Skip to main content
Version: v2

High-Level Design: Split Tender Refunds

FieldDetail
Author(s)ranga_chenna@optum.com
ReviewersBabuSa@Optum.com,syamala01@optum.com
StatusFinal
Last Updated2026-02-19
Version1.0
Disclaimer

This is a high-level design document intended for planning and alignment purposes. All numbers, statuses, thresholds, and timelines mentioned are illustrative and subject to change during implementation. This document is not a specification or implementation contract.


1. Overview

Split Tender allows a customer to pay for a single order using exactly two payment methods (e.g., two cards, a card and a bank account). This design assumes a maximum of two payment methods per transaction — support for more than two is out of scope. When a refund is needed on such a transaction, the system must determine how to distribute the refund across the original payment methods and handle scenarios where one or both refund legs fail.

This design covers how the CCG (Convenient Checkout Gateway) platform orchestrates refunds for split tender transactions via the v2/refunds API — including full refunds, partial refunds, multiple refunds, and failure handling.

Who should read this?
  • Product — Understand refund scenarios, outcomes, and customer impact. Focus on: clear refund outcomes; merchants can resolve partial failures; no money "stuck".
  • Engineering — Understand the orchestration flow, refund eligibility checks, error handling, Stripe integration, and status mapping.
  • QA — Understand testable scenarios, edge cases (expired cards, zero balance, time limits), and expected statuses.
  • API Consumers (Merchants) — Understand the v2/refunds contract: what to send, what to expect back, predictable statuses, and clear error codes.
  • Finance — Accurate refund accounting, no over-refunds, and an audit trail per payment method.

2. Problem Statement

  • A split tender payment involves two separate charges on two separate payment methods. Refunding such a transaction is not a single operation — it requires coordinated refunds across both methods.
  • If one refund succeeds and the other fails (e.g., card expired, account closed), the merchant needs a clear status and a path to resolve the remaining amount.
  • Without this design, refund handling for split tender would be manual, error-prone, and inconsistent across merchants.

3. Scope & Out of Scope

In Scope

#Item
1Support full refunds (return all money across both payment methods)
2Support partial refunds (return a specific amount from one or both methods)
3Support multiple refunds (refund in stages)
4Return clear statuses: REFUNDED, PARTIAL_REFUND, REFUND_FAILED, or PENDING

Out of Scope

#ItemReason
1Unlinked refunds / credits for split tenderOut of scope for initial release — merchants can use the existing standard v1/refunds unlinked refund flow to resolve failed legs; native support may be considered in a future iteration
2Automatic retry of failed refund legsMerchant decides next steps on partial failures; automatic retry logic may be evaluated in a future phase
3UI widget changes for refund initiationRefunds are API-only operations for merchants in this release
4Chargeback / dispute resolutionHandled by a separate disputes process
5End-customer notificationCCG notifies the merchant via webhooks; how the merchant communicates with their customer is outside CCG scope
6Auto-skip zero-balance methodsPlanned for a future phase once the Balance Tracker is implemented; in the initial release, Stripe is queried directly
7Pre-processing eligibility validation (status, amounts, time limits)Basic validation is performed, but comprehensive pre-flight checks (e.g., local balance verification) depend on the Balance Tracker

4. Current State (Before Split Tender Refunds)

Split tender payments already use the v2/payments APIs. However, refunds for split tender transactions are not yet supported at the platform level. Today, standard (single-method) refunds work as follows:

Limitations with split tender:

  • The v1/refunds API handles one payment at a time — it has no concept of a parent transaction with two child payments.
  • There is no balance tracking across multiple payment methods within a single order.
  • If a merchant manually refunds each leg separately, there is no coordinated status or rollback.
  • The v1 refund API cannot determine the correct final status across both legs of a split tender payment.

5. Proposed Design (After)

The v2/refunds API introduces split-tender-aware refund orchestration:

Key Components

ComponentResponsibility
CCG Refund API (v2)Receives refund request, validates, orchestrates refunds across payment methods
Refund Eligibility CheckIn the initial release, CCG issues the refund request directly to Stripe, and Stripe performs its own validation — failing the request if eligibility checks (e.g., insufficient balance, expired refund window) are not met. A dedicated internal Balance Tracker is planned as a future enhancement to enable local pre-validation before reaching Stripe.
Payment Processor (Stripe)Executes the actual refund on each payment method
Webhook ServiceSends refund status updates to the merchant at the parent transaction level

6. Data Flow

The step-by-step flow for a split tender refund:

Parallel Processing

Split tender refunds are always triggered in parallel — both payment methods are refunded at the same time. CCG waits for both results before determining the final status.

Processing Order

Payment CombinationProcessing ModelReason
Card + CardParallelBoth refunds triggered simultaneously
ACH + ACHParallelBoth refunds triggered simultaneously
Card + ACHParallelBoth refunds triggered simultaneously

7. Refund Scenarios Key Benefits

caution

The statuses and scenario outcomes below are proposed and subject to change during implementation.

#ScenarioWhat happensFinal Status
1Full refund — both succeedBoth payment methods refunded in fullREFUNDED
2Full refund — one failsOne succeeds, one fails (expired card, closed account, etc.)PARTIAL_REFUND
3Full refund — both failBoth refund legs fail (e.g., both accounts closed)REFUND_FAILED
4Partial refund — amounts specifiedMerchant specifies amount per payment method; both succeedREFUNDED
5Partial refund — one method onlyMerchant specifies an amount for only one of the two methods; that single refund succeedsREFUNDED
6Multiple refunds (happy path)Merchant issues refunds over time; both methods still have remaining balance on each request; all legs succeedREFUNDED
7Refund exceeds available balanceRequested amount is more than what remains; Stripe rejects the requestREFUND_FAILED
8Refund below minimum amountRequested amount is less than the minimum ($0.01); Stripe rejects the requestREFUND_FAILED
9Refund on disputed paymentOne payment is under dispute; refund not possible on itPARTIAL_REFUND
10Subsequent refund — zero balance on one methodOne method already fully refunded. In the initial release, CCG does not track balances locally, so it forwards both legs to Stripe. Stripe rejects the zero-balance leg; the other leg is processed normally.PARTIAL_REFUND
11Refund after 180 daysTime limit exceeded; Stripe rejects the requestREFUND_FAILED
12Concurrent refund with same merchantTransactionIdA refund is already in progress for this merchantTransactionId; the second request is rejected by CCGREFUND_FAILED
13Held refund (insufficient Stripe balance)Stripe does not have enough available balance to process the refund immediately; refund is queued until balance is available. PENDING may eventually transition to REFUNDED (once balance is available) or remain held until resolved. Merchants should monitor for the final webhook.PENDING (until resolved)
14Stripe unavailable or timeoutStripe is unreachable or does not respond within the expected time; CCG treats the leg as failedPARTIAL_REFUND or REFUND_FAILED

8. Validation Rules Summary

caution

Specific values (e.g., 180-day window, $0.01 minimum) are indicative and subject to change based on business and processor requirements.

Before processing any split tender refund, CCG validates:

RuleCheck
Unique Transaction IDmerchantTransactionId must not be a duplicate; rejected if a refund with the same ID already exists
Parent Transaction ExistsThe parent transaction ID must exist in the system
Payment StatusParent transaction must be in COMPLETED status before a refund can be initiated
Amount ConstraintsRefund amount must not exceed the remaining balance; minimum refund is $0.01 per method
Time LimitRefund must be requested within 180 days of the original transaction; requests beyond this window are rejected by Stripe
Already Fully RefundedA payment method with zero remaining balance cannot be refunded again; in the initial release, this is enforced by Stripe (which rejects the request), not by CCG locally
Payment Method ValidChild payment IDs provided in the request must belong to the specified parent transaction
Partial Refund AllocationWhen per-method amounts are specified, each amount must reference a child payment that belongs to the parent transaction; amounts cannot be assigned to unrelated payments
No Concurrent RefundIf a refund is already in progress for the same parent transaction, the new request is rejected to prevent over-refunds

9. Key Decisions

#DecisionReasoning
1Partial success IS accepted for refunds — one leg can succeed while the other failsA partial refund is better than no refund; merchant can resolve the rest manually
2Initial release uses Stripe as source of truth for refundable amountsReduces initial complexity; a dedicated internal Balance Tracker is planned as a future enhancement to improve performance and enable local validation
3Webhooks fire at parent level onlySimplifies merchant integration; one event per refund, not two
4Merchants must specify per-payment amounts for partial refundsSystem does not auto-distribute — avoids ambiguity in refund allocation
5Parallel processing for all refund combinationsBoth payment method refunds are triggered simultaneously; CCG waits for both results before determining the final status
6Concurrent refund requests with the same merchantTransactionId are rejectedIf a refund request is already in progress for the same merchantTransactionId, subsequent requests with that ID are rejected
7v1/refunds remains available for individual payment refundsMerchants can still use v1/refunds for refunds

10. Future Enhancement: Balance Tracker

🔮 Balance Tracker — Planned for a Future Release (click to expand)

Note: The Balance Tracker is planned as a future improvement and is not part of the initial release of split tender refunds. In the initial release, refund eligibility and remaining amounts are determined by the payment processor (Stripe) directly.

What is the Balance Tracker?

The Balance Tracker is a capability within CCG that will allow the platform to know the remaining refundable amount per child payment locally — without relying on Stripe for every request. The specific implementation approach (e.g., maintaining a running balance in the database, or computing it on the fly from completed refund records) will be determined during the implementation phase.

Once available, it will enable:

  • Faster validation — check refund eligibility locally without relying on Stripe to fail the request; reject ineligible refunds upfront with clear error messages
  • Accurate multi-refund tracking — know the remaining refundable amount across multiple partial refunds
  • Over-refund prevention — reject requests that exceed the remaining balance at the platform level, before reaching Stripe
  • Automatic zero-balance skipping — detect which payment methods are fully refunded and skip them without sending a request to Stripe

Worked Example: Partial Refund → Complete Refund

Setup: A customer pays $100.00 for an order using two payment methods:

  • Card (Child Payment 1): $60.00
  • ACH (Child Payment 2): $40.00

Step 1 — Initial State (after payment)

Child PaymentMethodChargedRefunded So FarRemaining Balance
Payment 1Card$60.00$0.00$60.00
Payment 2ACH$40.00$0.00$40.00
Total$100.00$0.00$100.00

Step 2 — Partial Refund ($25 from Card)

The merchant issues a partial refund of $25.00 from the Card only.

  • CCG determines Card has $60.00 remaining → eligible
  • Refund of $25.00 is sent to Stripe → succeeds
  • Remaining balance for Card is now $35.00
Child PaymentMethodChargedRefunded So FarRemaining Balance
Payment 1Card$60.00$25.00$35.00
Payment 2ACH$40.00$0.00$40.00
Total$100.00$25.00$75.00

Result: REFUNDED — the $25.00 partial refund on the Card succeeded.

Step 3 — Complete Refund (refund all remaining)

The merchant now requests a full refund for the remaining balance. CCG determines $35.00 (Card) + $40.00 (ACH) = $75.00 remaining.

Both refunds are triggered in parallel:

  • Card: Refund $35.00 → succeeds
  • ACH: Refund $40.00 → succeeds

Both methods now have $0.00 remaining.

Child PaymentMethodChargedRefunded So FarRemaining Balance
Payment 1Card$60.00$60.00$0.00
Payment 2ACH$40.00$40.00$0.00
Total$100.00$100.00$0.00

Result: REFUNDED — the remaining $35.00 (Card) + $40.00 (ACH) = $75.00 refunded. Both methods now have zero remaining balance.

Step 4 — Subsequent Refund Attempt (rejected)

The merchant tries to refund again. CCG determines $0.00 remaining across both methods → request is rejected immediately with NOT_ACCEPTABLE, without querying Stripe.

Sequence Diagram (with Balance Tracker)


11. Dependencies

DependencyTypeOwnerRisk Level
Stripe Refund APIExternalStripeMedium — subject to Stripe rate limits and available balance
CCG v2 Payment APIInternalCCG TeamLow
Webhook InfrastructureInternalCCG TeamLow
Balance Tracking (CCG DB)Internal (Future)CCG TeamLow (initial release) — not in scope for first release; see Section 10
Stripe Connect Account BalanceExternalStripeMedium — refunds come from available balance; autodebit setting matters

12. Success Metrics

caution

The targets below are illustrative and subject to change during implementation. Final thresholds will be agreed upon by Product and Engineering before release.

MetricIndicative Target
Refund success rate (both legs succeed)≥ 95%
Partial refund resolution time (merchant acts)Within 48 hours
Refund processing time (end-to-end)Cards: 1–3 days; ACH: 3–5 days
Over-refund incidentsZero

13. Risks & Mitigations

RiskImpactMitigation
One refund leg fails permanently (closed account)HighReturn PARTIAL_REFUND; merchant can issue an unlinked refund to resolve the failed portion
Both refund legs failHighReturn REFUND_FAILED; merchant retries or issues manual refunds
Stripe available balance insufficient for refundMediumIf autodebit is off, the refund is held (queued) by Stripe until the balance is available. CCG reports the refund as PENDING and sends an updated webhook once Stripe resolves it. Merchants should monitor for the final webhook. If autodebit is on, Stripe debits the connected account's bank automatically.
Balance tracking miscalculation (future)MediumApplies only after Balance Tracker is implemented; mitigated by thorough unit/integration tests and a reconciliation job
ACH refund takes 3–5 days — split tender has mixed timingLowClearly communicate per-method timelines in documentation
Refund requested after 180 daysLowValidate time limit upfront; return clear error message

DocumentLink
Split Tender Payments (Business)Split Tender Payments
Split Tender Refund Scenarios (Business)Refund Scenarios
Split Tender Refund Business RulesBusiness Rules
Split Tender Linked RefundsLinked Refunds
Standard Refund API (Developers)Refund API
Refund Business OverviewRefunds
Refund Error CodesError Codes

15. Glossary

TermDefinition
Split TenderA payment method where a customer pays for a single order using exactly two payment methods
Parent TransactionThe top-level order or session that groups the two child payments together
Child PaymentOne of the two individual charges within a split tender transaction
Linked RefundA refund that references the original payment transaction — money goes back to the original method
Unlinked RefundA refund issued to a payment method without referencing a prior charge — used as a fallback when linked refund fails
Balance Tracker(Future enhancement) A capability within CCG that will allow the platform to determine remaining refundable amounts per child payment locally, without relying on Stripe for every request — see Section 10
REFUNDEDFinal status when all requested refund amounts have been successfully returned
PARTIAL_REFUNDFinal status when one refund leg succeeded but the other failed
REFUND_FAILEDFinal status when both refund legs failed or the request was invalid
PENDINGInterim status when a refund is queued (e.g., waiting for sufficient Stripe balance)
AutodebitA Stripe Connect setting: when enabled, Stripe automatically debits the connected account's bank to cover refunds when the Stripe balance is insufficient
ACHAutomated Clearing House — a bank-to-bank transfer method; ACH refunds typically take 3–5 business days
WebhookAn automated notification sent by CCG to the merchant's server when a refund status changes
merchantTransactionIdA unique reference number that the merchant includes with each refund request to prevent duplicate processing
202 AcceptedAn acknowledgment from the system meaning "your request has been received and will be processed shortly" — not the final result
Refund LegOne half of a split tender refund — since there are two payment methods, each individual refund is called a "leg"
OrchestrationThe coordination of multiple steps (in this case, two refunds) into a single managed process
v1/refunds / v2/refundsVersion 1 and Version 2 of the CCG refund API; v1 handles single-method refunds, v2 adds support for split tender