Say you sell a $19 per seat tool and most of your 40-ish customers pay monthly by card through Checkout. Then an office manager at a 12-person agency writes:
"We'd like to move the whole team to the annual plan, but our finance team can't put this on a card. Can you send us an invoice, net 30, and we'll pay by bank transfer?"
That's 12 seats x $19 x 12 months, so $2,736 in one go. At under 50 customers that might be your biggest single payment of the year. You want to say yes. But your billing setup assumes a card on file, and "send an invoice and wait" quietly breaks a couple of assumptions in your app, mainly who gets access and when.
This is the version I'd want before replying to that email. What changes in Stripe when a customer pays by invoice, the access trap that comes with it, how bank transfers actually land, and a short policy so the next one takes five minutes.
What "pay by invoice" means in Stripe
Every Stripe subscription has a collection_method. There are only two values:
-
charge_automatically: the default. Stripe charges the default payment method every period. This is what Checkout subscriptions use. -
send_invoice: Stripe creates the invoice and emails it to the customer, with a due date. They pay it themselves, whenever they get to it before (hopefully) the due date.
For send_invoice you also set days_until_due (the "net 30" part). The invoice email links to a hosted invoice page where the customer can pay with whatever payment methods you've enabled for invoices, and download a PDF of the invoice and the receipt. For finance teams the PDF is the whole point, they need something to attach in their accounting system.
One small timing detail from the subscription invoice docs: with charge_automatically Stripe finalizes the renewal invoice right away, with send_invoice it finalizes it about an hour later. So if you have code that reacts to invoice.finalized, don't expect it at the exact second the period renews.
If this agency also wants their VAT number or company address on the invoice, that's its own small project. I wrote up what to set before you send a business customer an invoice with their VAT number, and it's worth doing that first, because a finalized invoice keeps the tax details it was finalized with.
The trap: "active" doesn't mean "paid" anymore
This is the part that matters most, and it's easy to miss.
With card subscriptions, a new subscription whose first payment fails sits in incomplete. Your paywall checks for active (or active and trialing) and everything is fine, because active roughly means "money came in".
With send_invoice, Stripe's API reference says it plainly: subscriptions with collection_method=send_invoice "are automatically activated regardless of the first Invoice status." The subscriptions overview says the same thing, the initial status is active "even if the first invoice is unpaid."
So if your app does this:
const hasAccess = sub.status === "active" || sub.status === "trialing";
then the agency gets full access for 12 seats the minute you create the subscription, before a cent arrives. For net 30, that's 30 days of access on trust, plus however long it takes you to notice the invoice is overdue.
Later on, the status does catch up. When a send_invoice invoice isn't paid by its due date, the subscription moves to past_due. Then, depending on your settings, it can stay past_due, become unpaid or get canceled. More on that below.
For a 12-person agency that asked politely, access on trust is probably fine. The point is to decide it on purpose instead of finding out your paywall decided for you. Three options:
| Policy | How it works | Good for |
|---|---|---|
| Access on trust | Give access when the subscription is created, cut it when it goes past_due
|
Known companies, renewals of existing customers |
| Access on payment | Keep the account locked (or on a trial) until the first invoice is paid
|
New companies you've never heard of |
| Short first term | First invoice net 7, later renewals net 30 | Somewhere in between |
Update (Oct 26): thanks to @davekurian for catching a bug in the first version of this snippet: it checked latest_invoice, which on a renewal is the new invoice still open inside net 30, so it locked out customers who'd already paid for the year. The version below stores a paid-through date from invoice.paid and checks it plus a grace window.
If you go with "access on payment", don't check the latest invoice at access time. Store a paid-through date when Stripe tells you an invoice was paid, and check against that. Something like this (illustrative, check the field names against your SDK and API version):
// Webhook handler, after verifying the signature.
// invoice.paid also fires when you mark an invoice paid out of band.
if (event.type === "invoice.paid") {
const inv = event.data.object as Stripe.Invoice;
// API 2025-03-31 and later; older versions use inv.subscription
const subId = inv.parent?.subscription_details?.subscription;
if (subId) {
const sub = await stripe.subscriptions.retrieve(subId as string);
// Stripe's docs: confirm the subscription is active before extending access
if (sub.status === "active") {
// the service period this invoice paid for (not inv.period_end)
const ends = inv.lines.data
.filter((l) => l.parent?.type === "subscription_item_details")
.map((l) => l.period.end);
if (ends.length) await savePaidThrough(sub.id, Math.max(...ends)); // only ever move it forward
}
}
}
async function hasAccess(sub: Stripe.Subscription) {
if (sub.collection_method !== "send_invoice") {
return sub.status === "active" || sub.status === "trialing";
}
if (["canceled", "unpaid", "incomplete_expired", "paused"].includes(sub.status)) return false;
if (await hasManualOverride(sub.customer)) return true;
const paidThrough = await getPaidThrough(sub.id); // unix seconds, null until an invoice is paid
if (!paidThrough) return false; // first invoice not paid yet: stays strict
const graceDays = (sub.days_until_due ?? 30) + 7; // net terms plus a week
return Date.now() / 1000 < paidThrough + graceDays * 86400;
}
The first invoice stays strict (no paid-through date, no access). A renewal keeps access while the new invoice is open, up to its net terms plus a week. Use the line item's period.end for this, not the invoice's period_end. Stripe's docs point you to the line item period for the service period, and on a renewal invoice period_end is not the end of the year they just paid for. If you have customers who paid before you add this, backfill their paid-through date once from their last paid invoice.
The hasManualOverride bit is for the "they emailed me the remittance PDF and the bank transfer is on its way" case. You'll want a way to say "yes, give them access now" without hacking the database.
How bank transfers actually land
You can let invoice customers pay by card on the hosted invoice page too, but if finance asked for bank transfer, they mean it. Stripe supports this through what it calls the customer balance, and the mechanics are nicer than I expected:
- Stripe gives each customer a unique virtual bank account number and puts it on the invoice PDF and the hosted invoice page. Your real bank details are never on the invoice.
- The same customer gets the same account details on every invoice, so their finance team can save you as a payee once.
- When money arrives, Stripe tries to match it to an invoice automatically. First by the invoice number in the transfer reference, then by exact amount against the oldest open invoice, then a couple of fallback rules.
- If it can't match, the money sits in that customer's cash balance and you get a
cash_balance.funds_availablewebhook. You reconcile it by hand.
A few details worth knowing before the first one arrives:
Underpayments. If the customer sends less than the invoice total, the invoice stays open until the full amount is covered. This happens a lot with international wires, where a bank fee gets taken in the middle. There's a setting (Settings > Billing > Invoices, under Adjustments and reconciliation) to automatically write off small differences below a threshold you pick.
Overpayments and leftovers. Extra money goes into the customer's cash balance. If funds sit unreconciled for 75 days, Stripe tries to send them back to the customer's bank. If they're still there at 90 days, Stripe moves them to your Stripe balance.
Currencies and countries. Bank transfers work for EUR, GBP, JPY, MXN and USD, and which ones you can offer depends on where your Stripe account is. In the Dashboard you can only make bank transfer a default payment method for USD and GBP invoices, for other currencies you add it per invoice. International SWIFT wires are listed as US-only, in USD.
EU transfers. Stripe's docs warn that the beneficiary name the customer types should match your business name on Stripe exactly, or the payment can get held for extra screening. Put your registered business name in the reply email so they copy it.
Refunds and reversals. Bank transfer payments mostly can't be pulled back the way card payments can (the docs list USD and CAD as the exceptions). And Stripe says it doesn't support refunds for international wires, you'd handle those yourself.
Stripe's page on accepting bank transfers on invoices has the full matching rules and the API calls. To turn it on for subscription invoices you set payment_settings[payment_method_types] to include customer_balance.
The fee math (why finance teams ask for this)
Using the US list prices on Stripe's pricing pages as I read them in October 2026 (yours depend on your country and any custom pricing, so check):
- Card: 2.9% + 30¢. On $2,736 that's about $79.64.
- USD bank transfer: 0.5%, capped at $5. On $2,736 that's $5. Stripe's page also lists $15 per wire payment, so a wire rather than an ACH transfer costs more.
- Stripe Billing itself (pay as you go) is 0.7% of Billing volume, and it applies whichever way they pay. Roughly $19 here.
So on an annual plan of this size, bank transfer saves you about $75 versus a card. Not life changing, but real. On a $19 monthly invoice for one seat the difference is pennies and the extra admin isn't worth it. That's why I'd only offer invoicing on annual plans or above some team size.
When they pay straight into your bank
Some finance teams will ignore the virtual account and pay your normal business account because "that's who we have on file". Stripe can't see that money. You mark the invoice as paid yourself, in the Dashboard or with paid_out_of_band=true on the pay endpoint, which marks it paid without charging anything.
Two cautions. Do this only after the money is actually in your account, not when they say it's been sent. And the Billing pricing page says the 0.7% covers Billing volume "processed on and off Stripe", so marking it paid outside Stripe doesn't obviously get you out of that fee. I'd check with Stripe on your own plan before assuming.
Moving an existing card customer to invoices
Often it's an existing monthly customer who's upgrading to annual and switching how they pay at the same time. Changing collection_method on a live subscription is allowed, and per the collection method docs only invoices created after the change use the new method. Roughly:
curl https://api.stripe.com/v1/subscriptions/sub_123 \
-u "sk_live_...:" \
-d collection_method=send_invoice \
-d days_until_due=30 \
-d "payment_settings[payment_method_types][0]=customer_balance" \
-d "payment_settings[payment_method_types][1]=card"
The monthly to annual switch is a separate change with its own proration and billing date questions, so do one at a time and look at the upcoming invoice before you confirm anything to the customer. Also make sure the customer record has the right billing email. With send_invoice the invoice goes to that email, and if it's the office manager's personal address instead of accounts@ it'll sit in the wrong inbox for 30 days.
One more thing: invoice-paid subscriptions don't support the newer Stripe pause (it requires charge_automatically). If an invoice customer later asks to pause, you'll be looking at a different option.
What happens when they don't pay
Card subscriptions have retries. Invoices don't really, the customer has to act. What Stripe gives you instead:
- Reminders. Up to three reminder emails, from 10 days before the due date to 60 days after (Settings > Billing > Subscriptions and emails, "Send reminders if a recurring invoice hasn't been paid").
-
What happens to the subscription. 30, 60 or 90 days after the invoice is past due you can have Stripe cancel the subscription, mark it
unpaid, or leave itpast_due. Withunpaid, new invoices keep getting created but stay as drafts.
At under 50 customers I wouldn't let a robot handle this one alone. A $2,736 invoice that's 10 days late deserves a short personal email to whoever asked for it, not just a Stripe reminder to accounts@. Usually it's stuck in an approval queue and one nudge fixes it. If you're going to write these emails anyway, I put together a playbook on billing emails that cut support tickets that covers renewal and receipt emails too.
A small invoicing policy
Write it once, paste it into the reply next time:
- Who can pay by invoice. Annual plans only, or maybe 5+ seats. Monthly stays on card.
- Terms. Net 30 for renewals, net 7 or net 14 for a first invoice.
- Access. On trust from day one, or after first payment. Pick one and make your paywall match.
- How to pay. Bank transfer to the details on the invoice, invoice number in the reference. Card on the same page works too.
- What happens if it's late. A personal email at about 7 days overdue, access paused at about 30, and what that looks like for their team.
- Paperwork. Company name, address, tax ID collected before the first invoice is finalized.
Then the reply to the agency writes itself:
"Happy to. I'll set the team up on the annual plan for 12 seats and send an invoice for $2,736 with 30 day terms. It'll come from Stripe with bank transfer details, please put the invoice number in the transfer reference so it matches automatically. Can you send me the company name, billing address and the email the invoice should go to? Your team keeps access while the invoice is open."
And once a company is paying for 12 seats, they'll start asking for roles, shared workspaces and someone other than the office manager who can see the invoices. If your product was built for one person per account, I wrote about adding team accounts to a product built for one user and what to change first.
Quick checklist
- [ ] Decide who's allowed to pay by invoice (annual, team size)
- [ ] Set
collection_method=send_invoiceanddays_until_due - [ ] Remember
send_invoicesubscriptions areactivebefore they're paid, and fix the paywall to match your policy - [ ] Turn on bank transfers (
customer_balance) and check your currency is supported - [ ] Billing email on the customer is the finance inbox, company details set before finalizing
- [ ] Reminders on, plus your own personal email when it's late
- [ ] Know what to do with underpayments, overpayments and money that lands in your own bank account
- [ ] Decide what Stripe does at 30/60/90 days past due
So, a question for anyone who already sells to teams: when a customer pays by invoice, do you give access the day the invoice goes out, or only once it's paid? And what days_until_due did you end up using for the first invoice?
Top comments (11)
The access trap is the one that bites a second time, because the fix for the first invoice customer is always manual: someone flips their access on, meaning to come back and automate it. At 40 customers that someone is you, and the second invoice customer arrives the week you are busiest. Same rule as the March pause: the policy is only real if the follow-up has a date and a name. With invoices there are two follow-ups, the day access starts and the day the transfer has not landed, and the second one is the one nobody schedules.
Yeah, the manual flip is the part that quietly becomes permanent. Two follow-ups is the right way to put it. The day access starts is easy to remember because the customer is waiting on you. The day the transfer hasn't landed is the one nobody puts on a calendar. Writing both dates down when the invoice goes out is probably the whole fix at this size.
"Probably the whole fix at this size" is right, and it scales by staying boring. The transfer-not-landed date is also the only one with money attached, which makes it the one most likely to get postponed with "give them another week." One small addition that keeps it honest: the calendar entry says what "not landed" means too, so the day arrives with a decision already made instead of a judgment call about being nice.
writing down what "not landed" means is a good add. otherwise day 31 turns into "maybe it's just slow" and you give them another week without deciding to. something like "no transfer by the due date plus 3 business days, access goes to read-only and i email them" is boring but it's a decision made on a calm day, not on the awkward one.
"A decision made on a calm day, not on the awkward one" is the whole trick, and it fixes the tone problem too. The email written on the calm day reads factual. The one written on day 38, after three weeks of maybe-it's-just-slow, reads annoyed no matter how carefully you word it. Read-only plus an email is also the right escalation: reversible, boring, and the customer always knows the next step before it happens.
yeah the day-38 email reads annoyed because by then you are annoyed, no wording fixes that. writing the read-only step down before the first invoice also means the customer heard about it before it happened, so when it kicks in it's just the thing you both already knew about, not a punishment.
One thing in the "access on payment" snippet: it checks
sub.latest_invoice, and on a renewal that is the new invoice, still open for 30 days. So a customer who paid the first year in full gets locked out the day the renewal invoice is finalized, even though they are inside net 30. I would only apply the paid check to the first invoice (billing_reason === "subscription_create"), or store a paid-through date frominvoice.paid(the period end of that invoice) and compare against that. Then renewals keep access until the date plus a grace window you pick, and the first-invoice rule stays strict.Good catch, you're right. On a renewal latest_invoice is the new open one, so that check would lock out someone who already paid a full year and is still inside net 30. Storing a paid-through date from invoice.paid and comparing against that plus a grace window is the cleaner rule, and it keeps the first-invoice case strict. I'll fix the snippet.
fixed it. the snippet now saves a paid-through date from invoice.paid (the subscription line's period end, not the invoice's period_end) and checks that plus net terms and a week of grace. so the first invoice stays strict and a renewal inside net 30 keeps access. past_due on its own doesn't lock anyone out anymore, the grace window decides. added a note crediting you above it. thanks again, that one would've hurt the exact customers you most want to keep.
The paid-through date from the subscription line is the key detail here. That keeps renewal access tied to the period the invoice actually paid for, instead of the invoice’s overall period end. Good fix for the net-terms edge case.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.