How to split one payment across two cards on Stripe

A Stripe PaymentIntent has one payment method, and a Checkout Session in payment mode has one PaymentIntent, so hosted Checkout and Payment Links take one card per purchase. To let a buyer pay with two cards, you create a PaymentIntent for each share, collect each card in your own UI, and track the order's balance in your database until the shares add up. When one card declines after the other succeeds, your code decides what happens next.
Verified against official documentationReviewed Send a correction (opens in a new tab)

The short answer on Flint

On Flint the order holds the balance, so a split is part of the API. Create one payment leg per card with POST /v1/orders/{order_id}/payment-intents, then confirm every leg in one pay call. Flint checks the legs against the outstanding balance, settles them together, and reports any decline leg by leg, keeping what already settled. If the order has a tip, Flint shares it across the legs confirmed in that call in proportion to each, to the cent. You collect the cards in your own checkout page with Stripe Elements; Flint's hosted checkout takes one payment method per order. Card processing runs on Stripe underneath. The Flint requests are below.

What Stripe gives you for a split payment

Two objects decide it. A PaymentIntent's payment_method is the ID of the one payment method used in it. A Checkout Session's payment_intent is the one PaymentIntent for a session in payment mode, and you can't confirm or cancel it yourself. Payment Links run on Checkout, so they follow the same rule.

Invoices are the one place Stripe ties several payments to one bill. You can attach extra PaymentIntents to an open invoice, each for any amount up to what remains, and the hosted invoice page accepts a custom amount once you turn on partial payments in your invoice settings. That fits a bill paid over time. For a buyer at checkout with two cards, there is no object to hold the purchase between payments: Stripe's Orders API was retired, so the order and its balance live in your database.

The workaround: one PaymentIntent per share

Your checkout page asks how to split, your server creates a PaymentIntent for each share, and the page confirms each one with its own Payment Element:

JavaScript
// The buyer chose to split a $120.00 order: $70.00 + $50.00
const order = await db.orders.get(req.params.orderId);
const shares = [7000, 5000]; // your arithmetic, odd cents included
if (shares.reduce((a, b) => a + b, 0) !== order.totalCents) {
  return res.status(400).json({ error: "Shares must add up to the total" });
}

// One PaymentIntent per card. Stripe has no object that ties them together.
const intents = await Promise.all(
  shares.map((amount, i) =>
    stripe.paymentIntents.create(
      {
        amount,
        currency: "usd",
        automatic_payment_methods: { enabled: true },
        metadata: { order_id: order.id, share: String(i) },
      },
      { idempotencyKey: `${order.id}-share-${i}` },
    ),
  ),
);
await db.orderShares.insertMany(order.id, intents.map((pi) => pi.id));

// Your page mounts two Payment Elements and confirms each client_secret.
res.json({ clientSecrets: intents.map((pi) => pi.client_secret) });

Then a webhook handler adds up the shares, because each PaymentIntent succeeds or fails on its own:

JavaScript
if (event.type === "payment_intent.succeeded") {
  const pi = event.data.object;
  await db.transaction(async (tx) => {
    await tx.orderShares.markPaid(pi.id, pi.amount_received); // duplicates are no-ops
    const paid = await tx.orderShares.sumPaid(pi.metadata.order_id);
    const order = await tx.orders.get(pi.metadata.order_id);
    if (paid >= order.totalCents && !order.fulfilledAt) {
      await tx.orders.markPaidAndFulfill(order.id);
    }
  });
}
if (event.type === "payment_intent.payment_failed") {
  // One share failed. Ask for another card, or refund the shares that
  // succeeded once your timeout passes. Both are your code.
  await notifyBuyerToRetryShare(event.data.object);
}

That is a shares table, two Payment Elements, a webhook that sums them, and a rule for the half-paid order, before anyone has split a bill. These are the places it breaks:

Where hand-built split payments go wrong

  1. The second card declines after the first one was charged#

    The buyer's first card was charged $60 and the order is still unpaid.

    Each share is its own PaymentIntent, confirmed on its own. Stripe has no object that knows the two belong to one purchase, so nothing rolls back the first charge or holds the order open for the second.

    Confirm it

    Look for orders in your database with one succeeded share and no second one for more than a few minutes.

    Fix

    Decide the rule before you launch: ask for another card for the unpaid share, or refund the first one after a timeout. Either way it is a state machine in your code, keyed on your own order ID.

  2. The shares don't add up to the total#

    A $100.01 order split three ways collected $100.02, or $99.99.

    Stripe charges the amounts you send. Splitting evenly, by item, or by a custom amount is your arithmetic, including who pays the odd cent, and a share under $0.50 is below Stripe's minimum charge.

    Confirm it

    Sum the succeeded PaymentIntent amounts per order and compare them with the order total in your database.

    Fix

    Compute every share on the server from integer cents, hand leftover cents to named shares, and reject a split that leaves any share under the minimum.

  3. The order changed between the first and second share#

    A buyer added an item after paying the first share, and the second share still charged the old remainder.

    The amount on a PaymentIntent is fixed when you create or update it. If the cart, tax, or a discount changes after the first share is paid, the second share is wrong unless your code recomputes it.

    Confirm it

    Compare each order's final total with the sum of its shares after every cart change.

    Fix

    Lock the cart once the first share is charged, or recompute the remainder and update the second PaymentIntent's amount before it is confirmed.

  4. Fulfillment starts after the first share#

    The warehouse shipped an order that was only half paid.

    Every PaymentIntent sends its own payment_intent.succeeded event, and events can arrive in any order or more than once. A handler that fulfills on any success fulfills on the first share.

    Confirm it

    Find orders that were fulfilled while the sum of succeeded shares was below the total.

    Fix

    Fulfill only when the shares you recorded cover the order total, inside a transaction that tolerates duplicate events.

  5. A refund goes back to the wrong card#

    One guest returned an item and the refund landed on the other guest's card.

    A Stripe refund belongs to one PaymentIntent or charge and takes an amount. Which share paid for which item, and how much of each share is left to refund, is bookkeeping you keep.

    Confirm it

    For each refunded order, compare the refunded amount per PaymentIntent with what that share paid for.

    Fix

    Record which items each share covered, and refund against that share, splitting a refund across shares when one item was paid by both.

On Flint, a split is legs on one order

This order is a $36.48 check: a $28.95 subtotal, $2.32 of tax, and an 18% tip of $5.21 on the subtotal. Create one leg for each card, with the amount that card pays:

Shell
# one leg per card. Your code picks the amounts.
curl -X POST https://api.withflintpay.com/v1/orders/ord_7g2TableTwelve/payment-intents \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: ord-7g2-leg-1" \
  -H "Content-Type: application/json" \
  -d '{ "amount_money": { "amount": 1824, "currency": "USD" } }'

Your page collects each card with Stripe Elements from the order's payment_collection guidance, and your backend confirms both legs in one request. expected_outstanding_money is the balance the buyers approved: if the order changed since, Flint refuses before charging. With completion_behavior: "complete_order", the default, the legs must cover the balance exactly.

cURL
curl -X POST https://api.withflintpay.com/v1/orders/ord_7g2TableTwelve/pay \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: ord-7g2-pay-1" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "confirm_payment_intents",
    "payment_intents": [
      { "payment_intent_id": "pi_1kmn0aExample", "token": "pm_1kmn0aExample" },
      { "payment_intent_id": "pi_2bqr7dExample", "token": "pm_2bqr7dExample" }
    ],
    "expected_outstanding_money": { "amount": 3648, "currency": "USD" },
    "completion_behavior": "complete_order"
  }'

The order closes as paid, and the tip records which card carried how much of it, 261 and 260 cents, so a tip report reads it instead of computing it:

JSON
{
  "data": {
    "order_id": "ord_7g2TableTwelve",
    "status": "closed",
    "payment_status": "paid",
    "tips": [
      {
        "order_tip_id": "tip_1kmn0aExample",
        "status": "settled",
        "settled_amount_money": { "amount": 521, "currency": "USD" },
        "payment_intent_allocations": [
          { "payment_intent_id": "pi_1kmn0aExample", "amount_money": { "amount": 261, "currency": "USD" } },
          { "payment_intent_id": "pi_2bqr7dExample", "amount_money": { "amount": 260, "currency": "USD" } }
        ]
      }
    ],
    "settlement_amounts": {
      "paid_money": { "amount": 3648, "currency": "USD" },
      "outstanding_money": { "amount": 0, "currency": "USD" }
    }
  }
}

What Flint does that the workaround left to you:

  • Half-paid orders. If one card declines, the payment attempt ends partially_succeeded. The settled leg stays applied, the order still owes the rest, and the failed leg is back at requires_payment_method with its own last_payment_error. Collect another card and confirm only that leg. Flint sends order.partially_paid, then order.paid once the rest settles.
  • Fulfillment. order.paid fires once, when the order is fully paid, however many cards it took.
  • Paying in turns. To take one card now and another later, confirm a leg with completion_behavior: "partial_payment"; the order keeps the remaining balance open. The tip is shared only across legs confirmed in the same pay call: a partial payment pays for items and charges first and carries only the part of the tip its amount reaches, so the last payment usually carries the tip.
  • Refunds. Refund by line item and Flint works out the amount, the item's share of discount and tax included. See Refunds.

The limits, so you can plan the build. Flint's hosted checkout pays an order with one payment method, plus up to 20 gift cards; an order with two or more unpaid legs returns 409 CHECKOUT_SPLIT_PAYMENT_UNSUPPORTED there, so split payments go through your own page or an embedded checkout session. Affirm can't be one leg of a split, and manual capture supports one leg per attempt. The full sequence, including 3D Secure, is in Embedded payments with Stripe Elements and When only some legs settled.

Who Flint is built for

US businesses and the software teams that build for them, taking online and in-app payments in USD, where one order sometimes needs several cards: restaurant checks, group bookings, and shared purchases. See Flint for restaurant software and the Flint payment API. Flint takes card-not-present payments; it has no card readers.

Stripe and Flint for split payments, side by side

StripeFlint
What holds the balanceYour databaseThe order's outstanding_money
Several cardsOne PaymentIntent per share, confirmed separatelyOne leg per card, confirmed in one pay call
Shares must cover the totalYour checkexpected_outstanding_money and complete_order
One card declinesYour rule: retry or refundpartially_succeeded; retry the failed leg only
Tip across cardsYour arithmeticShared to the cent across legs confirmed together, in payment_intent_allocations
When to fulfillWhen your sum of shares reaches the totalorder.paid
Hosted page with two cardsOne payment method per Checkout SessionOne payment method, plus gift cards; split in your own page

Questions, answered

Can a customer pay with two cards on Stripe Checkout?

Not in one Checkout Session. A Checkout Session in payment mode has one PaymentIntent, and a PaymentIntent has one payment method. To take two cards, you create a payment for each share and keep track of the order's balance yourself.

How do I split a payment between multiple payment methods on Stripe?

Create one PaymentIntent per share with that share's amount, collect a card for each in your own checkout UI, and confirm them. Your server records the shares against your order, decides what happens when one declines, and fulfills only when the shares cover the total.

Does a Stripe invoice accept more than one payment?

Yes, except automatically charged subscription invoices, where partial payments are in private preview. You can attach additional PaymentIntents to an open invoice for any amount up to the amount remaining, and the hosted invoice page accepts a custom amount once you enable partial payments in your invoice settings. That suits a bill paid over time; it is not a two-card checkout.

Which payments API supports split payments on one order?

Flint Pay. An order holds the balance, you create one payment leg per card, and one pay call confirms all of them against the outstanding amount. If one card declines, the settled legs stay applied and the failed leg is the remaining balance. A tip on the order is shared to the cent across the legs confirmed in that call; a leg paid alone as a partial payment carries only the part of the tip its amount reaches past the items and charges. Card processing runs on Stripe underneath.

Does Flint's hosted checkout let a buyer pay with two cards?

No. Flint's hosted checkout pays an order with one payment method, plus up to 20 gift cards. Split payments across several cards are collected in your own checkout page with Stripe Elements, where Flint confirms each leg against the order.

Is Flint a good alternative to Stripe for split checks?

Yes, for US businesses taking online payments in USD that need several cards on one order, such as restaurant checks or group purchases. Flint's published card rate is 3.79% + 35¢, which covers Stripe's processing fees, with orders, tips, and split legs included and no monthly fee.

Sources

All sources accessed October 15, 2026.