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?

Does this work with the SaaS / multi-tenant module for Perfex?

It is built to the multi-tenant rules, yes:

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:

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:

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:

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:

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:

GatewayDefault currency list
Card (Stripe)USD,EUR,GBP
GoCardlessGBP,EUR
PaystackNGN,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:

  1. Cron single-flight. A MySQL advisory lock; a second overlapping run returns immediately instead of charging anything.
  2. 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.
  3. One attempt row per attempt number. A unique index on (invoice, attempt number) makes a duplicate attempt impossible to open.
  4. 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.
  5. 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:

ActionLogin 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:

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:

  1. 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.
  2. 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.
  3. 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:

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.

SettingShips atWhat it does
Charge late fees on overdue invoicesOff 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 typePercentage A percentage of the outstanding balance, or a fixed amount.
Percentage / fixed amountBoth 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 period7 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 accruesOnce only Once, daily, weekly or monthly. A repeating fee accrues at most once per period.
Maximum number of accruals12 The ceiling that lets a repeating fee stop. It matters: a fee can only be billed once its accrual has ended.
Cap0 — no cap A percentage of the original invoice total, evaluated against everything accrued so far.
Raise the fee invoice automaticallyOff A second, separate switch. With it off, fees accrue and wait for a staff member to press Invoice now.
Minimum accrued fee before invoicing0 — 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 descriptionLate 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:

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.

DeactivateUninstall
Module tables and dataUntouched Kept, unless you first switch on Delete all module data on uninstall in Settings (off by default)
Module settingsKeptRemoved
Gateway settings and keysKeptRemoved
The eleven email templatesKeptRemoved
Staff permissions for the moduleKeptRemoved
ChargingStopsStops

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 subscriptionSuspend, then terminate it
What it doesMoves the subscription to Suspended and stops collectingThe same, and then moves it to Terminated after a waiting period
ReversibleYes — automatically on payment, or by hand Only while it is still suspended
Cancellation date and reason recordedNo, deliberately No — the status itself is the record
The customer's stored cardStill 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:

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:

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:

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:

Out of scope, honestly stated up front:

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.