Summary
PNLCS bills renewals WHMCS-style: a scheduler generates renewal invoices from next_due_date + recurring_amount and the customer pays each one interactively. There is no auto-pay today — saved "payment methods" store only the last 4 digits (PaymentMethodController::store), and the isTokenised() / payment_methods.remote_token scaffolding is currently unused.
This epic adds tokenized recurring auto-pay (a "subscription" experience) the way WHMCS does it: PNLCS invoicing stays the single source of truth, and the customer's stored card is charged automatically, off-session, when a renewal invoice is generated.
We deliberately do not use each gateway's native subscription product (e.g. Stripe Billing). A PNLCS invoice can mix a hosting service, addons, a domain, one-off charges, tax and promos; a native gateway subscription is a single recurring price and cannot represent that. Tokenized auto-capture on our own invoices is the correct model (and the one WHMCS uses).
Architecture
- Gateway capability contract. Introduce a
Tokenizable capability (gated by the existing isTokenised()) with two operations: (a) save a card — create/reuse a gateway customer and persist a reusable token in payment_methods.remote_token plus brand/last4/expiry for display; (b) charge a stored token off-session for a given Invoice. app/Contracts/GatewayModuleInterface.php stays as-is for interactive payment; tokenizing gateways additionally implement the new contract.
- Real card vault. Replace the display-only
PaymentMethodController::store (persists last_four only) with a gateway-driven save flow (Stripe SetupIntent, Iyzico card storage, ...).
- Auto-pay engine. After
InvoiceGenerationService generates a renewal invoice (services filtered by auto_renew, InvoiceGenerationService.php:42), a new step attempts an off-session charge on the client's default stored method when auto-pay is enabled -> mark the invoice paid + activate the service. On failure: dunning (scheduled retries + notification), with manual payment as the fallback.
- One-click toggle. Reuse the existing
service.auto_renew flag + toggle UI (today it only means "generate a renewal invoice") and add a global "enable auto-pay" setting + a per-client default. This is the one-click on/off.
Scope split
- Stripe is the reference implementation and lands the gateway-agnostic core — maintainer-owned.
- Every other tokenizing gateway is a separate, independent contributor task implementing the same contract against its own API:
- Iyzico (Card Storage / Subscription API)
- Authorize.Net (CIM profiles + ARB)
- Mollie (Customers + recurring payments)
- PayPal (Vault / Billing Agreements)
- Razorpay (Tokens)
- Tpay: possible later (card tokenization). BankTransfer: out of scope (manual method, cannot auto-charge).
For contributors
The per-gateway issues are help-wanted. They depend on the capability contract landing with the Stripe reference implementation — use the Stripe module as the template, and keep PNLCS invoicing as the source of truth (do not wire a gateway's native subscription product). Comment here to claim a gateway.
Sub-issues
Summary
PNLCS bills renewals WHMCS-style: a scheduler generates renewal invoices from
next_due_date + recurring_amountand the customer pays each one interactively. There is no auto-pay today — saved "payment methods" store only the last 4 digits (PaymentMethodController::store), and theisTokenised()/payment_methods.remote_tokenscaffolding is currently unused.This epic adds tokenized recurring auto-pay (a "subscription" experience) the way WHMCS does it: PNLCS invoicing stays the single source of truth, and the customer's stored card is charged automatically, off-session, when a renewal invoice is generated.
We deliberately do not use each gateway's native subscription product (e.g. Stripe Billing). A PNLCS invoice can mix a hosting service, addons, a domain, one-off charges, tax and promos; a native gateway subscription is a single recurring price and cannot represent that. Tokenized auto-capture on our own invoices is the correct model (and the one WHMCS uses).
Architecture
Tokenizablecapability (gated by the existingisTokenised()) with two operations: (a) save a card — create/reuse a gateway customer and persist a reusable token inpayment_methods.remote_tokenplus brand/last4/expiry for display; (b) charge a stored token off-session for a givenInvoice.app/Contracts/GatewayModuleInterface.phpstays as-is for interactive payment; tokenizing gateways additionally implement the new contract.PaymentMethodController::store(persistslast_fouronly) with a gateway-driven save flow (Stripe SetupIntent, Iyzico card storage, ...).InvoiceGenerationServicegenerates a renewal invoice (services filtered byauto_renew,InvoiceGenerationService.php:42), a new step attempts an off-session charge on the client's default stored method when auto-pay is enabled -> mark the invoice paid + activate the service. On failure: dunning (scheduled retries + notification), with manual payment as the fallback.service.auto_renewflag + toggle UI (today it only means "generate a renewal invoice") and add a global "enable auto-pay" setting + a per-client default. This is the one-click on/off.Scope split
For contributors
The per-gateway issues are help-wanted. They depend on the capability contract landing with the Stripe reference implementation — use the Stripe module as the template, and keep PNLCS invoicing as the source of truth (do not wire a gateway's native subscription product). Comment here to claim a gateway.
Sub-issues