Recurring Payments and Dunning
Everything you need to go from a fresh Perfex CRM install to money arriving in your account automatically — and to getting it back when a card declines.
This guide is written to be read once, top to bottom, the day you install the module. Every screen it mentions is a screen you can open right now; every default it quotes is the default the installer writes.
1. What this does
Perfex CRM already creates recurring invoices. It does not collect them: the host vendor's own documentation says the recurring feature re-creates the document. Somebody still has to chase the money.
This module is the collection half. It stores a payment authorization the customer gives you once, charges it off-session when an invoice comes due, records the result as a real Perfex payment, and — when the charge fails — runs a retry and e-mail sequence until the money arrives or you decide to stop.
Two modes
You choose the mode per subscription. They can both run on the same install at the same time.
| Mode | Who owns the billing clock | Use it when |
|---|---|---|
| Auto-charge a recurring invoice | Perfex. Its recurring cron keeps generating the invoices exactly as it does today; the module collects each one as soon as it appears. | You already run recurring invoices and you only want them paid without chasing. |
| Subscription plan | The module. It builds the invoice from a plan you defined (interval, amount, trial, setup fee, taxes, items) and charges it on schedule. | You are selling a subscription product and want plans, trials and cancellation semantics. |
In both modes the outcome is identical where it matters: a numbered Perfex invoice, a Perfex payment row carrying the gateway transaction id, and an audit trail on the Charge Attempts screen.
What happens when a payment fails
A decline is not one thing. The module reads the gateway's decline code and picks a different answer for each kind:
- Soft decline (insufficient funds, processing error, issuer unavailable) — retry on your policy's schedule, and tell the customer.
- Hard decline (lost or stolen card, incorrect number, revoked authorization, expired card) — do not retry. Ask the customer for a new payment method and mark the stored one dead.
- Bank wants a confirmation (3-D Secure) — e-mail the customer a single-use confirmation link instead of retrying.
- Authorization is gone (a cancelled direct debit mandate) — stop, mark the subscription as needing a payment method, and notify your staff.
- Your configuration is wrong (bad API key, currency not enabled) — notify staff. The customer is never dunned for your misconfiguration.
Retrying a hard decline is not merely useless: card networks bill for the attempts and issuers read repeated retries as fraud probing. That distinction is the reason this module exists.
New in version 1.1
Four additions. Three of them change how you bill or what your customers are told, and all three are off when you install and off when you upgrade — each has its own switch that you turn on yourself. The fourth is a report that reads what you already have. Until you turn a switch on, the module charges, retries and duns exactly as 1.0 did.
| Addition | What it is | The switch, and where it is |
|---|---|---|
| Late fees | A penalty that accrues against an overdue invoice and is billed on a separate invoice. The overdue invoice itself is never edited. See section 8. | autobill_latefee_enabled, default off. On the Late Fees
screen's own settings page, behind the Late fee settings button. |
| Renewal and expiry tracking | A ledger of dated things Perfex does not model — domains, SSL certificates, hosting accounts, licences — with reminders before the date. See section 9. | autobill_renewals_enabled, default off. On the module's Settings
screen. |
| Suspension and termination | Two more dunning terminal actions, and two more subscription statuses. It suspends billing, never a customer's login. See section 7.8. | Chosen per dunning policy. No policy uses either action until you pick one, and the seeded Default policy does not. |
| AR aging report | Who owes you what, and for how long, by customer and currency. Read-only. See section 11.4. | No switch. It bills nothing and e-mails nothing — it reads invoices you already have — so it is the one addition that is there after an upgrade without you doing anything. |
Upgrading from 1.0
Upgrading is the same action as installing: download the new archive from your CodeCanyon downloads page and upload it under Setup → Modules → Upload Module. Perfex compares the version header and runs the 1.1 migration once. Your settings, subscriptions, stored payment authorizations, dunning policies and charge history are kept.
Upgrading changes nothing until you switch a feature on yourself. The migration adds schema and defaults. It does not create an invoice, record a payment, send an e-mail, alter a subscription's status, or write a single row your customers could see. What you will notice afterwards are two new entries in the module menu, and neither of them acts: AR Aging, which is a read-only report, and Late Fees, which is shown to staff who may configure the module because the switch that enables late fees lives on it. Everything else in 1.1 stays invisible until you opt in.
What the migration does, and only this:
- creates the two new tables — late fees and tracked renewals — empty;
- adds 17 new options — the five that turn a behaviour on are in the table below; the other twelve are parameters, and none of them does anything until you have opted into the feature it belongs to;
- seeds 3 new e-mail templates (Service Suspended, Service Terminated, Renewal Reminder) — and only when their slug is missing, so wording you edited is never overwritten;
- and nothing else. It writes no cron row of its own: the three new sweeps arrive with the code and join the module's existing cron run the moment the new version is active.
Those three sweeps do nothing on an install that has opted into nothing. Late fees and renewals each read their own switch and return immediately while it is off. Terminations has no switch of its own, because there is nothing to switch off until a dunning policy is set to suspend, then terminate; on an upgraded install none is, so it looks for subscriptions past their waiting period and finds none.
An option that already exists is never rewritten, so a buyer who upgrades, opts in, and later re-runs the migration keeps their choice. Existing dunning policies keep the terminal action they already had.
The five switches, and what they ship as:
| Option | Ships as | What stays off while it is off |
|---|---|---|
autobill_latefee_enabled | 0 | Nothing accrues, no fee is calculated, no fee invoice is raised. |
autobill_latefee_auto_invoice | 0 | An accrued fee waits for a staff member to raise its invoice by hand. |
autobill_renewals_enabled | 0 | The renewal sweep does not run: no reminder is e-mailed, no renewal invoice is raised, whatever an individual renewal row is set to. |
autobill_suspend_notify_customer | 0 | Suspension and termination are recorded and your staff are notified, but the customer is not e-mailed. |
autobill_aging_include_late_fees | 0 | One column on a read-only report is hidden. Nothing else. |
Two of the late-fee rate options ship at zero as well — autobill_latefee_percent at
0.00 and autobill_latefee_fixed_minor at 0 — so even someone
who flipped the master switch by hand in the database would compute a fee of nothing until a rate
is set.
This is checked rather than promised. As its last step the migration runs three assertions: that each of the five switches above is declared off in its own seed table, that both rate options are declared at zero there, and that every row it inserted on this run reads back from the database exactly as it was written. If any of the three fails, it aborts with a message naming the offending option instead of finishing. Perfex only records the new version number after a migration returns, so an aborted upgrade leaves the install on 1.0 to be retried — never half-migrated, and with every switch still off.
What it does not do
Read this list before you buy or before you promise something to a customer. Everything here is a deliberate boundary, not a bug:
- Three gateways only: Stripe, GoCardless and Paystack. There is no PayPal and no Razorpay integration. Your existing Perfex gateways keep working for manual, on-session payments — they simply cannot be charged off-session by this module.
- A late fee never edits the invoice it penalises. Version 1.1 can charge late fees, but the fee accrues in the module's own ledger and becomes a separate Perfex invoice with its own number. The invoice the customer already holds a PDF of is never written to — not its lines, not its total, not its status. That immutability is what makes retries safe, and it is why the fee is a second document rather than an extra line.
- Suspension stops billing, not access. The module does not switch off a customer's Perfex login and does not touch their portal account, in 1.0 or in 1.1. A suspended customer can still sign in — which matters, because signing in is how they fix the card that caused the suspension.
- Renewals are a reminder ledger. Nothing in the renewals screen registers a domain, issues a certificate, or talks to a registrar or a certificate authority. It tracks a date, warns before it, and — if you ask it to — raises the invoice.
- Renewal reminders do not cover Perfex contracts, estimates or proposals. Perfex core already sends its own expiry reminders for those three. Tracking them here would mean your customer is told twice about one date.
- No pro-rata. There is no proration anywhere in the module. Changing a subscription's plan, amount or quantity part-way through a period produces no part-period charge and no part-period credit.
- GoCardless advance notices are sent by GoCardless, not by this module. Bank debit schemes require the payer to be told before money is taken; GoCardless does that for you and must be left switched on. The "module sends it" option is visible in Settings but disabled on purpose, so nobody can turn off the only notice being sent.
- GoCardless payments are never retried by the module. Retrying a bank debit from our side would be a second collection, not a retry. GoCardless Success+ owns that; for GoCardless the e-mail schedule is the whole recovery path.
- Flat pricing only in the plan screen. The database and the engine already carry per-unit, tiered and metered pricing, and there is still no screen for them. The same is true of coupons and of the plan flag "Visible in client portal": the storage exists, the customer self-signup page does not.
- Reconciliation is a state sweep, not an API poll. Once a day the module looks for charge attempts that never reached a final state and tells you about them. It does not query the three gateway APIs to compare balances. You resolve those attempts from the gateway dashboard.
- No card details ever reach your server. That is a feature, but it has a consequence worth stating: your staff cannot type a customer's card into Perfex. Customers add their own payment methods from the client portal, on the gateway's own hosted page.
This is an independent third-party module for Perfex CRM. It is not published by, affiliated with or endorsed by the Perfex CRM authors, and it is not affiliated with Stripe, GoCardless or Paystack.
2. Requirements
| Requirement | Detail |
|---|---|
| Perfex CRM | 3.4.x (the module declares Requires at least: 3.4.*) |
| PHP | 8.1, 8.2, 8.3 or 8.4 |
| Database | The MySQL/MariaDB server your Perfex already runs on. Tables are created with your install's own character set and collation. |
| HTTPS | Required. Gateways will not deliver webhooks to a plain-HTTP endpoint, and card capture redirects back to your site. |
| A working Perfex cron | Mandatory. See the warning below. |
| Outbound HTTPS | Your server must be able to reach api.stripe.com,
api.gocardless.com or api.paystack.co. Locked-down hosting that blocks
outbound connections cannot charge anything. |
| Working e-mail | Perfex's own SMTP settings. Every dunning notice, receipt and confirmation link is sent through Perfex's mail system. |
| A gateway account | At least one of Stripe, GoCardless or Paystack, with API keys. See Gateway setup. |
Nothing is collected without the Perfex cron. Automatic charges, retries, dunning e-mails, card-expiry warnings, webhook catch-up and the daily reporting snapshot all run inside the standard Perfex cron job. If the cron is not running, the module is silent: no charges, no e-mails, no errors — which is exactly what makes a dead cron so expensive. Set it up first (step 1 of the checklist), and keep an eye on the Cron Health panel on the dashboard.
3. Installation
- In Perfex, go to Setup → Modules.
- Click Upload Module and choose the ZIP file from your purchase download.
(If your host rejects the upload because of file-size limits, unzip the archive and upload the
autobillfolder intomodules/over FTP instead — the folder name must stay exactly as it is in the archive.) - The module appears in the list. Click Activate.
- Reload the admin area. A new sidebar entry named Recurring Payments appears between Sales and Subscriptions, with Dashboard, Subscriptions, Plans, Payment Methods, Charge Attempts, Dunning, Reports, AR Aging and Settings under it. Two more entries are conditional: Late Fees is shown to staff who may manage the module's settings — the switch that enables late fees lives on that screen, so hiding it would put the feature out of reach — and to everybody else only once late fees are on. Renewals does not appear at all until renewals are enabled.
What activation creates
| Created | Detail |
|---|---|
| 13 database tables | All prefixed autobill_ after your own table prefix:
payment methods, plans, schedules, charge attempts, gateway events, dunning policies, the
dunning log, coupons, usage records, MRR snapshots, cron state, late fees and tracked
renewals. The last two are created empty and stay empty until you switch their feature
on. |
| 33 settings | Written into Perfex's own options table, every one with a working default. The ones you would change are on the module's Settings screen, except the eleven late-fee options, which are edited on the Late Fees screen's own settings page. Every setting that turns a version 1.1 behaviour on is written at its off value — see Upgrading from 1.0. |
| 11 e-mail templates | Seeded into Setup → Email Templates, inside the Invoice group, all named with the Recurring Payments: prefix — Dunning 1 (Payment Failed), Dunning 2 (Payment Failed Again), Dunning 3 (Final Notice), Card Expiring Soon, Payment Authentication Required, Payment Receipt, Payment Authorization Cancelled, Upcoming Payment Notice, and the three added in 1.1: Service Suspended, Service Terminated and Renewal Reminder. All eleven are plain Perfex templates — edit the wording, the subject and the language versions exactly as you would any other. The last three are only ever sent once you have turned on the feature behind them. |
| 1 dunning policy | Named Default, with the schedule described under Dunning. It is used by every subscription that does not name another policy. |
| 7 staff capabilities | Available immediately in Setup → Staff → Roles. See Permissions. |
| 3 payment gateways | Three entries appear in Setup → Settings → Payment Gateways: Stripe - Card for Recurring Payments, GoCardless - Direct Debit for Recurring Payments and Paystack - Card for Recurring Payments. They are inactive and empty until you enter credentials, and they are separate from any Stripe or PayPal gateway you already had. |
Re-activating is safe. Every table creation is guarded, settings are only written when they do not already exist, an e-mail template is only seeded when its slug is missing, and the default policy is only created when the policy table is empty. Deactivating and activating again never resets your configuration and never duplicates anything.
What "Deactivate" keeps
Deactivating stops the module without destroying anything:
- All tables, settings, stored payment authorizations, subscriptions and history stay exactly as they are.
- The cron work stops. Nothing is charged, no dunning e-mail goes out, no snapshot is written.
- The public routes (webhook endpoints, client-portal pages, guest links) stay reachable but
answer
503 Service Unavailable. That is deliberate: gateways treat 503 as "try again later" and re-deliver those webhooks once you re-activate, instead of dropping them. - The admin menu and the customer-area menu item disappear.
Re-activating picks everything up where it left off. Removing the module is a separate action with its own rules — see Uninstalling.
4. First run checklist
Five things, in this order. The last one tells you whether the first four worked.
4.1 Set up the cron
- Go to Setup → Settings → Cron Job. The Command tab shows the exact
command for your install, in the form
wget -q -O- https://your-perfex-site/cron/index(with your cron key appended when yourapp-config.phpdefines one). - Add that command to your server's crontab so it runs every 5 minutes. Perfex throttles itself internally, so running it often is safe and makes charges and retries happen promptly.
- For an immediate first test, administrators can click Run Cron Manually on the same screen.
- Open the module's Dashboard and look at the Cron Health panel. Once the cron has run, it lists the module's eleven jobs with their last run, last success, run count and error count.
The eleven jobs, in the order they run: catch-up, due charges, retry queue, dunning e-mails, terminations, expiring cards, unhandled webhooks, late fees, renewals, reconcile, MRR snapshot. One failing job never stops the others — it records its error and the rest continue.
Three of those are new in 1.1, and on an install that has opted into nothing all three run, record a successful run, and do nothing. Late fees and renewals each read their own switch first and return immediately while it is off. Terminations has no switch of its own: it looks for subscriptions that a dunning policy suspended under the suspend, then terminate action and that have sat there longer than the waiting period, and until you point a policy at that action there are none to find.
4.2 Enter gateway credentials
Go to Setup → Settings → Payment Gateways and open the entry you need: Stripe - Card for Recurring Payments, GoCardless - Direct Debit for Recurring Payments or Paystack - Card for Recurring Payments. Enter the keys, tick Active, and list the currencies that gateway may take.
Per-gateway keys, webhook events and sandbox notes are on the Gateway setup page.
4.3 Create the webhook endpoint
Webhooks are how the gateway tells you what really happened — a bank debit that confirmed three days later, a 3-D Secure challenge the customer finally passed, a dispute, a card the network auto-updated. Without a webhook the module still charges, but it will not learn about anything that resolves after the API call returns.
- Open Settings → Diagnostics in the module. Each gateway block shows a read-only
Webhook URL field — copy it. The shape is
https://your-perfex-site/autobill/autobill_webhook/stripe(and…/gocardless,…/paystack). - In the gateway dashboard, create an endpoint pointing at that URL and subscribe it to the events listed on the Gateway setup page.
- Paste the endpoint's signing secret back into that gateway's settings in Perfex. Paystack is the exception: it signs webhooks with your secret key, so there is no separate secret to paste and the Diagnostics screen does not ask for one.
An endpoint without its signing secret is worse than no endpoint: every delivery will be rejected as unverified, and the module will never confirm those payments. Diagnostics flags a missing secret in red.
4.4 Check your currencies
This is the single most common reason a correctly configured install charges nothing, so it gets its own step. Perfex hides a payment gateway from an invoice whose currency the gateway does not list. If your invoices are in EUR and the gateway's Currencies field says only USD, then:
- the customer never sees a Pay button for that gateway, and
- the module refuses to charge that invoice at all — it stops before any money moves and notifies your staff once a day rather than once per invoice.
Diagnostics compares your system currencies against each gateway's list and prints "Not enabled for: …" when they disagree.
4.5 Read the Diagnostics screen
Settings → Diagnostics is the screen to open whenever "nothing is charging". It collects everything that can silently stop collection in one place.
| What it says | What it means | What to do |
|---|---|---|
| Unprocessed webhooks | Gateway events that were received and stored but not yet processed. | A small number is normal between cron runs — the "unhandled webhooks" job drains them. A number that never falls means those events keep failing; they are retried up to five times and then abandoned with one staff notification. |
| Stuck attempts | Charge attempts that were opened more than an hour ago and never reached a final state. | Usually a webhook that never arrived. Bank debits legitimately sit in transit for days. The daily reconcile job raises a staff notification once an attempt has been unresolved for more than 26 hours; resolve those from the gateway dashboard. |
| Base currency | Your Perfex base currency, for comparison with the gateway lists below. | Nothing, unless a gateway does not list it. |
| Enabled / Not configured (per gateway) | Whether that gateway is switched on in Perfex's payment-gateway settings. | A gateway that is not active cannot be offered to customers, and a plan invoice built while it is inactive is created with no payment mode set on it at all — see section 6.3. |
| API credentials: Set / Missing | Presence only. Keys are never decrypted, printed or logged — the screen is safe to screenshot. | Missing means no charge can be sent. Enter the keys. |
| Webhook signing secret: Set / Missing | Whether the endpoint secret has been pasted in. Not shown for Paystack, which has none. | Missing means confirmations will never arrive. See step 4.3. |
| Webhook URL | The exact endpoint to register in the gateway dashboard. | Copy it from here rather than typing it. |
| Enabled currencies | The gateway's own currency list, plus any of your system currencies missing from it. | Add the missing codes in the gateway settings, or stop invoicing in them. |
| Engine self-check | The charging engine's own view: per-gateway configuration, cron age against your alert threshold, the eleven jobs with run and error counts, unprocessed events, stuck attempts, subscriptions waiting for a payment method, and stored methods grouped by status. | "never ran" or a stale age means the cron is not reaching the module. Subscriptions needing a payment method need the customer, not you — see the portal. |
4.6 Optional: review the general settings
The late-fee options are not on this screen. They live on the Late Fees screen's own settings page, behind the Late fee settings button, so that one feature has one write path — see section 8.3.
| Setting | Default | What it does |
|---|---|---|
| Auto-charge every recurring invoice when the customer has a saved default payment method | Off | On: every recurring invoice Perfex generates is collected automatically as soon as the customer has a stored default method — no per-invoice subscription needed. Off: only the recurring invoices you explicitly attached. |
| Send receipt e-mail on successful charge | On | Perfex already sends its own payment confirmation; leave this on only if you want the module's receipt as well. |
| Warn when a customer's currency is not enabled on the gateway | On | Surfaces the currency trap described in 4.4. |
| Card-expiry warning e-mails (days before) | 30,7 |
One warning per listed day, per card. Empty sends none. |
| Authentication link lifetime (hours) | 72 | How long a 3-D Secure confirmation link stays usable. |
| Catch-up window (days) | 7 | After downtime, how far back the module looks for work it missed. |
| Warn when the cron has not run for (hours) | 26 | Past this, the dashboard shows a red banner and staff are notified. |
| GoCardless advance notice | GoCardless sends it | Locked. See the limitations in section 1. |
| Record GoCardless payment in Perfex when | Payment is confirmed | "Confirmed" marks the invoice paid when GoCardless confirms collection; "paid out" waits until the money reaches your bank — later, but more conservative. |
| Paystack card-verification amount | 5000 minor units |
Paystack has no setup-only flow, so saving a card without an invoice runs a small verification charge. 5000 = 50.00 NGN. |
| Renewals — the whole block is inert while the first row is off. See section 9. | ||
| Send renewal reminders and process renewals | Off | The switch for renewal tracking. While it is off the renewal sweep does not run: no reminder is e-mailed and no renewal invoice is raised, whatever an individual renewal is set to. The Renewals menu entry is hidden until it is on. |
| Default reminder days for new renewals | 60,30,14,7,1 |
The starting value on the renewal form. Each renewal can override it. |
| Renewal invoice line description | Empty | The line wording on an auto-raised renewal invoice. {label} and
{identifier} are substituted. Left empty, the module uses its own translated
wording, which is why the shipped value is empty rather than an English sentence. |
| Suspension & termination — see section 7.8. | ||
| E-mail the customer when a subscription is suspended or terminated | Off | While off, suspension and termination still happen and your staff are still notified — the customer is not e-mailed. Turn it on and the Service Suspended and Service Terminated templates are used. |
| Terminate a suspended subscription after this many days | 30 | Only used by a dunning policy whose terminal action is Suspend the subscription, then terminate it. No policy uses that action until you choose it. It sets how long the suspension lasts, not whether termination happens — to suspend without ever terminating, give the policy the Suspend the subscription action instead. |
| AR aging report — see section 11.4. | ||
| Show accrued late fees on the aging report | Off | Adds one column. Accrued fees are shown on their own and are never added into the outstanding total. |
| Create invoices as staff member | First active staff member | Which staff member owns the invoices the plan mode generates. |
| Delete ALL data when the module is uninstalled | Off | See Uninstalling. |
5. Mode 1 — auto-charging your existing recurring invoices
Nothing about your recurring invoices changes. Perfex still generates them on its own schedule, with your numbering, your items and your terms. The module simply collects each one.
5.1 The customer saves a payment method
This step can only be done by the customer, in the client portal. No screen in the admin area accepts card details, and that is not an oversight: keeping card data off your server is what keeps your PCI obligations small.
- The customer logs into the client portal and opens Billing & Cards in the customer-area menu.
- They click Add payment method and choose how they want to pay — credit/debit card, or bank debit where GoCardless is configured. Only gateways that can settle the customer's currency are offered.
- They tick the authorization statement ("I authorize your company to automatically charge my saved payment method for recurring invoices until I cancel this authorization"). It is mandatory.
- They are sent to the gateway's own hosted page to enter the details, and come back to your site afterwards.
What is stored on your server: the gateway's reusable token, the brand, the last four digits, the expiry month and year, and a snapshot of the authorization the customer accepted (the exact wording, the version, their IP address, their browser and the timestamp). The card number and the security code never touch Perfex.
Staff can see the stored methods — never the numbers — on Payment Methods, filter them by customer, gateway or status, set a customer's default, revoke one at the gateway, or delete one. Revoking or deleting a method releases the subscriptions that used it, and they then wait for a new one. A suspended subscription is released too, but keeps its suspended status: detaching a card is not a decision about collection, and it does not undo one.
5.2 Create the schedule
- Go to Subscriptions → New Subscription.
- Choose the mode Auto-charge recurring invoice.
- Pick the recurring invoice. The list only offers recurring invoices that are not cancelled, not draft, and do not already have a subscription — one recurring invoice, one subscription.
- Pick one of that customer's saved payment methods. If they have none, save anyway: the subscription is created with the status Needs Payment Method and starts working by itself the moment the customer adds one.
- Optionally set Collect this many days before the invoice due date. Leave it at 0 for cards; bank debit schemes need a few days of notice.
- Optionally choose a dunning policy other than the default, and a cycle limit.
The alternative is the blanket switch: turn on Auto-charge every recurring invoice… in Settings and every recurring invoice belonging to a customer with a stored default method is collected automatically, with the subscription created for you the first time it fires.
5.3 What happens when the invoice generates
Perfex generates recurring invoices during its cron, at the hour set in Setup → Settings → Cron Job → Invoice. The moment a child invoice is created, the module takes over:
- It finds the subscription attached to the parent recurring invoice.
- It skips the charge entirely if the subscription is cancelled, completed or paused — or if the customer asked to cancel at period end and this is that period end, in which case the subscription is closed instead of billed.
- It resolves the payment method: the one on the subscription, or the customer's default if that one is no longer usable. With none, the subscription flips to Needs Payment Method and nothing is charged.
- It checks the clock. Charges are meant to land in the customer's local small hours (2 a.m. by default, set per dunning policy). Inside a one-hour window of that time it charges immediately; outside it, the invoice is queued for the next occurrence of that hour and the regular cron sweep collects it.
- It charges the amount still left to pay on the invoice — a part payment already recorded is never collected twice.
5.4 Where the result appears
Every charge, successful or not, produces one row on Charge Attempts: the invoice, the customer, the gateway, the attempt number, the amount, the status, the decline code and message where there is one, the gateway transaction id, the next retry time, and the timestamps.
| Attempt status | Meaning |
|---|---|
| Succeeded | Money collected. A Perfex payment exists on the invoice with the gateway transaction id, and the invoice status is recalculated by Perfex as usual. |
| Failed | Declined. The invoice is untouched — same number, same amount — and dunning has taken the case. |
| Requires Authentication | The bank asked for 3-D Secure. The customer has been e-mailed a confirmation link. |
| In Transit | Normal for bank debit: submitted and travelling. It settles days later, over a webhook. |
| Pending / Processing | Sent, but the outcome is not known yet — usually a network problem mid-request. The module re-drives it safely under the same idempotency key. |
| Cancelled | A queued retry that was stood down because the invoice got paid another way. |
| Charged Back | A dispute arrived over a webhook. The Perfex payment is deliberately left alone — reversing money is a human decision — and your staff are notified. |
Staff with the Charge Manually capability get a Retry now action on failed and awaiting-authentication attempts, and a Charge now button on the subscription itself.
6. Mode 2 — subscription plans
6.1 Create a plan
Plans → New Plan:
| Field | Notes |
|---|---|
| Plan name, description | The description becomes the invoice line's long description when the plan has no items. |
| Currency and amount | The recurring price. A subscription may override the amount for one customer. |
| Setup fee | Added as its own invoice line on the first cycle only. |
| Billing interval | Day(s), Week(s), Month(s) or Year(s), plus "Repeat every N" — so "every 3 months" or "every 2 weeks" are both plans. |
| Trial (days) | Nothing is charged during the trial; the first charge falls on the day the trial ends. A subscription can override this per customer. |
| Pricing model | Flat rate. The plan screen sells flat-rate plans only. |
| Taxes | Applied to every line of the generated invoice. |
| Invoice items | Pick existing Perfex items and they become the invoice lines every time the plan bills, with their own rates and taxes. With no items, the invoice gets a single line named after the plan, at the plan's amount. |
| Active | Inactive plans stay attached to existing subscriptions but are not offered for new ones. |
| Visible in client portal | Reserved for a future release; there is no customer self-signup page. |
6.2 Subscribe a customer
Subscriptions → New Subscription, mode Subscription plan: choose the customer, the plan, a start date, an optional trial override, a quantity, a cycle limit (0 = unlimited), a dunning policy and a payment method.
The billing anchor is the day the first charge is owed: the trial end date when there is a trial, otherwise the start date. The subscription is created as Trialing, Active, or Needs Payment Method when no usable method is attached yet.
Customer and mode are fixed once a subscription exists — everything downstream (invoices, attempts, authorizations) is already tied to them. Everything else stays editable: payment method, amount, quantity, cycles, dunning policy and, for plan subscriptions, the next charge date.
6.3 What the generated invoice looks like
When a plan subscription comes due, the module builds a normal Perfex invoice before it charges anything:
- one line per plan item, multiplied by the subscription quantity — or a single line named after the plan when it has no items;
- a Setup fee line on the first cycle only;
- the plan's taxes on every line;
- your Perfex defaults for terms, client note and numbering;
- the due date from your invoice due after setting;
- the customer's billing address, copied as Perfex does it;
- owned by the staff member named in Settings → General.
The invoice's allowed payment mode is restricted to the module's own gateway when that gateway is active and enabled for the invoice currency, so the customer's "Pay now" button uses the same rails. When it is not — the gateway is switched off, or the invoice's currency is not on its list — the module sets no payment mode on the invoice at all, and Perfex reads an empty list as "offer nothing": the customer sees no Pay now button and the PDF carries no payment block.
That is a state to fix rather than to live with. Activate the gateway and add the currency to its list, or open the invoice and tick the payment modes you want offered. The Diagnostics screen tells you which of the two it is, per gateway, before a customer runs into it.
The document is created first, and the period advances at that moment — not when the money arrives. That is what guarantees a decline can never produce a second invoice for the same period. Retries re-use the invoice that already exists.
6.4 Cycles, pausing and cancelling
| Action | What it does |
|---|---|
| Charge now | Collects the subscription's open invoice immediately. If a plan subscription has no open invoice, the next one is built from the plan and charged straight away. Requires the Charge Manually capability. If the engine refuses (velocity cap, currency block, unconfigured gateway) the screen says so instead of claiming a charge was made. |
| Pause | Stops collection, optionally until a date. Nothing is charged while paused. Perfex keeps generating recurring invoices in mode 1 — pausing stops the collection, not the invoicing. |
| Resume | Puts a paused subscription back to work. |
| Cancel at period end | The subscription keeps running and stays chargeable until its current paid period closes. At the exact moment the next period would be billed, it is closed instead. The customer keeps what they paid for; you never bill a period they did not want. |
| Cancel immediately | Closes the subscription now, clears the next charge and stops all queued retries. Invoices that are already issued stay issued — cancelling a subscription is not a credit note. |
Cycles. Each billed period increments the cycle counter. With a cycle limit set, the subscription is marked Completed as soon as the last cycle is billed, and nothing further is charged. With a limit of 0 it runs until somebody cancels it.
What closes a subscription automatically: its cycle limit is reached; its plan or its customer is deleted; a dunning policy's terminal action cancels it; or the parent recurring invoice is deleted in mode 1.
7. Dunning — what happens after a decline
Dunning is the sequence of retries and messages between "the charge failed" and "the money arrived or we stopped". A policy defines that sequence; every subscription uses one, and the seeded Default policy is used by anything that does not name another.
7.1 What the default policy does, day by day
The seeded values: retries on days 1, 3, 5, 7, 10, 14; maximum 7 attempts; give up
after 21 days; charge at 02:00 in the customer's local time; e-mails on days
0, 3, 7, 14, 21; terminal action Mark subscription past due; notify staff on.
| Day | The module | The customer |
|---|---|---|
| 0 | Attempt 1 — the original charge — fails. The subscription becomes Past Due. | First notice, on the next cron run: friendly, names the amount and the invoice, says when the next attempt is, and links to the page where they can update their payment method. |
| 1 | Attempt 2. | — |
| 3 | Attempt 3. | Second notice: firmer, names the decline reason in plain language. |
| 5 | Attempt 4. | — |
| 7 | Attempt 5. | Second notice. |
| 10 | Attempt 6. | — |
| 14 | Attempt 7 — the last one the cap allows. When it fails, the terminal action runs immediately. | Second notice. |
| 21 | The give-up day: if the terminal action has not already run, it runs here. | Final notice: this is the last automatic message. |
Three details that matter more than they look:
- Every offset is measured from the first failure, never from "whenever the last step happened to run". A cron outage does not stretch a 14-day sequence into a month.
- At most one customer e-mail per invoice per cron run. After an outage several steps may be due at once; only the latest is sent and the skipped ones are closed out in the ledger, so three notices never land in the same inbox minute.
- Every step is claimed before it is sent. A customer can never receive the same dunning step twice, no matter how often the cron runs.
Escalation is mapped over your offsets rather than hard-coded: the first offset gets the soft
notice, the last gets the final warning, everything between gets the formal reminder. Trim
[0,3,7,14,21] down to [0,7] and you still get soft → final.
7.2 When the sequence does not apply
The retry schedule is for soft declines. The classifier described in section 1 can take a case out of the sequence at any point: a hard decline stops retries and asks for a new card, a 3-D Secure decline stops retries and sends a confirmation link, a dead mandate stops everything and notifies staff. In those cases the specific message replaces the generic step that was due, so the customer gets one accurate e-mail rather than two contradictory ones.
For GoCardless there are no module retries at all. The e-mail schedule and the terminal action are the whole sequence, and the terminal action fires on the give-up day.
7.3 Editing a policy
Dunning lists your policies with how many subscriptions use each, and the dunning log underneath. Open one, or create a new one:
| Field | Notes |
|---|---|
| Retry schedule (days after failure) | Comma-separated whole days, counted from the failed charge. Day 0 is the original charge, so retries start at 1. |
| E-mail schedule (days after failure) | Comma-separated. Day 0 — the same day — recovers the most. |
| Maximum attempts | Counts the original charge. Capped at 8: beyond that, issuers start reading the retries as fraud and the card networks bill for the excess. |
| Give up after (days) | 1–365. The day the terminal action runs and collection stops. |
| Charge at hour (customer local time) | 0–23. Retries and deferred charges land at this hour in the customer's timezone. |
| When all retries fail | The terminal action — see below. |
| Notify staff | Sends one staff notification when collection ends without payment. |
| Default policy | Making this the default clears the flag on every other policy. There is always exactly one, and the last remaining policy cannot be deleted. |
7.4 The live preview
The Preview button re-draws the timeline from the values currently in the form without saving anything, so you can see the sequence before you commit to it. It draws every day in the sequence, marks each attempt and each e-mail, and greys out the steps that will never run. It also warns you about the mistakes that are invisible when the three limits are read separately:
- the retry list plans more attempts than the maximum allows — the extra retries never run;
- steps fall after the give-up day — collection stops before they arrive;
- no retry days at all — a failed charge is never retried automatically;
- no e-mail days at all — the customer is never told the payment failed.
Preview writes nothing, so it is available to anyone who can read the screen; saving needs the create or edit capability.
7.5 Terminal actions
| Action | What it does |
|---|---|
| Mark subscription past due (default) | Leaves the subscription past due, stops the retries, and asks Perfex to recalculate the invoice's own status (unpaid → overdue). The safest choice: nothing is destroyed and a later payment revives everything. |
| Pause subscription | Stops collection and future periods until you resume it. |
| Cancel subscription | Closes the subscription. Issued invoices stay. |
| Suspend portal access (aggressive) | Deliberately softened: it cancels the subscription and notifies staff. The module never switches off a customer's Perfex login — that login is not ours, disabling it would also lock them out of the one page where they could fix their card, and nothing would ever switch it back on. A human decides about the login. |
| Suspend the subscription (new in 1.1) | Moves the subscription to Suspended. Collection stops, and it is reversible — a payment on the invoice brings it back by itself. See 7.8. |
| Suspend the subscription, then terminate it (new in 1.1) | The same suspension, plus a deadline: if the invoice is still unsettled after Terminate a suspended subscription after this many days (30 by default), the subscription is moved to Terminated, which is final. See 7.8. |
Exactly one terminal action runs per invoice, whichever path reaches it first, and it is recorded in the dunning log next to the e-mails.
The dunning log records every step: invoice, step number, channel, template, timestamp and whether it was sent successfully. Filter it by invoice id when a customer asks what you sent them. A mail server that is down is recorded on the step and the sequence continues — one failed e-mail never stalls the rest.
7.6 Card-expiry warnings
The cheapest recovery is the one that costs no failed charge at all. Once a day the module looks
for stored cards approaching their expiry date and warns the customer, using the thresholds in
Settings (30,7 by default: one e-mail a month before, one a week before).
One e-mail per threshold per card, and at most one per sweep — a card five days from expiry that was never warned gets the 7-day notice only, not both at once. Bank debit mandates have no expiry date and are not part of this sweep. Cards that the card network auto-updates are picked up over a webhook; if the network hands back a card on a different brand, the module treats the stored authorization as no longer describing what it would charge, marks it as needing re-consent and tells your staff.
7.7 Velocity protection
Independently of any policy, the module refuses to send more than 8 charge attempts against the same stored payment method within 14 days, across all invoices. Past that it stops, treats the case as "contact the customer", and notifies your staff. This ceiling exists because acquirers are fined for hammering a dead card, and no policy setting can raise it.
7.8 Suspension and termination
Version 1.1 adds two terminal actions and, with them, two subscription statuses. Neither is used by any existing policy: you choose one on the policy, and the seeded Default policy still ends at Mark subscription past due.
This suspends billing, not access. The module does not switch off the customer's Perfex login, does not disable their contact, and does not close their portal account. That was a version 1.0 decision and 1.1 keeps it: the portal is the one place the customer can replace the card that failed, and a module that locked them out would be locking them out of the fix. If you want to withdraw the service itself, that is your action to take, in whatever system delivers it.
The subscription statuses go from seven to nine:
| Status | What it means | How it ends |
|---|---|---|
| Suspended | Collection has stopped: the due-charge sweep only serves active and trialing subscriptions, so a suspended one is not picked up by it, and every queued retry against the invoice that caused the suspension is disarmed at the same moment. It is not cancelled — nothing is written to the cancellation fields, and on a plan subscription the next-charge marker is left where it was, so lifting the suspension is cheap. Read the note under this table before you lift a long one. | Three ways. A payment that settles the invoice — taken by the module, by core's Pay button, or typed in by your staff — puts it back to Active, or to Needs Payment Method if the stored card is no longer on the subscription; a part payment does not. The Lift suspension button on the subscription, which needs the Edit capability. Revoking or deleting the stored payment method is not a third route: the card is detached, but the suspension stands until the invoice is settled or staff lift it. |
| Terminated | The end of the line. Reached only from Suspended, only through the suspend, then terminate action, and only after the waiting period. | It does not. Nothing in the module reinstates a terminated subscription. If the invoice is paid afterwards, the subscription stays terminated and your staff are notified so a human can decide whether the customer is coming back. |
Because Terminated is final, the Service Terminated e-mail deliberately carries no "update your payment method" link: it would imply that paying brings the subscription back, and it does not. The Service Suspended e-mail does carry one, because there paying does.
Three details worth knowing before you pick one of these actions:
- Termination is a separate cron job, and the only thing between a suspension and a termination is the waiting period you set. That figure is a delay, not a switch: if you want a policy to suspend and stop there, give it the suspend the subscription action rather than trying to disable the second half with the day count.
- A suspended customer keeps their stored payment method reserved. Suspended counts as a live subscription for that purpose, so the card is not treated as orphaned and is still there when the suspension lifts. A terminated subscription stops counting, as cancelled and completed ones do, so staff may then delete the card — nothing is detached at the gateway by the termination itself.
- The customer cannot lift a suspension from the portal. They can pause, resume and cancel their own subscriptions (section 10), but not a suspended one — a dunning suspension is your decision to reverse, not theirs. Paying the invoice still reverses it, which is the route they do have.
An invoice that is paid, cancelled or deleted while its subscription is suspended is never terminated — termination is a collection outcome, and there is nothing left to collect.
Two things to know before you suspend, and before you lift one.
On a plan subscription the next-charge date stays where suspension found it. If it is already in the past when you lift the suspension, the next sweep bills the periods that elapsed meanwhile — the same convention pause and resume follow, and it is written to the log. On a long suspension that can be several charges at once, so look at the charge date before you lift it.
If the subscription rides on one of your Perfex recurring invoices rather than on a plan of ours (mode 1), Perfex's recurring-invoice generator is core's, not the module's, and suspending here does not stop it. Stop or pause that recurring invoice in Perfex as well.
The customer is not told by default. autobill_suspend_notify_customer
ships off. While it is off, the status change is made, the dunning log records it and your staff are
notified, but no e-mail reaches the customer. Turn it on in Settings and the two
seeded templates are used. The waiting period before termination is the same screen's
Terminate a suspended subscription after this many days, which ships at 30.
8. Late fees
Late fees are off. Nothing accrues, nothing is calculated and no fee invoice is raised until you switch them on, and the switch is on the Late Fees screen's own settings page, behind the Late fee settings button. Switching them on does not backdate anything: the first sweep after you enable them accrues one period, not the months an invoice has already been overdue.
8.1 The fee is a second invoice, never an edit
The rule the whole feature is built around: the overdue invoice is never written to. Not its lines, not its total, not its status. It is a numbered document the customer already holds a PDF of, and version 1.0 refused to touch one on exactly those grounds.
So the fee accrues in the module's own ledger — one row per overdue invoice — and when it is time to bill it, it becomes a separate Perfex invoice: its own number from your own numbering sequence, its own date, its own due date from your invoice due after setting, its own PDF. One line, at the accrued amount, in the same currency as the original, with no tax and no discount applied. The overdue invoice's number appears in that line's description and in both the client note and the admin note, so whoever reads either document can connect them.
The link between the two exists only on the module's side. That has two consequences worth stating plainly:
- Delete the fee invoice and the original is untouched. The accrual goes back to waiting for a document and can be invoiced again — unless you had already waived it after invoicing it, in which case it stays waived, because you told the customer it was forgiven.
- Delete the original and the accrual goes with it. No debt, no penalty.
Nothing in the module charges a fee invoice by itself. It is not a recurring invoice and it is not attached to a subscription, so no sweep opens a charge attempt on it and no stored card is debited for it.
Tick the payment modes before you send it. A fee invoice is created with an empty allowed-payment-mode list, and Perfex reads an empty list as "offer nothing" — the customer would see no Pay now button and the PDF would carry no payment block. Open the fee invoice, choose the modes you want offered, and save. After that it behaves like any other invoice: if one of those modes is a gateway from this module, the customer pays it from the portal in the ordinary way.
8.2 What accrues, and what does not
- Every overdue invoice in Perfex, not only the ones this module charges. The sweep selects on an invoice's own status and due date and has no reference to subscriptions at all, so a one-off invoice you typed by hand accrues on exactly the same terms. Set your rate and your grace period with your whole receivables book in mind.
- Only invoices Perfex marks Overdue or Partially Paid. Paid, Cancelled and Draft invoices are not debts and never accrue.
- The fee is calculated on what is still outstanding, not on the invoice total. That figure is core's own "left to pay", so recorded payments and applied credit notes both count against it. A customer who has paid ninety per cent of an invoice owes a fee on the remaining ten.
- Nothing accrues inside the grace period. The invoice has to be more than Grace period days past its due date, counted with core's own overdue figure.
- At most one accrual per invoice per period, whatever the cron does. Running the sweep three times in one day accrues once. Running it after a missed day does not skip a period.
- An invoice paid down to nothing stops accruing on the spot, even if Perfex has not yet recalculated its status.
- Accrual has to end before the fee can be billed. One overdue invoice carries at most one fee invoice, so the fee is only ever documented once its accrual is closed. Four things close it: a once only fee has fired; the cap is reached; the maximum number of accruals is reached; or the debt is settled, cancelled or returned to draft. This is why a repeating fee with no cap still needs a maximum accrual count — without either, it would go on accruing and could never become an invoice.
- A fee on a debt you voided is dropped, not billed. If the original invoice is cancelled or moved back to draft, the accrual is cancelled. If it is paid late, the penalty stands.
8.3 The settings
Late Fees → Late fee settings, behind the Manage Settings capability. These eleven options are not on the module's general Settings screen: one feature, one write path.
| Setting | Default | What it does |
|---|---|---|
| Charge late fees on overdue invoices | Off | The master switch. While it is off nothing accrues and nothing is invoiced automatically. The one action that still works with it off is Invoice now on a fee that accrued while it was on — see 8.4. |
| Fee type | Percentage of the outstanding balance | Percentage, or a fixed amount. |
| Percentage per accrual | 0.00 |
Used when the fee type is percentage. Accepted range 0–100. It ships at zero, so enabling the feature without setting a rate computes a fee of nothing. |
| Fixed amount per accrual (minor units) | 0 |
Used when the fee type is fixed. Minor units: 500 is 5.00 in a two-decimal
currency, and 500 in a currency with no decimals. |
| Cap total fees at this percentage of the invoice | 0 (no cap) |
Measured against the original invoice's total, across everything accrued so far, so no sequence of cron runs can exceed it. Accepted range 0–1000. |
| Grace period (days past the due date) | 7 |
Nothing accrues until the invoice is overdue by more than this. Accepted range 0–365. |
| How often it accrues | Once only | Once only, daily, weekly or monthly. |
| Maximum number of accruals | 12 |
After this many accruals the fee closes and can be invoiced. 0 removes the
limit — only do that with a cap set, or the fee never closes and never becomes an
invoice. |
| Raise the fee invoice automatically | Off | A second switch, also off. While it is off, fees accrue and wait for a staff member to press Invoice now. |
| Minimum accrued fee before invoicing (minor units) | 0 — no
minimum |
Applies to automatic invoicing only. 0 invoices any amount above nothing; a fee
that accrued to zero is never invoiced whatever this is set to. Useful for not raising a
document for a rounding error. |
| Invoice line description | Late payment fee for invoice {invoice_number} |
What the customer reads on the fee invoice. {invoice_number} is replaced with the
overdue invoice's number. It cannot be saved empty. |
8.4 The screen and its actions
Late Fees lists every accrual with its overdue invoice and due date, the customer, the outstanding balance the fee was calculated on, the amount accrued, how many times it has accrued, the date of the last accrual, and the fee invoice once one exists. Filter it by status or search by invoice number or customer.
| Status | Meaning |
|---|---|
| Accruing | Still adding. Not yet billable. |
| Ready to invoice | Accrual has ended; the fee is waiting for its document. |
| Invoiced | Its own invoice exists. |
| Waived | Written off by a staff member. Accrual stops. |
| Cancelled | Dropped — the debt behind it was voided, or a staff member cancelled the fee. It leaves the aging report. |
| Action | What it does | Capability |
|---|---|---|
| Invoice now | Raises the separate fee invoice for whatever has accrued so far. It works on a fee that is still accruing as well as on a closed one, and it keeps working after you have switched the feature off again — the accrual already happened and a staff member is asking for its document. Two staff pressing it at the same second still produce one invoice. | Create |
| Waive | Stops the accrual and writes off the fee, recording who did it and when. An already issued fee invoice is not deleted by this — cancel or credit that invoice yourself. | Edit |
| Cancel | Stops the accrual and drops the row from the aging report. | Edit |
| Run accrual now | Runs the sweep immediately rather than waiting for the next cron pass, and reports how many rows accrued, closed and were invoiced. Because a fee accrues at most once per period, a manual run cannot double-charge. It refuses with a notice while the feature is off. | Create |
The sweep itself is the late fees cron job, once per cron pass, in three steps: accrue today's period, close the accruals that are over, then raise invoices for the ones that are closed — in that order, so a fee whose accrual ends today gets its invoice today rather than tomorrow. Both the accrual pass and the invoicing pass are bounded per run, so a large ledger drains across runs instead of stalling the cron.
9. Renewal and expiry tracking
A ledger of dated things Perfex does not model: domains, SSL certificates, hosting accounts and licences. It remembers the date, warns you and your customer before it, and can raise the renewal invoice.
Not contracts, estimates or proposals. Perfex core already sends its own expiry reminders for all three, on its own schedule and with its own settings. Tracking them here as well would mean your customer is told twice about one date, so this screen deliberately does not offer them.
Renewals are off. While Send renewal reminders and process renewals is off in Settings, the renewal sweep does not run at all — no reminder is e-mailed and no invoice is raised, whatever an individual row is set to — and the Renewals entry does not appear in the sidebar. Rows already tracked stay tracked and can still be rolled forward by hand.
9.1 What a tracked renewal holds
| Field | Notes |
|---|---|
| Name | What the customer would call it — the domain, the certificate, the licence. |
| Identifier | Optional reference, up to 191 characters. |
| Type | Domain, SSL certificate, Hosting, Licence or Other. |
| Customer | Required. The reminder goes to that customer's first active contact who receives invoice e-mails, and to their first active contact with an address if none is flagged. |
| Start date, Expiry date | The expiry date is what everything is measured from. |
| Term (months) | 1–600. How far forward a renewal rolls the expiry date. |
| Renewal cost and currency | What a renewal invoice would be for. Zero means no invoice can be raised for this row. |
| Remind this many days before expiry | A comma-separated list. Defaults to the list in
Settings (60,30,14,7,1); each row can override it. |
| Linked subscription | Optional, and only a subscription belonging to the same customer. |
| Auto-renew, Auto-invoice | Both off. See 9.3. |
| Internal notes | Staff-only. |
Statuses are Active, Expiring, Expired, Renewed and Not tracked.
9.2 What the sweep does
Once per cron pass, when the feature is on, the renewals job:
- reads rows that are Active or Expiring and expire within the next 400 days — a ceiling that keeps one pass bounded, so an install tracking thousands of rows still reaches the ones that are actually due;
- works out the most urgent reminder threshold each row has crossed and not yet fired;
- claims that threshold before anything is sent, so one reminder per threshold is guaranteed by the record rather than by the e-mail — a failed send is logged and not retried, because a second copy of the same reminder is worse than a missing one;
- e-mails the customer with the Recurring Payments: Renewal Reminder template, notifies your staff, and moves the row from Active to Expiring — unless auto-renewal takes the row over instead, which is 9.3;
- on the expiry date itself, moves the row to Expired and notifies your staff once. An expired row leaves the sweep, which is what stops the notice repeating.
Start the reminder sequence again on a row clears which thresholds have been sent, so every threshold still ahead of the expiry date can fire once more.
9.3 Auto-renewal, and its three locks
A renewal invoice is only ever raised when all three of these are true: the install-wide switch is on, and the row's Auto-renew box is ticked, and its Auto-invoice box is ticked. Both boxes ship unticked, and with either of them unticked the row only sends reminders.
When all three line up, then at the first threshold the row crosses, the module:
- raises one invoice — a single line at the renewal cost, in the asset's own currency, no tax and no discount, with the term it covers printed as the line's long description;
- rolls the expiry date forward by the term, marks the row Renewed and clears its reminder history;
- notifies your staff, with a link to the invoice.
A Renewed row is out of the sweep. Somebody has to put it back for the next term, and that is deliberate: it makes an unattended loop of invoices impossible rather than unlikely.
Three things this does not do:
- It does not collect the invoice. Nothing in the renewal path charges a stored payment method. The invoice is raised and then behaves like any other Perfex invoice.
- It does not guess a price. With no renewal cost, a deleted currency or a deleted customer, no invoice is raised, the reason is written to the module's log, and the row falls back to sending the ordinary reminder.
- It does not renew anything in the outside world. No registrar, no certificate authority, no control panel is contacted. The date and the document are all this owns.
Mark renewed, the staff button, rolls the term forward and restarts the reminder sequence. It raises no invoice — if you want one, raise it in Sales.
10. The client portal
The module adds one item, Billing & Cards, to your customer-area menu.
10.1 What a logged-in customer can do
- See their subscriptions: plan, amount, status, next payment date and which payment method will be used.
- See their stored payment methods: brand and last four digits, expiry, status, and which one is the default.
- Add a payment method through the gateway's hosted page (section 5.1).
- Update one: this starts a fresh capture and then moves every live subscription from the old method to the new one, carrying the default flag across — "update card" actually changes what gets charged.
- Remove one: refused while an active subscription still uses it, and the token is detached at the gateway before the row is deleted, so nothing keeps living at the gateway that you can no longer see.
- See their payment history: the last 100 charge attempts on their invoices. This page is only shown to contacts who have the Perfex invoices permission, because it contains invoice numbers and links.
When any of their subscriptions is past due, a banner at the top asks them to update their payment method, linking straight to the capture flow.
10.2 The two guest links
Both links are reached from an e-mail, without a portal login, and are authorized by an unguessable 64-character secret in the URL — the same approach Perfex itself uses for invoice and estimate links.
| Link | What it opens | How long it lasts |
|---|---|---|
Subscription self-service/autobill/clients_autobill/subscription/<hash> |
A standalone page for one subscription: plan, amount, status, next payment. The customer can pause it for 1–3 months, resume it, or cancel it. Cancelling always passes through a retention step first — the pause offer is shown before the confirm button exists — and a customer-initiated cancellation is always at period end: they keep what they have paid for and nothing further is charged. | Does not expire. The link is the subscription's own identifier and stays valid for as long as the subscription row exists. A closed subscription still opens the page, read-only. |
3-D Secure re-authentication/autobill/clients_autobill/authenticate/<token> |
The bank's confirmation step for one charge attempt. Stripe runs the challenge inside its own secure window on the page; Paystack owns the whole flow and the customer is redirected to it. | Single use, and it expires after the Authentication link lifetime in Settings (72 hours by default). The token is stored hashed, so a database copy does not hand anyone a working link. |
An unknown link, an expired link and an already-used link all produce the same neutral "this link is no longer valid" page with a 410 status. Telling them apart would tell an attacker which tokens exist.
The confirmation page needs JavaScript, because the bank runs its check inside its own window. With JavaScript switched off, the page explains that and links the customer to the invoice's own server-rendered payment page, which works without it.
Confirming 3-D Secure does not by itself record money. The gateway's webhook is the only authority on whether the payment succeeded; the confirmation page only burns the link and moves the attempt out of "awaiting authentication" so dunning stops chasing it. This is why the webhook endpoint in step 4.3 is not optional.
11. Reports and the dashboard
11.1 The dashboard
The module's first screen answers three questions: how is recurring revenue doing, what needs a human, and is the machinery running.
- KPI row, one set per currency: MRR, ARR, active subscriptions, past due, recovered this month, involuntary churn over 30 days.
- Needs attention: past-due subscriptions, cards expiring soon, cancelled authorizations, and attempts awaiting 3-D Secure. Each block shows a preview and links to the full list.
- Cron health: the eleven jobs with last run, last success, run count and error count, plus a red banner when the cron has been silent longer than your alert threshold.
- Recent attempts and recent webhook events: the last 20 of each.
11.2 What the numbers mean
| Term | Plain language | Where it comes from |
|---|---|---|
| MRR (monthly recurring revenue) |
What your live subscriptions are worth in a month. A yearly plan of 1,200 counts as 100 a month; a weekly plan of 10 counts as about 43. | Every live subscription's amount, normalised to a monthly figure (month → amount ÷ interval; year → ÷ 12 × interval; week → × 52/12; day → × 30), summed per currency. Live means active, trialing or past due. Currencies are never converted into each other — you get one MRR per currency, which is honest rather than convenient. |
| ARR | The same figure annualised. | MRR × 12. |
| Churn | Subscriptions that ended in the period, and the monthly revenue that left with them. | Counted from cancellations recorded inside the snapshot window. |
| Involuntary churn | Customers you lost to a payment failure rather than to a decision — the ones dunning is supposed to save. | Churned subscriptions whose cancellation reason was written by the dunning engine. It is an approximation with a deliberate bias towards under-counting: Perfex stores no state history, so subscriptions still parked in past due are not counted as churned yet. |
| Recovered this month | Money you would have chased by hand: payments that only succeeded because an earlier attempt had failed. | Charge attempts with an attempt number greater than 1 that settled in the current calendar month. Attempt 1 is a normal collection and is deliberately excluded. |
| New / expansion / contraction | How MRR moved: arrivals, price rises on customers you kept, price drops on customers you kept. | Measured between consecutive daily snapshots of the same currency, so the identity MRR = previous + new + expansion − contraction − churned always holds exactly. |
Where the figures live. Once a day the cron writes one snapshot row per currency. The dashboard shows the newest snapshot and tells you the date it is "as of"; before the first snapshot exists it computes MRR live instead. The Reports screen reads snapshots only — so the movement chart and the cohort table need a few days of cron runs before they say anything, and the screen says so rather than drawing an empty chart.
11.3 The Reports screen
- MRR movement — the last 12 months per currency: MRR, active subscriptions, and the four movement figures. Bars are scaled within one currency and never compared across currencies.
- Success rate by gateway — over a 30- or 90-day window. The rate is succeeded ÷ (succeeded + failed): attempts still travelling (normal for bank debit for days) and attempts waiting on the customer are counted but kept out of the ratio, so a bank-debit gateway does not read as broken.
- Top decline reasons — where your failures actually come from. Hard declines need a new card; soft ones are what the retry schedule exists for.
- Cohort retention — each row is a signup month, each cell the share of that cohort still subscribed N months later.
11.4 The AR aging report
New in 1.1, and the one addition with no switch: it bills nothing, sends nothing and writes nothing. It reads the invoices you already have. AR Aging has its own entry in the module menu and needs only the View capability.
Every unpaid, partially paid and overdue invoice, rolled up by customer and by currency, and split by how far past its due date it is, in the five bands the screen itself labels Not yet due, 1-30 days, 31-60 days, 61-90 days and Over 90 days — day 90 falls in the 61-90 band, so the last band starts at day 91.
- Paid, cancelled and draft invoices are never aged. They are not debts.
- The balance is core's own definition — invoice total minus payments minus applied credit notes — so a row here agrees with the amount due on the invoice screen.
- An over-paid invoice is excluded, not netted off. One invoice's credit balance can never mask another invoice's debt.
- Currencies are never added together. Each currency is totalled on its own, the same rule the MRR figures follow.
- Filtering by age band filters customers, not invoices. Asking for Over 90 days gives you the debtors who have something older than 90 days, each with their whole balance still visible — which is the question a collections call actually starts from.
Filter by currency, customer, age band and a customer search; 25 rows to a page. Every filter and every page link is a plain link, so a filtered view is a URL you can paste into an e-mail.
Export CSV writes the whole filtered set, not the page you are looking at.
Amounts are plain decimals with a dot separator so a spreadsheet can sum the column, and any text
cell that starts with =, +, - or @ is prefixed
with an apostrophe — a customer name is not something you want executing as a formula when a
colleague opens the file.
One optional column: switch on Show accrued late fees on the aging report in Settings (off by default) and a further column shows fees that have accrued but do not yet have an invoice of their own. They are shown on their own and are never added into the outstanding total, because they are not yet a receivable.
12. Sale agent on generated invoices
Perfex puts a Sale Agent field on every invoice and offers it as a filter on four of its own reports, under Sales → Reports. An invoice with an empty agent is excluded from all of them.
From version 1.2 this module fills that field in, taking it from the record the invoice comes from:
| Invoice | Sale agent it receives |
|---|---|
| Subscription charge, plan mode | The staff member who created the subscription |
| Subscription charge, adopted from an existing invoice | The staff member who created the subscription; failing that, the agent already on the invoice it was adopted from |
| Late fee | The agent on the invoice the fee belongs to |
| Renewal | The agent of its linked subscription, if it has one |
| Anything with none of the above | The fallback below, or empty |
A subscription a customer started themselves in the client portal has no staff member behind it. For those, set Default sale agent for generated invoices under Setup → Settings → Recurring Payments. It ships as None, which writes no agent — the behaviour of every version before 1.2.
A per-record agent always wins over the fallback, so setting one does not overwrite attribution that a real staff member earned.
Upgrading from 1.0 or 1.1? Invoices already in your database are not touched. Only invoices created after the upgrade carry an agent. If you need the history corrected, that is a manual edit in Perfex — this module will not rewrite issued invoices.
“Added from” stays empty, and cannot be fixed. Perfex sets that field itself and forces it to empty for anything created by the cron job, which is every invoice this module generates. No setting changes it. It is core behaviour, not a limitation of this module — but it is core behaviour you will notice, so it is written down here rather than left for you to discover.
13. Permissions
The module registers seven capabilities under its own feature name in Setup → Staff → Roles (and on each staff member's own permissions tab). Administrators always have all of them.
| Capability | What it gates |
|---|---|
| View (Global) | Every read-only screen: dashboard, subscriptions, plans, charge attempts, dunning policies and log, reports. Without it the module's menu does not appear at all. It is also the capability that decides who receives the module's staff notifications. |
| Create | Creating subscriptions, plans and dunning policies. |
| Edit | Editing subscriptions (including pause, resume and cancel), plans and policies; setting a customer's default payment method; revoking a stored method. |
| Delete | Deleting plans, dunning policies and stored payment-method rows. |
| Charge Manually | The two buttons that move money on demand: Charge now on a subscription and Retry now on a charge attempt. |
| View Payment Methods | The Payment Methods screen. It is required for every action on that screen, so a staff member with Edit but not this one cannot touch stored authorizations at all. |
| Manage Settings | The Settings screen — general options, licence, diagnostics and the e-mail template list — and its entry in the menu. |
Version 1.1 adds no new capability. The screens it adds reuse these seven: Late Fees, Renewals and AR Aging are read with View; raising a fee invoice, running the accrual sweep and creating a renewal need Create; waiving or cancelling a fee, editing a renewal and lifting a suspension need Edit; deleting a renewal needs Delete; and both settings screens need Manage Settings. A role that could not see the module yesterday cannot see anything new today.
Every action that changes state is submitted as a POST and carries Perfex's CSRF token, so a crafted link cannot make a staff member cancel a subscription or delete a policy by accident.
14. Uninstalling
Decide the data question before you remove the module: the setting is read at uninstall time, and once the module is gone you cannot change it.
| Keep data (default) | Delete data | |
|---|---|---|
| Setting | Delete ALL data when the module is uninstalled = No | = Yes, saved before uninstalling |
| The 13 module tables | Kept. Subscriptions, stored authorizations, charge history, late fees, tracked renewals and snapshots survive; re-installing resumes where you left off. | Dropped. |
| Module settings | Removed in both cases. | |
| Gateway settings and keys | Removed in both cases. | |
| The 11 e-mail templates | Removed in both cases, including any wording you edited. | |
| Staff permissions for the feature | Removed in both cases. | |
| Perfex invoices and payments | Never touched. They are core Perfex data, and the payments the module recorded stay exactly where they are. That includes the separate invoices raised for late fees and renewals: they are ordinary Perfex invoices and they survive uninstalling either way. | |
Uninstalling does not cancel anything at the gateway. Stored cards and direct debit mandates keep existing in your Stripe, GoCardless or Paystack account. If you want them gone, revoke them from the Payment Methods screen (which detaches each token at the gateway) or from the gateway dashboard, before you remove the module.
If you only want to stop the module temporarily, Deactivate instead of removing — see the end of section 3.
15. Troubleshooting
Sixteen questions, and the screen that answers each. If yours is not here, try the FAQ.
Nothing is being charged at all
Nine times out of ten this is the cron. Open Settings → Diagnostics and look at the engine self-check: never ran or a large age in hours means the Perfex cron is not reaching the module. Fix it in Setup → Settings → Cron Job (section 4.1), then use Run Cron Manually and refresh. If the cron does run but a specific job shows errors, its last error message is on the dashboard's Cron Health panel.
The gateway is missing from the invoice, or a charge was refused before it started
The invoice's currency is not in that gateway's currency list. Perfex removes a gateway from any invoice whose currency it does not list, and the module refuses to charge rather than sending a guaranteed failure. Settings → Diagnostics prints "Not enabled for: …" for exactly this; fix the list in Setup → Settings → Payment Gateways. You get one staff notification per gateway and currency per day, not one per invoice.
Webhooks are not arriving
Check, in this order:
- Settings → Diagnostics — is the signing secret set? Missing means every delivery is rejected.
- The gateway dashboard's delivery log — what status is it getting back?
503means the module is deactivated or that gateway has no credentials yet (the gateway will retry, which is what you want).400means the signature did not verify — almost always the wrong secret, or the endpoint was created in the wrong mode (live vs test).405means something sent a GET; opening the URL in a browser does that and is harmless.500means a temporary failure on your side and the gateway will re-deliver. - Is the URL reachable from the public internet over HTTPS? A staging site behind basic auth or a firewall never receives anything.
An attempt is stuck in Pending, Processing or In Transit
In Transit is normal for bank debit and can last several days — that is the scheme, not a fault. Pending or Processing for more than an hour usually means the gateway answered and the webhook never arrived. The module re-drives such attempts safely under the same idempotency key, and after 26 hours the daily reconcile job raises a staff notification listing how many are unresolved. Resolve those from the gateway dashboard: find the transaction, see whether it took money, and act on the invoice accordingly. Reconciliation does not query the gateway APIs for you.
Dunning e-mails are not being sent
- Test Perfex's own mail first: Setup → Settings → Email. If Perfex cannot send, neither can the module.
- Settings → Email Templates in the module lists all eleven seeded templates with their status and links straight to the Perfex editor. A template marked missing means it was deleted — re-activate the module to seed it again. A template marked inactive is skipped.
- The customer needs an active contact with an e-mail address. Without one the module notifies your staff instead of mailing nobody.
- Check the dunning log under Dunning: it records every step, successful or failed, with the error. Remember that each step is claimed once — a step already recorded as sent will never be sent again, by design.
Late fees are switched on but nothing has accrued
Work down this list, on Late Fees → Late fee settings:
- The rate. Both rate options ship at zero. A percentage of
0.00, or a fixed amount of0, computes a fee of nothing and the invoice is passed over. - The grace period. Nothing accrues until an invoice is more than that many days past its due date — seven by default.
- The invoice's status. Only Overdue and Partially Paid accrue. An invoice Perfex still calls unpaid because its due date has not passed is not late.
- The outstanding balance. The fee is charged on what is still owed. An invoice settled by a payment or a credit note has nothing left to penalise.
Then press Run accrual now rather than waiting for the cron: it reports how many rows accrued, closed and were invoiced, which tells you which of the four it was.
A late fee accrued, but no invoice was raised for it
Two likely reasons, both by design. Raise the fee invoice automatically ships off, so fees wait for a staff member to press Invoice now. And automatic invoicing only picks up accruals that have ended — a fee set to accrue daily, weekly or monthly with no cap and no maximum accrual count never ends, so it can never be billed. Give a repeating fee a cap or a maximum number of accruals. Also check the minimum accrued amount before invoicing, which applies to automatic invoicing only.
A subscription is Suspended or Terminated and I need it back
Suspended is reversible two ways: record a payment against the invoice and it returns to Active by itself, or press Lift suspension on the subscription, which needs the Edit capability. If it comes back as Needs Payment Method instead of Active, it has no usable stored method — the customer adds one from the portal and it starts collecting again.
Terminated is final on purpose. Nothing in the module reinstates it, including a payment; if the invoice is paid, your staff get a notification saying exactly that. A returning customer needs a new subscription.
A renewal reminder was not sent
Check, in this order: is Send renewal reminders and process renewals on in Settings — while it is off the sweep does not run at all. Is the row's status Active or Expiring — Expired, Renewed and Not tracked rows are out of the sweep. Has that threshold already fired? One reminder is sent per threshold, ever; Start the reminder sequence again clears the history so every threshold still ahead can fire once more. And a renewal expiring more than 400 days out is not read at all until it comes inside that horizon.
A customer says the 3-D Secure link no longer works
Those links are single-use and expire after the Authentication link lifetime in Settings (72 hours by default). Two ways out: the customer can simply pay the invoice from the portal or from their invoice link, which achieves the same thing; or a staff member with Charge Manually can press Retry now on the attempt, which produces a fresh charge and, if the bank asks again, a fresh link and a fresh e-mail.
Paystack charges fail after the customer changed their e-mail address
A Paystack authorization is bound to the e-mail address it was created with. The module therefore charges with the address stored on the payment method at capture time and never with the contact's current address — changing an e-mail in Perfex does not break anything. What does break it is the authorization itself being replaced or removed at Paystack, or a method stored without its original address: the attempt fails with a clear message on the Charge Attempts screen. The fix is always the same — ask the customer to add the payment method again from the portal, which creates a fresh authorization.
A subscription sits at "Needs Payment Method"
It has no usable stored method: the customer never added one, or theirs expired, was revoked at the gateway, or was removed. Only the customer can fix it, in the portal. You do not have to do anything afterwards: when they add a method, every stranded subscription of theirs adopts it automatically and starts collecting again. The Needs attention panel on the dashboard and the engine self-check both count these.
Perfex stopped generating the recurring invoice
That is core behaviour, not the module. Check Setup → Settings → Cron Job → Invoice for "Create new invoice from recurring invoice only if the invoice is with status paid?" — if it is set to Yes and the previous invoice is still unpaid while dunning works on it, no new invoice is created. Also check the recurring invoice's cycle limit and that its status is not cancelled or draft.
Could a customer be charged twice?
The module is built so the answer is no, on four independent levels: only one cron run can be
charging at a time; every attempt carries a deterministic idempotency key
(inv_{invoice}_att_{n}) so a repeat of the same attempt makes the gateway replay its
original answer instead of taking money again; a gateway transaction id can be claimed by exactly one
attempt, and that claim happens before the payment is written into Perfex; and every webhook
delivery is de-duplicated on the gateway's own event id. If you still suspect a duplicate, open
Charge Attempts and filter by that invoice — every attempt against it is listed with
its transaction id.
The invoice was paid by bank transfer, cash or another gateway
Nothing to do. Any payment recorded against the invoice in Perfex — by the module, by the customer through core's Pay button, or typed in by your staff — closes the open dunning on that invoice, stands down the queued retries, and puts a past-due subscription back to Active. A partial payment does not: the invoice still owes money, so the sequence continues from where it was rather than restarting.
Customers still reach portal links while the module is deactivated
Expected. A deactivated Perfex module keeps its routes, so the module answers those requests itself
with 503 Service Unavailable instead of showing a broken page — and gateways treat that
as "retry later" and re-deliver the webhooks once you activate again. Nothing is charged and nothing
is lost while it is off.