The short answer on Flint
On Flint the order is the cart. The first add-to-cart creates an order from your catalog variants; later adds, quantity changes, and removals update it, and promotion codes stack by your rules. Flint prices every change on the server and returns the totals. When the buyer checks out, one checkout session for that order collects the address, offers shipping or pickup, adds US sales tax, holds stock while the buyer pays, and takes the payment. The paid order is what you fulfill. Card processing runs on Stripe underneath. The Flint requests are below.
What Stripe gives a store with several products
- Payment Links take up to 20 line items with flat-rate prices, fixed when you create the link. Buyers can change quantities if you allow it and add optional items you set up, but they can't browse and build a cart on the link.
- Checkout Sessions take up to 100 line items and optional items combined in payment mode, with Stripe recommending you consolidate past a few dozen, at most 10 of them optional, one coupon or promotion code, and up to 5 shipping rates. On the hosted page, shipping rates can't change with the buyer's address.
- After payment, Stripe's fulfillment guide has you retrieve the session's line items, save them to your own database, reconcile quantities the buyer adjusted, and update your stock records.
Everything between the first add-to-cart and the Checkout Session, the cart itself, is yours to build.
The workaround: a server cart and a Checkout Session
Keep the cart on your server, keyed to the browser session, and build the session from it when the buyer checks out:
// The browser sends IDs and quantities. Never trust its prices.
app.post("/cart/checkout", async (req, res) => {
const cart = await db.carts.get(req.session.cartId); // your cart table
const line_items = cart.items.map((item) => ({
price: catalog.priceIdFor(item.productId, item.size), // your mapping
quantity: item.quantity,
}));
// One coupon or promotion code per session: merge yours first
const discounts = cart.couponId ? [{ coupon: cart.couponId }] : [];
// Up to 5 fixed shipping rates; the hosted page can't change them by address
const shipping_options = shippingRatesFor(cart).slice(0, 5);
await inventory.hold(cart.items, { minutes: 31 }); // your stock holds
const session = await stripe.checkout.sessions.create({
mode: "payment",
line_items,
discounts,
shipping_options,
shipping_address_collection: { allowed_countries: ["US"] },
automatic_tax: { enabled: true },
expires_at: Math.floor(Date.now() / 1000) + 31 * 60, // just past Stripe's 30-minute minimum
client_reference_id: cart.id,
success_url: "https://example.com/thanks?session_id={CHECKOUT_SESSION_ID}",
});
res.redirect(303, session.url);
});
Behind those calls sit a cart table, a product-to-Price mapping, your discount rules, a shipping table, stock holds, and the webhook that writes the order. These are the places it goes wrong:
Where a hand-built Stripe cart goes wrong
On Flint, the order is the cart
Create the order when the buyer adds the first item. Keep its ID with the browser session:
# The first "Add to cart" creates the order
curl -X POST https://api.withflintpay.com/v1/orders \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: cart-8f2c-create" \
-H "Content-Type: application/json" \
-d '{
"line_items": [
{ "variant_id": "var_1kmn0aExample", "quantity": 1 }
]
}'
Each later change is one call, and each returns the whole order with fresh pricing_amounts to render in the cart:
# Later adds append lines; Flint prices them from your catalog
curl -X POST https://api.withflintpay.com/v1/orders/ord_1kmn0aExample/line-items \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: cart-8f2c-add-2" \
-H "Content-Type: application/json" \
-d '{ "line_items": [{ "variant_id": "var_2bqr7dExample", "quantity": 2 }] }'
# Change a quantity, or DELETE the same route to remove the line
curl -X PATCH https://api.withflintpay.com/v1/orders/ord_1kmn0aExample/line-items/li_2bqr7dExample \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "quantity": 3 }'
A promotion code is a request too. Flint checks the code's rules and how it combines with any automatic offers, applies what qualifies, and tells you in a warning when a code can't combine:
curl -X POST https://api.withflintpay.com/v1/orders/ord_1kmn0aExample/discounts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "promotion": { "promotion_code": "SPRING10" } }'
When the buyer clicks checkout, create a session for the order and redirect to its url:
curl -X POST https://api.withflintpay.com/v1/checkout-sessions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: cart-8f2c-checkout" \
-H "Content-Type: application/json" \
-d '{ "order_id": "ord_1kmn0aExample" }'
What the hosted checkout does with it:
- Delivery. Collects the address and offers your shipping methods, priced by region, free over a threshold, or from your own rate callback, plus in-store pickup and local delivery. See Delivery options.
- Tax. With automatic tax on, adds US sales tax once the address is in, on the total after discounts. See Sales tax.
- Stock. Holds tracked stock when payment starts, commits it when payment succeeds, and releases it if payment fails. See Inventory.
- The record.
order.paid fires on the same order, with the items, discounts, shipping, and tax already on it, so there is no session to copy into an order table.
If the buyer goes back to the cart after you created the session, change the order as before. A change you make with your API key ends the open session, so its URL can't collect the old total; create a new session when they check out again. To render the checkout on your own domain instead, create the session with "surface": "embedded", as Build your own checkout describes.
Who Flint is built for
US businesses selling physical or digital goods in USD on their own site or app, and the developers and agencies building those stores. See the Flint commerce API, and, if you use a hosted cart service on top of Stripe today, Flint as a Snipcart alternative.
A Stripe cart and a Flint order, side by side
| Stripe Checkout and Payment Links | Flint orders and checkout |
|---|
| Cart object | None; your table | The order |
| Items | Up to 20 on a Payment Link; up to 100 on a session | Catalog variants and bundles added to the order |
| Prices and totals | Your server, then the session | Flint, on every change, in pricing_amounts |
| Discount codes | One coupon or promotion code per session | Codes and automatic offers, stacked by rule |
| Shipping by address on the hosted page | Up to 5 fixed rates | Region rates, thresholds, pickup, local delivery, callbacks |
| Stock during payment | Your holds | Held when payment starts |
| Record you fulfill from | Your copy of the session's line items | The paid order, on order.paid |
Questions, answered
Does Stripe have a shopping cart?
No cart object. A Payment Link sells a fixed list of up to 20 items, and a Checkout Session takes the line items you pass when you create it, up to 100 line items and optional items combined in payment mode, at most 10 of them optional. A cart where buyers add and remove products while they browse is something you build and store, then turn into a Checkout Session.
Can a Stripe Payment Link have multiple products?
Yes, up to 20 line items with flat-rate prices, chosen when you create the link. The buyer can change quantities if you allow it and add optional items you set up, but can't browse your catalog and build their own cart on the link.
How do I check out multiple products with Stripe Checkout?
Keep the cart on your server as product IDs and quantities, build line_items from your Price IDs when the buyer clicks checkout, create the session, and redirect to its URL. After payment, read the session's line items in your webhook and save the order to your own database.
What is the best shopping cart API to use with Stripe processing?
Flint Pay, for US businesses selling in USD. A Flint order is the cart: you add and remove catalog items, apply promotion codes that stack by rule, and Flint computes discounts, shipping, and US sales tax on the server. One checkout session collects payment on Stripe's processing, holds stock while the buyer pays, and the paid order is the record you fulfill from.
Do I need Snipcart or another cart service on top of Stripe?
Not with Flint. Snipcart's pricing page lists 2% per transaction plus payment gateway fees, or $20 a month under $1,000 in sales. Flint's orders, catalog, promotions, shipping, inventory, and checkout are included in its published card rate of 3.79% + 35¢, which covers Stripe's processing fees, with automatic tax at 0.50% on taxed payments and no monthly fee.
Is Flint a good alternative to building a cart on Stripe?
Yes, for US businesses selling physical or digital goods online in USD that would otherwise build a cart, discounts, shipping rules, tax, stock holds, and an order table around Checkout. Flint's API, Node and PHP SDKs, and hosted checkout cover those pieces.
Sources
All sources accessed October 15, 2026.