Frequently asked questions
Answers here describe what version 1.2.1 actually does. Where the honest answer is “no” or “not yet”, it says so — a documentation page that oversells costs you a support ticket and costs us a refund.
Fit and scope
What do I need to run this?
- Perfex CRM 3.4.x and the database Perfex itself requires.
- PHP 8.1 – 8.4.
- A working Perfex cron. This module does not add a second cron job; it registers itself into the one you already run.
- HTTPS on your Perfex site. Gateways will not deliver webhooks to plain HTTP, and card authorisation pages must not be reached over it.
- Outbound HTTPS from your server to your gateway's API.
- A live account with at least one of Stripe, GoCardless or Paystack.
Does this work with the SaaS / multi-tenant module for Perfex?
It is built to the multi-tenant rules, yes:
- every table name goes through
db_prefix()— the literaltblis never written anywhere in the module; - the installer is idempotent (every
CREATEguarded bytable_exists(), everyALTERbyfield_exists()), so it can run once per tenant database and re-run on re-activation without damage; - all settings live in the options table, never in a file on disk — disk is shared between tenants;
- email templates are inserted per tenant database;
- cron is idempotent and single-flight (a MySQL advisory lock), so overlapping runs are safe;
- assets are addressed with
module_dir_url(), never an absolute URL.
Two honest caveats. (1) The module has no tenant-aware licensing feature — it also makes no licence call at all, so nothing gets in your way, but there is no per-tenant activation model either. (2) SaaS modules typically impose per-tenant invoice quotas. If a tenant hits its quota, creating a plan invoice fails; the module logs the failure and charges nothing, but it cannot raise the quota for you. We have not certified the module against a specific SaaS module release — test on a staging tenant first.
Does it replace Perfex's built-in subscriptions? What happens to subscriptions I already have?
No. The module never reads or writes Perfex's own subscriptions table. Core subscriptions keep running exactly as they do now, on their own screen, with their own Stripe integration. This module has its own Subscriptions screen and its own tables, and the two do not talk to each other.
There is no automatic migration from core Stripe subscriptions in version 1.1. To move a customer over by hand: set the core Stripe subscription to cancel at period end (do not cancel it immediately), ask the customer to add a payment method on their Billing & Cards page in the client portal, then create a subscription for them here.
Do not run the same customer on both at once — core Stripe and this module do not know about each other, and the customer will be billed twice.
Can it charge an invoice that was not created by this module?
Yes — that is the main use. Perfex's own cron generates recurring invoices; this module hooks the moment core creates one and charges it against the customer's stored payment method. The invoice is a normal Perfex invoice with its normal number; the module does not create a parallel document and does not modify the one core created.
Two ways to switch it on:
- Per invoice — create a subscription in this module and pick the existing recurring invoice it should follow.
- Globally — switch on Auto-charge every recurring invoice when the customer has a saved default payment method in Settings. Every recurring invoice for a customer who has a saved default method is then charged automatically, with no subscription to create by hand.
What it does not do: there is no “charge this invoice now” button on an arbitrary one-off invoice in the admin area. Off-session charging always happens through a subscription. A one-off invoice can still be paid by the customer on-session through this module's own gateway on the invoice's Pay now button.
Do you support PayPal or Razorpay?
Not in version 1.1 either. Three rails ship:
- Stripe — cards, worldwide, with 3-D Secure recovery.
- GoCardless — bank debit (Bacs, SEPA, ACH and the other schemes GoCardless selects from the payer's country).
- Paystack — cards, primarily Nigeria, Ghana, South Africa and Kenya.
PayPal and Razorpay were deferred deliberately, not forgotten: each needs its own mandate model and neither maps cleanly onto the stored-token, charge-off-session pattern the other three share. Shipping a half-built rail that silently fails to collect is worse than shipping three that work. No release date is promised. Your customers can of course keep paying through Perfex's own PayPal gateway — that money just is not automated by this module.
I already run 1.0. What does upgrading to 1.1 change?
Nothing at all, until you switch a feature on yourself. That is the rule version 1.1 was built to, and it is enforced by the upgrade rather than promised in a release note.
Four features arrive: late fees, renewal and expiry tracking, subscription suspension and termination, and an AR aging report. They bring seventeen new options with them, and the five that turn a behaviour on all arrive off — late fees, automatic fee invoicing, renewal reminders, the customer email for suspension and termination, and the late-fee column on the aging report. The two rate settings arrive at zero on top of that, so even a switch flipped straight in the database computes a fee of nothing.
The migration checks this itself. Before it finishes it reads back every option it seeded, and if any gate is on it aborts the upgrade with the offending option named rather than applying it half-way.
Concretely, on an install that already has customers on it, the upgrade:
- creates two tables, both empty — the late-fee ledger and the renewal ledger;
- adds three email templates, none of which anything sends until you opt in;
- adds three cron jobs. Late fees and renewals read their own option, find it off and return. Terminations has no option of its own: it looks for subscriptions a policy suspended under one of the new terminal actions, and on an install upgrading from 1.0 no policy uses one, so it finds nothing;
- adds two terminal actions to the dunning-policy list and two subscription statuses. No existing policy is rewritten and the default terminal action is the one it always was, so no policy you already have behaves differently;
- writes no row to your invoices, payments, customers, contacts, subscriptions, charge attempts, payment methods, dunning log or revenue snapshots, and moves no subscription off the status it was on.
That last line is checked before release rather than assumed. A harness counts every table on a populated 1.0 database, runs the real migration and then a full eleven-job cron pass, and counts again. A migration that writes nothing is easy; a cron pass in which three new sweeps run against a live book and still write nothing is the part worth testing.
Perfex has no auto-update for modules. Upload the new archive under Setup → Modules exactly as you did the first time; Perfex compares the version header and runs the migration. Your settings, subscriptions, stored tokens and charge history are kept.
How charging works
Do I have to set up cron? What happens if it stops?
You need Perfex's cron, which you almost certainly already have. This module runs after core's cron tasks finish and is independent of core's per-feature hour gating, so there is nothing extra to schedule. Eleven jobs run on each pass: catch-up, due charges, retry queue, dunning emails, terminations, expiring-card notices, unprocessed webhooks, late fees, renewals, reconciliation and the daily revenue snapshot. Each is wrapped so that one failing job never stops the other ten, and each records its last run, last success and last error.
Three of the eleven are new in 1.1 — terminations, late fees and renewals — and none of them does anything on an install that has not opted in. Late fees and renewals each read their own setting first and return immediately while it is off, which is how both ship. Terminations works differently: it has no setting of its own, because there is nothing to switch off until a dunning policy is set to suspend, then terminate. Until one is, it looks for suspended subscriptions past their waiting period and finds none.
If cron stops, nothing is charged — and the module tells you rather than staying quiet. After the Alert when cron has not run for threshold (default 26 hours) active staff get a CRM notification, and the Settings screen shows the age of the last run alongside the state of all eleven jobs.
Does it charge at a particular time of day?
Yes. Each dunning policy carries a charge hour (default 02:00), interpreted in the subscription's own timezone, because collecting in the customer's small hours is what card issuers expect from a subscription merchant. A recurring invoice that arrives outside that window is deferred to the next occurrence of the hour instead of being charged immediately. If a subscription has no usable timezone the charge happens straight away rather than never.
Note that Perfex stores no timezone on customers or contacts, so the timezone used is the one you set on the subscription itself.
How many times is a failed payment retried?
It depends on why it failed. The module reads the gateway's own advice code first, then the decline code:
- Soft decline (insufficient funds, issuer unavailable, processing error, do-not-honor) — retried on the policy schedule. The default policy is days 1, 3, 5, 7, 10 and 14 after the first failure, capped at 7 attempts or 21 days, whichever comes first.
- Hard decline (lost or stolen card, invalid account, card not supported, revoked authorisation, incorrect number) — not retried at all. Retrying is billed per attempt by the card networks and reads as fraud probing to the issuer. The customer is asked for a new card instead.
- Authentication required — the customer is sent a single-use confirm-payment link rather than being retried blindly.
- Dead mandate — collection stops and the customer is asked to re-authorise.
There is also a hard ceiling per stored instrument: 8 attempts in 14 days across all invoices. Past that the module stops charging and asks you to speak to the customer.
GoCardless is the exception to all of the above: retries there belong to GoCardless Success+, and this module never re-submits a bank debit itself. Double-retrying a mandate collects twice.
Why is a payment still “in transit”?
Because GoCardless is bank debit, not a card. Money does not move on submission: the collection is handed to the scheme and settles over several working days (Bacs is typically about five). The module therefore marks the attempt In Transit as soon as GoCardless accepts it and deliberately writes no payment into Perfex yet — recording it early would show the invoice as paid before the money exists and would corrupt your books if the collection later fails.
The Perfex payment record is created when GoCardless sends payments.confirmed. If
you prefer to wait until the money is actually in your bank, set Record GoCardless payment in
Perfex when to Payment is paid out to your bank in Settings — a conservative
choice that shows invoices as unpaid for longer.
A bank debit sitting in transit for a few working days is normal and needs no action. If one is still in transit well past the scheme's settlement window, check the attempt in the GoCardless dashboard: the module deliberately does not guess at the fate of money it cannot see.
Why did the gateway disappear from my invoice?
Almost always the currency list. Perfex filters the payment modes offered on an invoice by that invoice's currency, and a gateway whose Currencies setting does not contain the invoice's ISO code is dropped from the invoice silently — no error, no button.
Fix it at Setup → Settings → Payment Gateways → your gateway → Currencies. It is a comma-separated list of ISO codes. Shipped defaults are deliberately narrow, because a wrong currency is worse than a missing one:
| Gateway | Default currency list |
|---|---|
| Card (Stripe) | USD,EUR,GBP |
| GoCardless | GBP,EUR |
| Paystack | NGN,GHS,ZAR,KES |
The same list gates the module itself. A plan invoice is not even created when the gateway cannot settle its currency — issuing a numbered invoice nobody can pay is the worse failure — and you get a staff notification naming the subscription, the currency and the gateway.
Will my customer be charged twice if the cron runs twice, or the webhook arrives twice?
No. Webhook redeliveries are normal traffic for every gateway, and overlapping cron runs are a real scenario because Perfex cron can also be triggered by hand. There are five independent guards, all enforced by the database rather than by hopeful code:
- Cron single-flight. A MySQL advisory lock; a second overlapping run returns immediately instead of charging anything.
- Deterministic idempotency keys. Every charge is sent under
inv_{invoiceId}_att_{n}— never a random UUID. If the same key is sent again the gateway replays its original answer instead of taking the money a second time. - One attempt row per attempt number. A unique index on (invoice, attempt number) makes a duplicate attempt impossible to open.
- The transaction is claimed before the money is written. A unique index on (gateway, transaction id) is taken before the payment is recorded in Perfex, so a second delivery of the same transaction finds the claim already held and returns the existing payment instead of adding another.
- Webhook dedupe. A unique index on (gateway, event id); a redelivered event is recognised and skipped.
On top of that, the module asks Perfex's own ledger whether that transaction id already exists on the invoice before recording anything.
What happens if my server is down when a payment fails?
Nothing is lost and nothing is silently skipped. Retries and dunning emails are stored as dates on rows, not as jobs in a queue that has to fire on time, and every offset is measured from the first failure — not from whenever the previous step happened to run. A cron that misses a day therefore does not push the whole sequence a day later.
When cron runs again, everything whose date has passed is picked up in the same pass. Each outbound dunning step is claimed in a ledger table before it is sent (unique on invoice + step + channel), so even if several days of catching up happen at once, a customer never receives the same step twice.
A charge that dies mid-flight — a network drop or a PHP timeout while the gateway was answering — is parked as pending rather than guessed at, and re-driven about an hour later under the same idempotency key. That is precisely what makes replaying it safe.
Does it handle partial payments?
Yes. A retry charges what Perfex says is still left to pay on that invoice at that moment, so a part already settled is never re-collected. The invoice document itself is never modified.
What happens when a payment is disputed or charged back?
The attempt is marked Charged Back and staff get a notification pointing at the invoice. The module never deletes or reverses the Perfex payment automatically: reversing money is a human decision, and the invoice may already carry credit notes or partial settlements.
Customers, emails and the portal
Does the customer need a portal login to fix a failed payment?
It depends on what they need to do, so here is the honest breakdown:
| Action | Login needed? | How it is authorised |
|---|---|---|
| Confirm a bank security check (3-D Secure) | No | Single-use, expiring token in the email (default 72 hours), stored hashed |
| Cancel, pause or resume a subscription | No | The subscription's own 64-character hash, the same pattern Perfex uses for invoice links |
| Pay the invoice itself | No, with Stripe | The standard hash-authorised Perfex invoice link and its Pay now button |
| Add or replace a stored card | Yes | Perfex client portal login |
The Update payment method link in the dunning emails points at the customer's Billing & Cards page inside the Perfex client portal, and Perfex requires a contact login for that. Binding a new payment instrument to a customer account from a link with no authenticated session behind it is not something we were willing to build.
If a customer will not log in, the operable route is the invoice: the ordinary Perfex invoice link needs no login, and with the Stripe gateway its Pay now button goes straight to Stripe's own checkout. If Save card by default is on, that on-session payment also stores the card for future automatic collection — so the problem fixes itself.
One difference worth knowing: with GoCardless or Paystack, Pay now routes through this module's own capture flow inside the client portal, so those two do ask for a portal login. Only Stripe's Pay now works entirely without one.
Can a customer cancel their own subscription?
Yes, from the link in your emails, with no login. Cancelling always passes a retention step first — a pause offer of up to three months is shown before the confirm button exists — and a cancellation takes effect at the end of the paid period, never mid-period. Pause and resume live on the same page. Every action is written to the activity log.
Does it send my customers emails I cannot control?
No. Every customer-facing message is an ordinary Perfex email template under Setup → Email Templates. You can rewrite the wording, change the subject, or switch a template off entirely, and your change is what goes out. Eleven templates are installed:
- Payment failed — first, second and final notice
- Card expiring soon
- Payment authentication required (3-D Secure)
- Payment receipt
- Payment authorization cancelled
- Service suspended, and service terminated — neither is sent until you switch on the customer notice for suspension and termination in Settings, which ships off
- Renewal reminder — sent only while renewal reminders are on, which they are not by default
- Upcoming payment notice — installed and editable so your wording is ready, but nothing in the module dispatches it; see the advance-notice note on the Compliance page
You control the cadence too. The dunning policy decides which days emails go out (default: days 0, 3, 7, 14 and 21 after the first failure), and the receipt has its own on/off switch in the module's Settings.
The {autobill_*} placeholders — card brand, last four, amount due, retry date,
decline reason, update-card link, confirm-payment link — are filled in for you. A placeholder
that cannot be resolved is stripped rather than shown to the customer as a raw brace. Customers are
never shown the gateway's raw error text either: decline codes are mapped to plain-language reasons,
because issuer wording routinely carries internal identifiers and occasionally your own account
state.
Staff notifications (cron stale, currency mismatch, chargeback, velocity cap, stuck attempts) go to Perfex's in-CRM notification list, not to your customers, and only to staff who hold this module's view permission.
Multi-currency: what actually happens?
Each subscription bills in exactly one currency and stays there. For an adopted recurring invoice that is the invoice's currency as Perfex set it; for a plan subscription it is the plan's currency, or the per-subscription override if you set one. There is no exchange-rate conversion anywhere — the module never converts an amount, it charges the invoice's own currency at the gateway.
Three consequences:
- The gateway must list that currency, or nothing is charged and no plan invoice is created. You get one staff notification per gateway and currency per day, not one per invoice, because a misconfiguration hits every invoice in that currency at once.
- Minor-unit conversion is done properly. Zero-decimal currencies (JPY, KRW, VND, XAF and the rest) are sent as whole units; three-decimal currencies (BHD, JOD, KWD, OMR, TND) at ×1000; everything else at ×100 — always rounded, never truncated. Perfex's own bundled Stripe gateway special-cases only JPY and truncates; this module does not reproduce that.
- Reporting is per currency. Revenue snapshots are stored once per day per currency and are never summed across currencies, because a summed multi-currency MRR is a meaningless number.
Unrelated to this module but worth knowing: Perfex does not support three decimal places for a number, which also affects tax rates (the 9.975% case). No module can fix that.
Data, cards and limits
Where are card numbers stored?
Nowhere. Not in your database, not in your server's memory, not in your logs. The module never renders a card field — not in the client portal, and deliberately not in the admin area either. Card entry happens on the gateway's own hosted page. What is stored is the gateway's token plus the card brand, the last four digits and the expiry month and year.
The full picture, including what this means for your PCI questionnaire, is on the PCI & data handling page.
Can staff see customers' card details?
Only the brand, the last four digits and the expiry — because that is all the module has. The Payment Methods screen sits behind its own capability, View Payment Methods, which you grant per role under Setup → Staff → Roles; a staff member without it cannot open the screen at all.
There is no card-entry form anywhere in the admin area, by design. Staff should never be in a position to type a customer's card number into your CRM — that single choice is what keeps your CRM out of scope.
Can I use my existing Perfex Stripe keys?
You can use the same Stripe account: paste the same keys into this module's own gateway settings. What you must not assume is that the two share anything.
This module registers its own payment gateways under Setup → Settings → Payment Gateways and reads credentials only from there — never from Perfex's built-in Stripe gateway. That separation is deliberate: core's Stripe integration sets the API key globally, and two integrations overwriting each other's key inside one PHP request is exactly how the wrong account gets charged.
You also need a separate webhook endpoint in the Stripe dashboard pointing at this module's URL. Core's Stripe webhook will not feed it. Both endpoints can live side by side on the same Stripe account — see Gateway setup.
If you leave both core Stripe and this module active on the same invoice, the customer sees two Pay now buttons. Deactivate whichever you do not want in the gateway settings.
Can I add a late fee automatically?
Yes, from version 1.1. The objection that made the answer “no” in 1.0 still stands, and it is the reason the feature is shaped the way it is: a numbered invoice is a document your customer already holds a PDF of, and amending it is unacceptable in most tax jurisdictions. So the fee is never written onto the invoice it penalises.
Instead the fee accrues against the overdue invoice in the module's own ledger, and when it is billed it becomes a separate Perfex invoice with its own number — same customer, same currency as the original, one line, and the original's number in the line description and in both the client note and the admin note so whoever reads the two PDFs can connect them. The link between the two lives in the module's own column. Nothing updates the original: not its lines, not its total, not its status.
The fee is calculated on what is still owed, not on the original total. That figure comes from Perfex's own “left to pay” helper, so part payments and applied credit notes are already netted off — a customer who has paid ninety per cent is penalised on the ten.
It applies to every overdue invoice in Perfex, not only to the ones this module charges. The sweep selects on an invoice's own status and due date, so a one-off invoice that has nothing to do with a subscription accrues on exactly the same terms. Decide your rate and your grace period with your whole receivables book in mind, not just your subscriptions.
Paying late does not erase a fee that has already accrued. Settling the debt stops the accrual that day and the amount accrued up to then still stands, because the invoice was in fact paid late. Waive it from the Late Fees screen if you would rather not charge it.
What it does not do:
- It never modifies the original invoice, and no setting turns that on.
- It puts no tax on the fee invoice. How a penalty is taxed is your accounting decision, and the module will not guess it.
- It does not collect the fee for you. A fee invoice is not attached to any stored mandate and is never charged off-session by the engine. It is also raised with no payment modes ticked, so open it and choose the modes you want offered before you send it, exactly as you would on an invoice you typed yourself.
- It does not accrue against a cancelled or draft invoice, and it does not backdate: nothing accrues for the days an invoice was overdue before you switched the feature on.
- Delete the original invoice and its accrual goes with it. No debt, no penalty.
How do I switch late fees on?
They ship off, on a fresh install and after an upgrade alike. The screen is Recurring Payments → Late Fees, and the Late fee settings button on it opens the only page where the master switch is written — which is why that one menu entry stays visible to staff who may configure the module even while the feature is off. Everyone else sees it only once it is on.
Turning the master switch on is deliberately not enough to charge anyone. Both rate settings ship at zero, so with the switch on and nothing else changed, nothing accrues. You have to set a percentage or a fixed amount first.
| Setting | Ships at | 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 a staff member pressing Invoice now on a fee that accrued while it was on — the accrual already happened, and somebody is explicitly asking for its document. |
| Fee type | Percentage | A percentage of the outstanding balance, or a fixed amount. |
| Percentage / fixed amount | Both zero | Nothing is added until one of them is set. The fixed amount is entered in minor units — 500 is 5.00 in a two-decimal currency. |
| Grace period | 7 days | Nothing accrues until the invoice is overdue by more than this, measured with Perfex's own days-overdue figure so the number agrees with your invoice list. |
| How often it accrues | Once only | Once, daily, weekly or monthly. A repeating fee accrues at most once per period. |
| Maximum number of accruals | 12 | The ceiling that lets a repeating fee stop. It matters: a fee can only be billed once its accrual has ended. |
| Cap | 0 — no cap | A percentage of the original invoice total, evaluated against everything accrued so far. |
| Raise the fee invoice automatically | Off | A second, separate switch. With it off, fees accrue and wait for a staff member to press Invoice now. |
| Minimum accrued fee before invoicing | 0 — no minimum | Applies to automatic invoicing only, in minor units. Zero means every ended accrual is invoiced; a fee that accrued to nothing is never invoiced whatever this is set to. |
| Invoice line description | Late payment fee for invoice
{invoice_number} |
Your wording. {invoice_number} is replaced with the overdue invoice's
number. |
The sweep runs once per cron pass and is idempotent by database predicate, not by hope: a row that accrued today is not a candidate again today, however many times cron is invoked by hand.
What happens if I delete a fee invoice?
The original invoice is untouched, because it was never written to in the first place. The accrual returns to waiting for a document, so you can raise it again, waive it, or cancel it from the Late Fees screen. The one exception is a fee you waived after it had been invoiced: deleting its document leaves it waived, because you already told the customer it was forgiven.
Waiving works the other way round: it stops the accrual, and it deliberately does not delete a fee invoice that has already been issued. Cancelling or crediting an issued invoice is your decision to make in Perfex, not something a module should do behind you.
Does the module phone home, or need a licence key?
There is a field for your Envato purchase code and its format is checked offline. Version 1.1 still makes no outbound call to any licence server at all. The design rule is fail-open: a network problem, an expired certificate on someone else's server or a blocked outbound HTTP must never stop your invoicing, charging or cron. If a later version adds verification, it keeps that rule.
What happens if I deactivate the module while subscriptions are live?
Charging stops immediately — the cron task is only registered for active modules, so no due schedule is charged and no retry fires. Nothing is deleted and nothing is lost:
- The webhook endpoints stay reachable (Perfex keeps deactivated modules
routable) and answer
503, which every gateway treats as “try again later”. Events that arrive while you are deactivated are therefore redelivered rather than dropped — up to each gateway's own retry limit. - The client-portal pages answer
503as well, rather than showing a broken page. - Invoices core generates during the outage are simply not charged.
When you reactivate, the next cron pass picks up whatever is still due; the retry queue and the dunning sequence resume from the dates they already hold, and the step ledger stops any email going out twice. If you were deactivated longer than your gateways redeliver, replay the missed events from the gateway dashboard.
What happens when I uninstall?
Deactivating and uninstalling are different things.
| Deactivate | Uninstall | |
|---|---|---|
| Module tables and data | Untouched | Kept, unless you first switch on Delete all module data on uninstall in Settings (off by default) |
| Module settings | Kept | Removed |
| Gateway settings and keys | Kept | Removed |
| The eleven email templates | Kept | Removed |
| Staff permissions for the module | Kept | Removed |
| Charging | Stops | Stops |
Everything that is removed is discovered by prefix query rather than from a hand-written list, so a later version adding a table cannot leave an orphan behind.
Uninstalling does not detach stored tokens at the gateway. If that matters, delete the clients in Perfex first (which does try to detach each token at the gateway), or clear them from the gateway dashboard. See the PCI & data handling page.
Suspension, renewals and collections
What is the difference between suspended and terminated?
They are two of the actions a dunning policy can take when every retry has failed, and two new statuses a subscription can hold. Neither is the default, and neither happens unless you choose it on a policy under When all retries fail.
| Suspend the subscription | Suspend, then terminate it | |
|---|---|---|
| What it does | Moves the subscription to Suspended and stops collecting | The same, and then moves it to Terminated after a waiting period |
| Reversible | Yes — automatically on payment, or by hand | Only while it is still suspended |
| Cancellation date and reason recorded | No, deliberately | No — the status itself is the record |
| The customer's stored card | Still counted as in use, so it is never mistaken for an orphan while they are away | Stops counting as in use once terminated, exactly as after a cancellation, so staff may delete it. Nothing is detached at the gateway by the termination itself |
The waiting period is Terminate a suspended subscription after this many days in Settings, thirty days out of the box, and it applies only to policies using the second action. It is a delay, not a switch: if you want a policy to suspend and stop there, set its terminal action to suspend the subscription rather than trying to disable the second half with the day count.
Suspension is chosen over cancellation on purpose. It stops collection without spending a cancellation date and reason on an outcome that is meant to be undone. Termination is the opposite: it is meant to be final, and nothing in the module undoes it.
On a plan subscription the next charge date is left where suspension found it, which is what makes lifting a suspension cheap — but read the last paragraph of What happens if a suspended customer pays? below before you lift a long one, because the periods that elapsed meanwhile are still owed.
A subscription a human already cancelled or completed is never overwritten by either action. Writing “suspended” over “cancelled” would put an ended subscription back on your live list and imply it is coming back.
Does suspension cut my customer off from the portal, or from the service they are paying for?
No. Neither. Suspension stops billing and nothing else.
The module does not touch the active flag on your contacts, and it will not. That login is not ours to switch off, and switching it off locks the customer out of every other part of your CRM and out of the one page where they could add a working card — while no code path in the module would ever switch it back on. Version 1.0 refused to do it and version 1.1 keeps that refusal. A suspended customer can still sign in, still see their invoices, and still add a payment method.
It also cannot cut off whatever you are actually selling. The module has no reach into your hosting panel, your product, or anything outside Perfex. If a suspension in this module should suspend a real service somewhere else, that is a human step on your side.
What a suspension does do, and all it does:
- The module's due-charge sweep stops serving it: only active and trialing subscriptions are collected.
- Its queued retries are cleared, so the retry ladder stops too.
- Staff get a CRM notification naming the invoice.
- The customer is emailed only if you have switched that on; it ships off.
- The customer can no longer pause or resume that subscription themselves from the portal. Lifting a dunning suspension is your staff's decision, not the decision of the person who has not paid.
One caveat for subscriptions that ride on a Perfex recurring invoice rather than on a plan of ours. Suspension stops this module's own charging sweep, but the recurring invoice that generates the next document belongs to Perfex, not to us, and it keeps generating. When you suspend one of these, stop or pause its recurring invoice in Perfex as well.
What happens if a suspended customer pays?
They are restored automatically when the payment settles the invoice — whether it was collected by this module, taken through Perfex's own Pay now, or typed in by hand as an offline payment. There is nothing for staff to press. The subscription comes back to active if its stored card is still on the row, and to needs payment method if the card was removed while it was away.
Two qualifications, both of them honest rather than convenient:
- A part payment is not a recovery. The invoice has to be settled. A partial payment cancels the queued retries but leaves the dunning ledger alone, because clearing it would restart the sequence at step one and re-mail a customer who has already paid you something.
- A terminated subscription is not restored, and this is deliberate. The payment is recorded on the invoice as normal, the subscription stays terminated, and staff get a notification saying so. Bringing a terminated customer back is a decision to re-subscribe them, not a side effect of money arriving.
Staff can also lift a suspension by hand from the subscription screen, for the case where payment reached you outside the CRM entirely. There is no equivalent button for termination. Revoking or deleting the customer's stored payment method is not a route out: it detaches the card from every subscription using it, but a suspended one keeps its status. Releasing an instrument is not a decision about collection.
On a plan subscription the next charge date is left where suspension found it. If periods elapsed while the customer was suspended they were never cancelled out of those periods and still owe them, so the next sweep bills them — the same convention pause and resume already follow. On a long suspension that can mean several charges arriving at once, so it is worth looking at the charge date before you lift one. It is written to the log, because “why was my customer charged the moment they paid” is a fair question.
What does renewal and expiry tracking cover that Perfex does not already?
Things Perfex has no record of at all: domains, SSL certificates, hosting accounts and licences. You give each one a name, an identifier, an expiry date, a term and a renewal cost, and the module reminds you and your customer before the date arrives.
It deliberately does not cover contracts, estimates or proposals. Perfex core already sends its own expiry reminders for those, on its own cron and its own settings. A second reminder for one event means your customer gets two emails, which is a defect, not a feature.
What else it is not:
- It is a reminder ledger, not an automation. Nothing in it registers a domain, issues a certificate, or talks to a registrar or a control panel. It tracks the date.
- Reminders are off until you switch them on in Settings. While they are off the sweep does not run at all: nothing is emailed and nothing is invoiced, whatever an individual renewal is set to.
- Raising the renewal invoice automatically needs two boxes ticked on the renewal itself, auto-renew and auto-invoice, and both ship unticked. With neither ticked a renewal only sends reminders.
Reminder days are a comma-separated list, 60, 30, 14, 7 and 1 by default, set per renewal. One reminder is sent per threshold, ever — the threshold is claimed in the row before anything is sent, so overlapping cron runs cannot send two, and a send that fails is logged rather than retried. A second copy of a reminder is worse than a missing one. If you need the sequence to run again, there is an explicit action on the renewal that clears it.
One documented compromise: each cron pass reads a bounded window of the renewals expiring soonest, and ignores anything expiring more than four hundred days out. A reminder threshold set further ahead than that fires when the renewal enters the window rather than at the distance you asked for.
What is on the AR aging report?
Every unpaid, partially paid and overdue invoice in Perfex — not only the ones this module bills — grouped by customer and by how long the money has been outstanding, in the five bands the screen itself labels not yet due, 1-30 days, 31-60 days, 61-90 days and over 90 days. There is a CSV export, and filters for customer, currency and age band. It is its own screen, reached from AR Aging in the sidebar.
The report only reads; it changes no invoice and writes no row. Days overdue are computed the same way Perfex's own invoice list computes them, and against the same date, so the two screens agree rather than differing by a day at the wrong hour.
Amounts are never added across currencies; each currency is totalled on its own, for the same reason the revenue snapshots are kept per currency. If you switch on Show accrued late fees on the aging report, one extra column appears showing fees that have accrued but not yet been invoiced. They are shown separately and are never folded into the outstanding total, because until a fee invoice exists nobody owes that money yet.
Support
What exactly do I get for support, and what is out of scope?
In scope, through Envato item support:
- Defects in the module — something it claims to do and does not.
- Questions about how the module's own features work.
- Help reading its diagnostics when charges are not happening: the Settings screen's cron and gateway state, the Charge Attempts log, the webhook event list.
- Guidance on the setup steps in this documentation.
Out of scope, honestly stated up front:
- Your gateway account. Approvals, identity verification, payouts, holds, per-country availability, pricing, disputes and dispute outcomes are between you and Stripe, GoCardless or Paystack.
- Perfex core. Core bugs, core upgrades and core support belong to Perfex. (Note that Perfex's own auto-update errors 497/498/499 come from Perfex's licensing, not from this module, and are commonly confused with it.)
- Server administration. Cron, TLS certificates, mail delivery, outbound HTTP, PHP configuration.
- Customisation and new features, integrations with other modules, and any support on a modified copy of the module.
- Your PCI compliance, and advice about it. See the PCI & data handling page for what the module does and does not do for you.
- Recovering money. We cannot influence a gateway's decision on a dispute.
To get a fast answer, send: your Perfex version, PHP version, the module version, which gateway, the invoice number, and the row from the Charge Attempts screen (its id, status and decline code). Never send secret keys, webhook secrets, or full webhook payloads.
I turned on PHP error logging and I see “foreach() argument must be of type array|object, null given” whenever a payment is recorded. Is that this module?
No — that warning comes from Perfex itself, and it appears for every payment your install records, whoever records it: this module, Perfex's own Add payment button, or Perfex's built-in Stripe gateway.
The cause is in application/libraries/sms/App_sms.php. Its list of SMS gateways is
only filled in when an SMS gateway module registers one, and Payments_model asks it to
fire the “payment recorded” SMS trigger after every payment without checking that the
list exists. On an install with no SMS gateway module the list is empty and the loop warns.
We verified this with no part of this module loaded at all — calling Perfex's own SMS method
on its own raises the same warning. We cannot fix it, because a module that edits Perfex core files
would break your next Perfex update and is not something we will ship. It is harmless: it is a
warning, not an error, nothing fails, and it is invisible unless you have error logging switched on.
Installing any SMS gateway module, or leaving display_errors off as you should in
production, makes it go away.