Skip to content
onchain-engineeringusdcinvoice-reconciliation
Joshua C.

Partial USDC Payments for Onchain Invoices: Engineering Patterns

Learn how to handle partial USDC payments for onchain invoices, including reconciliation patterns, settlement mechanics, and example flows for stablecoin billing.

Partial USDC Payments for Onchain Invoices: Engineering Patterns

Stablecoin payments do not always arrive in a single, full transaction. Customers split invoices, wallets run short, or a payer simply miskeys an amount. If you operate onchain billing, handling partial USDC payments is not optional. You need to reconcile what settles with what was invoiced, and decide what to do next.

Why Partial Payments Happen Onchain

Partial payments are common in onchain commerce. Here are the main reasons:

  • A user enters less than the full amount at checkout and submits the transfer.
  • A wallet’s USDC balance is insufficient, but the user sends what they have.
  • A business pays invoices in tranches from multiple wallets.
  • An operator splits payment intentionally to manage cash flow or gas costs.

Unlike legacy rails, stablecoin settlements are atomic, final, and visible. There is no concept of a ‘pending’ payment. Each transaction stands alone, and your invoice engine needs to track and match each incoming payment to an outstanding amount.

Patterns for Tracking Partial Payments

1. Invoice State Machine

For each invoice, define clear states. At a minimum:

  • Unpaid: No USDC received.
  • Partially Paid: Some USDC received, but less than the invoice total.
  • Paid: Full invoice amount received.
  • Overpaid: More than the invoice total received.

Whenever a transfer arrives, update the invoice state. Store the amount received and the transaction hash. If your integration supports it, surface the partial status in the customer portal or webhook payload.

2. Journal of Settlements

Record every USDC payment as a line item linked to its invoice. This is your source of truth for reconciliation. Example journal entries might include:

Time Invoice ID Amount (USDC) Tx Hash
2024-06-12 10:30:00 INV-1001 150.00 0xabc123
2024-06-13 09:15:00 INV-1001 50.00 0xdef456

With this, you can always sum the total received for any given invoice and expose the outstanding balance.

3. Handling Overpayments

Sometimes, a payer sends more than required. Define a policy:

  • Auto-credit the excess to the customer’s account for future invoices.
  • Flag for manual review and refund if needed.
  • Never assume intent, excess funds are final, since USDC settlement is non-reversible.

Worked Example: Invoice Reconciliation with Partial Payments

Suppose you issue Invoice #2002 for 250 USDC. The payer accidentally sends 100 USDC first, then 75 USDC, then finally 75 USDC the next day.

Here’s how you would reconcile this:

  1. First Payment:
    • Transaction received: 100 USDC.
    • Invoice #2002 moves to Partially Paid.
    • Outstanding balance: 150 USDC.
  2. Second Payment:
    • Transaction received: 75 USDC.
    • Invoice remains Partially Paid.
    • Outstanding balance: 75 USDC.
  3. Third Payment:
    • Transaction received: 75 USDC.
    • Invoice moves to Paid.
    • Outstanding balance: 0 USDC.

Example: Tracking Settlements in Code

A simple structure in TypeScript:

interface USDCSettlement {
  txHash: string;
  amount: number;
  timestamp: Date;
}

interface Invoice {
  id: string;
  total: number;
  settlements: USDCSettlement[];
  get paidAmount(): number;
  get status(): 'unpaid' | 'partially_paid' | 'paid' | 'overpaid';
}

// Example implementation
class InvoiceImpl implements Invoice {
  id: string;
  total: number;
  settlements: USDCSettlement[] = [];

  constructor(id: string, total: number) {
    this.id = id;
    this.total = total;
  }

  get paidAmount() {
    return this.settlements.reduce((sum, s) => sum + s.amount, 0);
  }

  get status() {
    if (this.paidAmount === 0) return 'unpaid';
    if (this.paidAmount < this.total) return 'partially_paid';
    if (this.paidAmount === this.total) return 'paid';
    return 'overpaid';
  }
}

UX and Operational Considerations

Exposing Outstanding Balances

Always show the outstanding balance in the customer portal. If a payer submits a partial amount, surface a clear message: “You have paid 100 USDC, 150 USDC remains.”

Issuing Reminders

For partially paid invoices, trigger reminders until the full amount settles. Onchain status checks can power timely notifications. Do not mark an invoice as closed until the sum of onchain receipts matches or exceeds the total.

Matching Payments to Invoices

If a customer sends USDC without referencing an invoice, you need heuristics to match the transaction. Common strategies:

  • Use unique settlement addresses per invoice.
  • Require the payer to include an invoice ID in transaction metadata (where supported).
  • Manual review for unmatched incoming transfers.

What Happens on Shortfall

If the invoice expires before being fully paid, decide if you will return the partial payment, credit it to the customer’s account, or mark the invoice as failed and leave funds as-is. Remember: all onchain settlements are final. Reclack does not reverse payments or initiate refunds automatically.

Onchain Settlement and Finality

Each partial payment is a separate, confirmed transaction. Once settled, there is no reversal or chargeback. Your reconciliation logic should treat each settlement as final, and only update invoice states based on confirmed onchain transfers.

How We Handle Partial USDC Payments at Reclack

At Reclack, we treat every USDC payment as final. Our system tracks each settlement per invoice, updates the outstanding balance, and exposes partial states to both merchants and customers. Overpayments are flagged and credited for future use. We do not attempt to reverse or recover excess funds. For more, see reclack.com.

onchain-engineeringusdcinvoice-reconciliation