Bill subscriptions automatically with GoCardless
Use the Automated Subscriptions app to raise a Payment Request for every subscription invoice so ERPNext charges GoCardless mandates without manual steps.
On this page
ERPNext Subscriptions generate Sales Invoices on schedule, but nothing collects the money. The Automated Subscriptions app closes that gap: when a subscription invoice is submitted, it raises a Payment Request, and a gateway that supports pre-authorization (this guide uses GoCardless) charges the customer's existing mandate. Use this guide to set it up and to understand what happens, and what to check, when a charge does not go through.
How it works
The app registers an on_submit hook on Sales Invoice. For each submitted invoice that belongs to a subscription, the hook creates a Payment Request against the invoice, points it at the right Payment Gateway Account, and submits it. Submitting a Payment Request asks the gateway to collect. For GoCardless that means a charge against the customer's mandate for the invoice's outstanding amount, with no payment link and no email.
The collection date is the invoice's due date, not today. That is deliberate: subscriptions that bill at the beginning of a period post their invoices with the period start date, so the hook never compares the posting date with today.
If you would rather have customers pay from a hosted page by ACH or card, and reconcile the deposits against your bank feed, see Reconciling Bank Transactions with Mercury. That route is payer-initiated and does not charge a mandate.
The app is a small layer on top of ERPNext and Frappe Payments. It does not talk to GoCardless itself. The charge, the mandate lookup and any retry behavior live in the gateway controller of the Payments app.
Prerequisites
You need the following before the hook can do anything useful:
A site with ERPNext and the Frappe Payments app installed. The app declares both as required apps.
A Payment Gateway Account for your company, backed by a working GoCardless gateway.
A GoCardless mandate for each customer you want to charge automatically. Collecting mandates is a Payments and GoCardless task and is not covered here.
A Subscription Plan with its Payment Gateway field set to that Payment Gateway Account.
A Subscription for the customer that uses that plan, generating invoices that are submitted.
Note
The Payment Gateway field on a Subscription Plan is a link to a Payment Gateway Account, not to the Payment Gateway itself. Pick the account that belongs to the company issuing the invoice.
Install the app
From the bench directory, fetch the app and install it on your site:
bench get-app https://github.com/Avunu/automated_subscriptions
bench --site <SITE_NAME> install-app automated_subscriptionsInstall and migrate both write the default values for the app's Subscription Settings fields, so a fresh site starts with the defaults listed below.
The app is broader than the Payment Request hook. It also adds billing-date anchoring, proration, consolidated invoices and credit notes on cancellation, all controlled by extra fields on Customer and Subscription Settings. None of that is required for auto-billing, and this guide leaves it out.
Configure the plan and the subscription
Set the gateway on every plan you want collected automatically. In ERPNext, open the Subscription Plan and fill in Payment Gateway. The hook reads this field and nothing else to decide whether to charge.
Then check the Subscription itself:
Leave Submit Generated Invoices checked. The hook runs when an invoice is submitted, so a subscription that leaves invoices in draft produces no Payment Request until someone submits them by hand.
Decide how the invoice due date is set. The Payment Request collects on the invoice's due date, which comes from the customer's payment terms or from the subscription's Days Until Due. A due date in the past is a late charge, see the lateness setting below.
Choose Generate Invoice At as usual. Any option works, because the hook does not look at the posting date.
Subscription Settings
The app adds an Automated Subscriptions section to Subscription Settings. One field matters for auto-billing:
| Field | Default | What it does |
|---|---|---|
| Auto-charge Max Lateness (days) | 30 | An invoice whose due date is further in the past than this is not charged. Its Payment Request is left as a draft for you to review. |
The section also contains an Auto-billing Delay field. We did not find any code that reads it, so do not rely on it to postpone charges.
What happens when an invoice is submitted
The hook works through these checks in order and stops at the first one that applies:
The invoice is not a subscription invoice. An invoice counts if its header or any of its item rows links to a Subscription.
The invoice is a return (a credit note), or nothing is outstanding. ERPNext refuses to create a Payment Request for a zero amount, so the hook skips these. Otherwise a credit note or a zero-total trial invoice would abort the submit.
The site-wide kill switch is on (see below).
The site is running a migration, a patch, an install or a data import.
No gateway can be resolved from the plans on the invoice.
The invoice is stale: its due date is older than the lateness window. The hook creates the Payment Request but leaves it as a draft.
Otherwise the hook creates the Payment Request, saves it, and submits it. Submitting runs the gateway's own validation, and a charge against a valid mandate is created at that point.
How the gateway is chosen
The hook collects the Subscription Plan from every item row on the invoice and reads each plan's Payment Gateway. Plans with a blank gateway are ignored if another plan on the invoice has one. The result decides what happens:
| Plans on the invoice | Result |
|---|---|
| No plan has a gateway | No Payment Request is created. |
| One gateway across the plans that set one | That Payment Gateway Account is used. |
| Two or more different gateways | No Payment Request is created and an Error Log entry is written. Raise the request yourself. |
Invoices from older subscriptions may have no plan on their item rows. In that case the hook falls back to the plans listed on the subscription named in the invoice header.
Email behavior
A gateway that collects the payment itself tells ERPNext it handled the charge, and the hook then mutes the Payment Request email. This is what a direct-debit mandate charge does, so customers are not emailed a request for money that is already being collected.
If the gateway reports that the customer must act (no usable mandate, for example), the email is not muted and the customer receives the normal payment request with a link.
Note
The meaning of the gateway's return value is defined by ERPNext. The GoCardless controller in the Payments build we read follows it: it reports "handled" when a charge was created against a mandate, and falls back to the payment-link flow when no valid mandate exists or no charge could be created. If you run a different build of the Payments app, check that its GoCardless controller behaves the same way.
Failure handling
The hook is built to skip quietly or leave work for a person in the situations it expects. Gateway problems do not abort the invoice: ERPNext swallows exceptions raised inside the gateway's validation step and treats them as "the customer must pay by link".
Anything that fails before that step, such as ERPNext refusing to create the Payment Request, is not caught by the hook. It raises inside the invoice's submit and the submit fails with it. That is why the hook skips credit notes and zero-total invoices up front.
| Situation | What you see | What to do |
|---|---|---|
| Invoice is stale (past the lateness window) | A draft Payment Request with a comment starting "Left as draft", plus an Error Log titled "Stale subscription invoice not auto-charged" followed by the invoice name | Find out why the invoice is late, then submit the draft or collect another way. |
| Mixed gateways on one invoice | No Payment Request, and an Error Log titled "Mixed payment gateways on subscription invoice" followed by the invoice name | Create the Payment Request by hand for the right gateway. |
| No gateway on any plan | No Payment Request and no log entry | Set Payment Gateway on the plan if the customer should be auto-billed. |
| Credit note or zero-total invoice | No Payment Request | Nothing. This is expected. |
| Gateway cannot charge (for example no valid mandate) | A Payment Request whose email is not muted | The customer pays through the link, or you set up a mandate. |
Late firing is normal, so the app only holds back genuinely old invoices. A subscription invoice that is submitted a few days after its posting date still charges, using its due date as the collection date.
For the details of retrying a failed GoCardless charge, see the Payments app. That logic is not part of this app.
Pause auto-charging for a whole site
Set the subscription_auto_charge_disabled key in the site configuration to stop the hook creating any Payment Request on that site:
bench --site <SITE_NAME> set-config subscription_auto_charge_disabled 1Invoices still submit normally. Turn the switch off again with:
bench --site <SITE_NAME> set-config subscription_auto_charge_disabled 0The switch lives in the site's configuration file, not in the database. A restored copy of the site database, such as a staging clone, therefore does not inherit it. The app does this on purpose: restored copies can carry live gateway credentials.
Warning
A site restored from a production backup can still hold live gateway credentials and live mandates. Set subscription_auto_charge_disabled on every non-production copy before you submit a subscription invoice or let the scheduler run there.
The hook also stays out of the way while a migration, a patch, an install or a data import is running, so restoring data or running bench migrate does not fire charges.
Test it safely
The app's own tests replace the gateway validation with a stub, and you should do the same in spirit: never test against a live gateway with a real customer's mandate.
On a non-production site, point a Payment Gateway Account at a GoCardless sandbox.
Create a Subscription Plan that uses that account and a Subscription for a test customer.
Submit one generated invoice and open the Payment Request listed against it. Check the gateway account, that the collection date equals the invoice due date, and that the request is submitted with status Requested.
Lower Auto-charge Max Lateness (days), submit an invoice with an old due date, and confirm you get a draft request and the Error Log entry instead of a charge.
Submit a credit note against an invoice and confirm it creates no Payment Request.
Sources
This article is in the public domain (CC0 1.0), code samples included. Use it however helps you.