Skip to content

Recurring auto-pay (tokenized subscriptions) — WHMCS-style #34

Description

@Panelica

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

  1. 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.
  2. Real card vault. Replace the display-only PaymentMethodController::store (persists last_four only) with a gateway-driven save flow (Stripe SetupIntent, Iyzico card storage, ...).
  3. 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.
  4. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:billingInvoices, orders, taxes, couponsarea:gatewayPayment gateways (Stripe, PayPal, Bank Transfer, Authorize.Net)enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions