In B2C, the customer pays before the box leaves your warehouse. In B2B, the box leaves first and the money arrives 30, 60, sometimes 90 days later — because the buyer isn't a person with a card, it's a company with an accounts-payable cycle. A purchasing manager can't pay a card at checkout: the order goes through approval, gets a budget code, and is settled by finance on a fixed run. Force immediate card payment and you've pushed the buyer out of their own procurement workflow, which is exactly where orders go to die. Offering "pay on invoice" removes that friction. The catch is that PrestaShop ships offline payment modules (bank wire, cheque) that validate an order without capturing money, but it has no complete native deferred-terms / credit-limit workflow, so the real question this post answers is narrow and concrete: how do you actually wire deferred payment terms into a PrestaShop checkout — which payment method, which order state, which group gate, and how do you stop a customer ordering past their credit limit?
This is the implementation post for the cluster. Deciding which businesses earn terms in the first place — the vetting, the credit application — is its own job, covered in trade account applications. Here we assume the customer is already approved and ask: now what does PrestaShop have to do?
What "pay on invoice" means at the PrestaShop level
Strip away the finance vocabulary and a deferred-payment order is just an order that PrestaShop accepts without capturing money, then tracks as unpaid until you manually mark it paid. Three things have to be true for that to work cleanly:
- A payment method that takes no money. The customer reaches the payment step, picks "Pay by invoice," and the order is validated — no PSP, no card, no redirect. PrestaShop already supports this pattern: the core ps_wirepayment (bank wire) module does exactly this, placing the order in an unpaid state and showing the customer your bank details. A deferred-terms method is the same mechanism with different wording on the invoice.
- An order state that says "we're owed money." The order must not sit in "Payment accepted." It needs a state where
paid = 0but the order is real and shippable — either core Awaiting bank wire payment or, better, a custom state you create (see below). - A gate so only approved B2B buyers ever see the method. A retail customer must never be offered "pay later." That gate is a customer group — the same group machinery the rest of this cluster relies on.
Get those three right and the rest is process, not platform.
Step 1 — Gate the method to an approved B2B group
In Customers → Groups, you'll already have (or should create) a group for vetted trade accounts — call it Trade — Terms Approved. Group creation and what each group can see is its own topic; if you're not already running price/visibility by group, start with customer groups in PrestaShop.
The lever that actually hides the method lives under Payment → Preferences / Payment Methods (the exact label varies by version), in the payment module restrictions section. There you set group restrictions alongside currency- and country-based restrictions per payment module. Restrict your invoice-payment module to the Trade — Terms Approved group only. Now the method renders at checkout for an approved buyer and is simply absent for everyone else — no conditional theme code, no risk a retail shopper stumbles onto Net 30. Under the hood those choices are stored in the PREFIX_module_group restriction table (commonly ps_module_group) and applied when core assembles the available payment options for the current customer's group; you're just feeding it the right group.
Step 2 — Create a dedicated order state (don't reuse "Awaiting payment")
You can drop these orders into the stock "Awaiting bank wire payment" state, but it muddies your dashboard: real wire orders and 60-day invoice orders look identical, and you lose the ability to filter "what's on terms and ageing." Create a purpose-built state under Shop Parameters → Order Settings → Statuses. Recommended flags:
| Status field | Setting for "On payment terms" | Why |
|---|---|---|
| Consider the associated order as paid | No | It isn't paid yet — this keeps your paid-revenue figures honest and lets you filter the order as unpaid/on-terms. |
| Allow a customer to download/view PDF invoice | Yes | The buyer needs the invoice to pay — it carries the due date and reference. |
| Set the order as shipped | No | Shipping is a separate, deliberate action after your credit check. |
| Send email to customer | Yes, with a terms template | The mail that delivers the invoice and states "Net 30, due [date], ref [number]." |
| Color | A distinct colour (e.g. amber) | So an "on terms" order is visible at a glance in Orders. |
The internal switch that matters most is paid on the order state (the OrderState object's paid property). When it's 0, PrestaShop still treats the order as outstanding, so you can filter unpaid/on-terms orders as one list (PrestaShop has no native receivables-ageing ledger — that needs a dedicated module) and the order doesn't inflate your "payment accepted" totals. When you later receive the bank transfer, you move the order to Payment accepted and PrestaShop reconciles it. So what does that buy you? A back office where "money we're still owed" is one filtered list instead of a guess.
Step 3 — The payment module: accept the order, capture nothing
A deferred-terms payment module does four things, and only four:
- Renders the option for the approved group (via
hookPaymentOptionsreturning a PaymentOption — the modern equivalent of the oldhookPayment). - Validates the order with no capture by calling
validateOrder()and passing your custom "On payment terms" state ID instead ofPS_OS_PAYMENT. This is the single line that distinguishes a terms order from a paid order. - Writes the terms onto the order — the net period (30/60/90), the due date, and a payment reference — so they land on the generated invoice.
- Optionally blocks on credit limit before validating (Step 4).
The clean part of doing it this way: because you ride the standard validateOrder() path, the order, the invoice, the customer email and the order history are all generated by core exactly as they are for any other payment — no forked checkout, nothing to re-patch on the next PrestaShop upgrade. You're not bending the platform; you're using the slot it already provides for "payment happens out of band," which is precisely what bank wire is.
Step 4 — Enforce the credit limit before the order is placed
This is the piece merchants most often skip and most regret. Every terms customer should carry a credit limit — the maximum total of unpaid invoices you'll allow. Without enforcement, a customer with a 2,000 EUR limit and 1,800 EUR already outstanding can quietly place another 900 EUR order, and you find out when it's overdue.
PrestaShop has no native credit-limit field, so you implement it in the module's pre-validation step:
- Store the limit per customer — a simple custom field (a
customertable column, or a small module table keyed byid_customer) read in the payment hook. - Before
validateOrder(), sum the customer's open balance: total of orders in your "On payment terms" state (or any state wherepaid = 0) that haven't yet been settled. A singleSUM(total_paid_tax_incl)over the relevantordersrows for thatid_customerdoes it. - If open balance + this cart total exceeds the limit, don't render the option as available — or render it disabled with a message: "This order exceeds your available credit. Please settle outstanding invoices or pay by card." The retail card method should still be offered as the fallback so the sale isn't lost.
So what? The limit stops being a number in a spreadsheet you check after the fact and becomes a rule the checkout enforces in real time — bad debt is prevented at the door, not chased afterward.
Step 5 — The invoice has to do the collecting for you
With terms, the PrestaShop invoice isn't a receipt — it's the instrument that gets you paid, so it has to carry everything a buyer's accounts-payable department needs to settle without emailing you back. PrestaShop's PDF invoice is generated by the OrderInvoice object and rendered through the HTMLTemplateInvoice template; the fields you must make sure appear:
- A unique invoice number (core handles this once the order reaches an invoice-eligible state).
- The payment terms and explicit due date — "Net 30, due 14 July 2026," not just "Net 30."
- Your bank details for the transfer.
- A payment reference the buyer must quote, so an incoming transfer reconciles to the right invoice instead of becoming a mystery deposit.
- Any early-settlement discount (e.g. 2/10 Net 30) stated in words.
The free way to add bank-and-terms text is the core Shop Parameters → Order Settings → Invoices footer; the structured way — per-customer due dates, per-order references, an outstanding-balance summary, batch reminders for what's overdue — is what a dedicated invoice management module exists to handle. If you're sending a handful of invoices a month, the footer is fine; once receivables become a real ledger you'll want the order state, the due date and the reference generated and tracked automatically rather than typed.
Net 30 vs Net 60 vs early-payment discount — what to actually offer
Don't offer one blanket term to everyone. Tie the term to the relationship and tier it through the customer group, so the payment-method gate from Step 1 also encodes the generosity of the terms:
| Term | Offer to | PrestaShop setup |
|---|---|---|
| Prepaid / card only | Brand-new accounts, first order | No terms method shown — they're not in the approved group yet. |
| Net 30, small limit | 2nd–3rd order, paid promptly so far | Approved group, low credit-limit value, Net 30 on the invoice template. |
| Net 60 | Established, reliably paying accounts | Higher-tier group with its own method instance / limit; longer due-date calc. |
| 2/10 Net 30 (2% off if paid in 10 days) | Anyone you'd rather pay you fast | Discount stated on the invoice; reconcile the reduced amount manually on early payment. |
Because each tier is a group, you can hold three method instances (Net 30, Net 60, prepaid) and let group restriction decide which a given buyer sees. A second-order customer never sees Net 60; an established account never has to ask for it.
The cash-flow reality you're signing up for
One honest caveat, because the platform can't fix it: terms mean you've paid for stock and shipping while the money is 30–60 days out. At 20,000 EUR/month of B2B revenue on Net 30, roughly a month's revenue is permanently in transit; on Net 60, two. That working-capital gap is a business decision, not a PrestaShop setting — finance it from your own funds, a credit line, or factoring. What PrestaShop can do, and what the steps above deliver, is make sure you always know the exact size of that gap: one filtered list of unpaid-terms orders, ageing in front of you, with limits enforced before the next one is added.
Where this sits in the B2B picture
Deferred payment is one capability of a proper B2B setup, not the whole thing. If you're building trade selling from scratch, start with the overview — selling B2B on PrestaShop — then layer in the pieces: showing net prices to business buyers, automating EU VAT exemption, and the vetting that decides who earns terms at all — verifying business customers before giving access. Get the gate, the order state and the credit-limit check right, and "pay on invoice" stops being a spreadsheet of risk and becomes just another way your approved buyers check out — the way their procurement department already expects to.
Comments
No comments yet. Be the first!
Be the first to ask a question or share useful feedback.
Leave a comment
Share a question, an installation detail, or feedback that could help another reader.