I’m building a small Shopify app for B2B (invoice / Net terms) accounts receivable, and I’d rather check my assumptions with people who actually run this than keep guessing.
What it does so far: outstanding vs. overdue by company on one screen, an aging breakdown, per-company statements, and reminder drafts you preview before anything is sent — orders that have been paid drop out of the reminder list automatically, so you don’t chase someone who already settled.
I have not run a wholesale store myself. That’s exactly why I’m asking instead of telling. Three questions:
How do you see total outstanding across your B2B customers today — Shopify admin, a spreadsheet, an accounting tool, something else?
Has a payment reminder ever gone out to someone who had already paid? What broke — timing, a stale manual list, something else?
What would a reminder tool have to do before you’d trust it to draft messages on your behalf?
For transparency, per the board rules: the app is in review and not published, so there is nothing to install today, and I’m not linking to anything or posting contact details here. It’s planned as freemium — a free tier plus paid plans.
Happy to summarise whatever I learn back into this thread.
On your second question, the failure I would expect is not timing. It is that the payment never happened inside Shopify at all. Wholesale money arrives by bank transfer or cheque, someone reconciles it in Xero or a spreadsheet, and nobody goes back and marks the order paid in admin. Shopify still says outstanding, so your reminder is correct against the data and wrong in the real world. Listening for orders/paid does not rescue you there because the event never fires. The interesting design question is what you do about payments recorded somewhere else, not how fresh your list is.
For the first question, most of the raw material is already sitting on the order. Payment terms on an order carry due, overdue and dueInDays, and under that each payment schedule has dueAt, issuedAt, completedAt, balanceDue and totalBalance. Aging per company falls out of dueAt and balanceDue without asking anyone to maintain a sheet.
One trap while you are building. Do not hang the reminder decision off the order financial status. A B2B order with a deposit plus a balance sits partially paid for weeks, and there is a real difference between the deposit schedule being settled and the whole order being settled. completedAt on the individual schedule is the flag that means stop chasing this one.
On trust, the thing I would want is being able to mark something paid from inside your reminder screen and have it write back to the order. Otherwise the tool becomes a fourth place the truth lives, which is the problem the merchant already had.
Are the merchants you have spoken to recording those bank transfers back into Shopify at all, or is admin already stale for them?
lumine’s write-back is the right cure, and there is a gap in front of it worth designing for.
Write-back fixes the record once somebody notices the payment landed. Between the money arriving in the bank and that person opening your screen, the order still says outstanding and your reminder is still armed. That window is days, and it is exactly when the wrong email goes out.
Which lands on your own line: paid orders drop out of the reminder list automatically. That is true only if Shopify learns they were paid. On lumine’s failure mode Shopify never learns, so they never drop out, and the safeguard reads as working precisely because it never fires.
So the state I would add is a third one. Outstanding, paid, and CANNOT TELL, with cannot-tell being any company whose orders are past due AND who has a history of settling outside Shopify. Chase the first, never the third, and show the third with a count so it is visible rather than silently folded into outstanding.
That turns your product question into matching. A transfer arrives with a reference somebody typed, wrong invoice number, or one payment covering four, or the right number short by the fee. Reference alone looks perfect in testing and fails on the first customer who settles three at once.
Disclosure first, since it matters here: I build Upright Net Terms, an app in exactly this space - so treat this as field notes from a competitor, shared because the thread deserves them and the space is big enough to think out loud in.
Two assumption-checks from what I have seen merchants actually do:
The two replies above already found the real boss fight: reconciliation, not reminders. I will add the shape it takes in practice - B2B customers pay statements, not invoices. A wholesale buyer with four open invoices sends one bank transfer for a round number that matches nothing. Whoever records it has to decide how to spread it (oldest-first? customer’s remittance note?), and any app whose data model is strictly per-invoice paid/unpaid cannot represent the leftover. A per-company running balance with partial application is the feature that separates tools that survive contact with real AR from dashboards. There is live demand for this in the wild: someone asked for exactly a running balance on invoices here a few days ago - Running Balance on invoices - and no stock Shopify feature covers it.
On reminders: the aging table is table stakes, and firms mostly do not want automation to send anything firm-toned without a human deciding. What they do want automated is the gentle tier (‘statement attached, all current’) because sending it feels like busywork. Escalation is a decision, not a schedule.
Good luck with the build - genuinely. The more tools take AR on Shopify seriously, the fewer merchants track net terms in a spreadsheet named final_FINAL2.xlsx.
Answering as another builder, so take it with that in mind. Disclosure: I build Credit Guard, a credit-limit and credit-hold app for Shopify B2B that went live this week.
On your question 2, wrong reminders to people who already paid: @lumine has it right that this is a reconciliation problem before it is a timing problem, and the design decisions that follow from it are worth stating plainly.
Read the payment schedule, not the order’s financial status. Shopify’s payment terms carry the due date and whether the schedule has completed, and that is the object a reminder should be keyed to. Financial status lags and lumps.
Send through Shopify, not your own mail server. Shopify exposes a payment-reminder action that renders the merchant’s own reminder template and sends from the merchant’s own sender to the order’s contact. That means the app never needs the buyer’s email address at all, the merchant controls the wording, and the buyer sees the same email they would get from Shopify’s native reminders. I chose that route partly for the privacy posture and partly because it means one less place for the data to be wrong.
Ship reminders off by default. A merchant who collects by bank transfer and has never marked an order paid should not have reminders fire the moment they install. Make them turn it on, and tell them to mark orders paid first.
On question 3, trust: the thing that made the biggest difference in my own testing was showing the overdue list before any email goes out, so the merchant sees the mismatch between Shopify and reality before a buyer does. @Ivan_S1’s “cannot tell” state is a good idea and I have not solved it either; right now that account shows as overdue and the merchant’s fix is to mark the order paid.
Happy to compare notes. The reconciliation gap is the whole category’s problem, not any one app’s.
@sainthesam The native send is the part worth copying.
Never holding the buyer’s email at all is a better privacy position than anything bolted on afterwards, and the merchant’s own template going out beats anything a third party would write.
On cannot-tell, there may be a signal already sitting in the data rather than in the merchant’s memory.
OrderTransaction carries manualPaymentGateway, a non-null boolean for whether the transaction was processed by a manual payment gateway.
A company whose earlier orders settled that way has already told you it pays off platform, and
nobody had to flag it.
That gives you nothing on a company’s first order, which is where I would leave the manual override rather than try to infer one.
@Ivan_S1 That is a better signal than anything I had. A company whose earlier orders settled through a manual gateway has already told you how it pays, and reading that off past transactions costs nothing and needs no new permissions. Agreed on leaving the first order to a manual override rather than guessing. Noted for the roadmap; nothing gets built until a paying merchant hits the problem, but this is how I’d build it when they do. Thanks.
@sainthesam The part I have not solved sits one level below this.
A payment schedule is binary: it has completed or it has not.
A partial payment is neither, so it has no home in the object the reminder keys on.
You end up holding a balance somewhere else, and the moment you do, your number and Shopify’s schedule can disagree, which is the same reconciliation gap one layer down rather than a fix for it.
A reminder tool doesn’t close that without becoming a ledger, and I am not sure it should.
@Ivan_S1 Agreed, and I split it exactly there. The reminder side keys on the schedule and stays dumb on purpose. The credit-hold side is a ledger, there is no way round that if you want a limit to mean anything, and the choice I made is that it never holds its own running balance: every event recomputes the company total from Shopify’s own order data, so the number cannot drift from Shopify’s, it can only lag it by a webhook. Partial payments land as Shopify records them on the order, which is not perfect for the cheque-that-covers-three-invoices case, and I would rather say that plainly than pretend a metafield solves it. Good thread, thanks.
Picking up the partial-payment thread, since it is the exact seam we built on. Disclosure: I build Upright Net Terms, and the running balance in it exists because of this problem.
Ivan is right that the payment schedule is binary and a partial payment has no home in it, and sainthesam is right that a limit only means something with a ledger underneath. The way out we found is to let the ledger own one thing only: the allocation. A payment is recorded once, against the customer, and allocated oldest-due-first across their open orders. Any order the allocation fully covers is marked paid in Shopify at that moment, so its schedule completes and Shopify’s own reminder stops by itself. The order it only partly covers keeps its schedule open, with received and remaining shown beside it. Whatever is left sits as unapplied credit against the customer until the next order. The cheque that covers three invoices becomes three allocation lines and one Shopify state change per settled order, not a balance living apart from Shopify.
What that buys: Shopify’s order data stays the source of truth for everything settled, the ledger only holds the in-between, and the two can disagree by at most one partially paid order. What it costs: that partial order still reads as unpaid to anything keyed on financial status, which is the honest limit of doing this without a custom payment gateway. We took the trade because a reminder firing on a genuinely partial invoice is a smaller sin than a hold lifting on a balance nobody entered.
Agreed, and that ordering is the right one: the remittance is the payer telling you what the money is for, oldest-first is only a guess in its absence. Ours currently does the guess and lets the merchant see the split before it commits, which is not the same as reading the remittance. Taking a pasted remittance, invoice numbers and amounts, and turning it into the allocation is the obvious next step, and I would rather say it is not there yet than imply it is.