Code Agency
4 min read

Recurring revenue in Odoo: subscriptions, renewals and dunning that works

SaaS-style billing inside an ERP: recurring plans, automated renewals, failed-payment retries, and the MRR and churn numbers finance actually asks for.

Every client who bills on a schedule — SaaS resellers, maintenance contracts, media subscriptions, gyms — asks the same question early: "can Odoo do recurring billing, or do we bolt Stripe Billing onto the side and reconcile by hand?" The honest answer is that Odoo's Subscriptions app already models the whole lifecycle: plans, renewals, failed payments, churn. Most of the pain we see isn't Odoo missing a feature, it's a plan that was never actually configured as a subscription, just an invoice someone remembers to duplicate every month.

Recurring plans, not duplicated invoices

A recurring plan defines the cadence — monthly, quarterly, annual — and gets attached to a subscription product on the order line, the same way a regular sales order works. That single decision changes everything downstream: Odoo now knows when the next invoice is due, what happens on upgrade or downgrade mid-cycle, and how to prorate a plan change instead of asking someone in finance to do the maths in a spreadsheet.

The trap is treating "recurring" as a label instead of a structure. A plan per actual pricing tier — not one generic "subscription" product with the real terms buried in an order note — is what makes upsells, seat changes and cancellations something Odoo can act on automatically instead of something a human has to remember.

Auto-renewal without silent scope creep

Renewal quotations generate automatically ahead of the period end, and the contract rolls forward the moment the customer confirms — no one re-keys the same order every quarter. That automation is exactly why the notice period matters: a subscription that renews silently for twelve more months because nobody read the reminder e-mail is a churn complaint waiting to happen, not a retention win.

We set the renewal reminder window explicitly per plan — typically 30 days out for annual contracts, a week for monthly ones — and route the "about to renew" activity to whoever owns the account, not just an automated e-mail into the void. Machines handle the invoice; a person still owns the relationship.

Failed payments: retries before you lose the customer

This is where most home-grown billing setups fall apart, and where Odoo's payment token integrations (Stripe, Mollie, Adyen) earn their keep. When an automatic charge fails, the subscription doesn't just sit there — it moves to a "payment failed" state, retries on a schedule, and only after a defined number of attempts does it actually lapse.

The sequence we configure on most contracts:

  • Attempt 1, immediately — cards decline for transient reasons (a bank's fraud check, a temporarily maxed limit) far more often than for a genuinely dead card.
  • Attempt 2, day 3 — with a dunning e-mail asking the customer to update their payment method, linking straight to the portal.
  • Attempt 3, day 7 — a second, more direct reminder; access stays live through this window.
  • Close at day 10 — the subscription actually lapses, not before.

That grace period is the whole point. Involuntary churn — a customer who wanted to stay but whose card expired — is the cheapest churn to prevent, and the most expensive to ignore if you cut access on the first failed attempt.

MRR and churn, without a spreadsheet

Finance doesn't ask "what did we invoice this month" — they ask for MRR, net new MRR, and churn rate, and they ask for it broken down by plan. Odoo's Subscriptions dashboard computes all three natively: monthly recurring revenue normalizes annual and quarterly plans onto the same monthly basis, and churn is tracked both by logo (accounts lost) and by revenue (value lost), which almost never move together.

That dashboard covers the numbers finance asks for in the Monday meeting. The moment someone wants cohort retention curves or LTV by acquisition channel, we're past what a KPI widget should be doing and into a proper BI layer — same reasoning as reconciliation automation in Odoo accounting: let Odoo run the operational process, and give the analytical question its own tool.

Recurring billing configured this way is standard on every Odoo implementation we run for clients who bill on a schedule — it's less work to set up correctly once than to keep patching a manual process every renewal.

Want us to publish something specific?

Tell us what you'd like to read and we'll add it to our writing queue.

Get the next one in your inbox

New articles, videos and the occasional engineering note — a short mail when there’s something worth reading, nothing else.