Integrations
WHMCS

WHMCS

The existing Lunixi form opens on the merchant website by default. Hosted checkout remains optional and uses only the validated backend paymentPageUrl. Its opaque token can differ from the embedded token; the module never builds this URL from that token. Missing or legacy URLs keep embedded checkout available; hosted checkout stays on hold/reconciliation without a new intent or key. Existing attempts keep their saved mode, request body, profile and key. The official form/panel controls available methods and multi-card options.

Verified public merchant ZIP 0.1.2: lunixi-whmcs-0.1.2.zip (opens in a new tab) · SHA256SUMS (opens in a new tab) · Release v0.1.2 (opens in a new tab). The CI-built installation package was downloaded and verified on 2026-10-31. Use this asset rather than GitHub's automatic source archive.

Public CI ZIP SHA256: 940aa94ee9a0962645ba76d4eedc0e83a13d3aa280ba94d02940d4633f5547c5.

Lunixi Payments accepts WHMCS invoice payments through the hosted pay.lunixi.com form or the embedded Lunixi SDK form. Methods, split cards, installments, eligible alternative methods and insurance remain configured in Lunixi. The module never collects raw card data.

The gateway and companion admin addon bundle the namespace-isolated PHP SDK. Shared Commerce code owns signing, integer money, durable attempts, raw-byte webhook authentication and settlement evidence. Merchant installation needs no Composer.

⚠️

Version 0.1.2 targets official WHMCS 8.13 / 9.0 / 9.1 contracts with 64-bit PHP 8.2+. No licensed WHMCS runtime was available for native acceptance. Executed tests use actual MariaDB/InnoDB and Illuminate with clearly identified official-contract stand-ins. Complete the repository's licensed runtime acceptance before deployment; source publication does not imply WHMCS Marketplace approval.

Requirements and installation

  • A separately licensed WHMCS installation, its supported PHP/ionCube combination, PHP Sodium/cURL/JSON/PDO MySQL and InnoDB financial tables.
  • HTTPS SystemURL, a registered Lunixi merchant/key and matching webhook connection.
  • A separately backed-up 32-byte server encryption key, outside the web root/database.

Source: Lunixi-Payment/lunixi-whmcs (opens in a new tab). Use the built lunixi-whmcs-0.1.2.zip, not a GitHub source archive. Extract into the WHMCS root; it supplies modules/gateways/lunixi.php, modules/gateways/lunixi/, modules/gateways/callback/lunixi.php and modules/addons/lunixi_payments/.

Generate the independent key locally:

php -r 'echo base64_encode(random_bytes(32)), PHP_EOL;'

Set LUNIXI_WHMCS_ENCRYPTION_KEY in both web and CLI PHP worker environments. Keep it stable across upgrades. Loss prevents decryption of historical credentials/tokens; do not derive it from native/public IDs or a merchant/webhook/database secret.

  1. Activate Lunixi Payments in System Settings → Addon Modules and allow only appropriate admin roles.
  2. Open Addons → Lunixi Payments and create an immutable encrypted profile.
  3. Activate Lunixi Payments under Payment Gateways, then enter the created profile ID.
  4. Register the exact webhook URL shown beside that profile, including ?profile=ID, in Lunixi. Match TEST/LIVE environment and payment event subscriptions.

Merchant profile

SettingPurpose
Merchant IDOriginal settlement identity
Key ID / Ed25519 private keyServer-side authenticated API access
Webhook signing secretRaw-byte signed merchant delivery authentication
TEST / LIVEMust match the actual key and webhook connection
Hosted / embeddedpay.lunixi.com redirect or embedded official Lunixi form
Authorize first, capture laterKeep the invoice unpaid until complete capture

Key entry is in the companion addon. Private key/webhook fields are password inputs and are never populated back. Profiles use authenticated encryption with the separate server key. The approved origin is fixed to https://api-gateway.lunixi.com; the key selects actual API environment, without an invented request parameter.

A newer profile rotates authentication/webhook secrets only within the same merchant AND TEST/LIVE environment. Historical profile identity, invoice snapshot and checkout settings remain fixed. Other merchants/environments cannot take over old attempts. Retain encrypted profiles and the server key through the refund/reconciliation retention period.

Checkout and native accounting

The selected authenticated WHMCS client account's owner must submit a native CSRF-protected POST before a token is exposed. Version 0.1.2 excludes delegated client users and admin client-area masquerading; a native owner session is required.

Outstanding principal is computed from native DECIMAL invoice total, applied credit and existing partial-payment transactions as exact minor-unit integers. Browser amounts and floating-point balance accessors are not used. Unsupported precision, conflicting transaction currencies and nonpositive balances fail before an intent request.

The reservation commits before network I/O. Timeouts reuse the exact request/idempotency key. An expired token does not authorize another charge. An administrator can reset only an authenticated terminal failure with no captured funds while retaining history.

  • Hosted form redirects only to paymentPageUrl.
  • Embedded form loads the fixed official https://cdn.lunixi.com/lunixi-checkout.min.js and mounts browser-safe JSON; gateway HTML is never injected.
  • Authorization and partial capture stay unpaid. Only complete authenticated settlement credits invoice principal once.
  • Browser returns only redirect the authenticated owner to the invoice; return status never marks it paid.

Invoice owner, currency, total, credit and external transaction fingerprint are rechecked under lock. Later edits/outside payments require review instead of overcrediting. Cancelled invoices are not automatically paid. Buyer installment fees become separate noninvoice income, never extra invoice/client credit or a processor-fee field. Refund allocation is explicitly principal first, then installment fee, with native original-transaction relationships.

Verified webhook and reconciliation

The shared SDK checks bounded exact raw bytes, v2 HMAC, fresh timestamp, signed event ID/type/environment, persistent replay/payload collisions and authenticated GET identity/settlement. A signed status alone is insufficient. Lost create-response recovery additionally requires the persisted opaque attempt/fingerprint echoed by authenticated GET.

Ledger, webhook receipt, native invoice/account transactions and encrypted outbox use the same native Capsule connection and transaction, with serialized invoice locking. Native failure rolls all back. Direct PDO transaction starts are not used. Split parent settlement requires complete, balanced, uncompensated aggregate capture; one paid leg cannot complete an invoice.

Run maintenance every few minutes with the web worker's key:

php /path/to/whmcs/modules/gateways/lunixi/cron.php

The command reconciles fair batches by last-check time and drains postcommit jobs. Monitor nonzero exits and the addon outbox panel.

Capture, refund and void

In the addon, inspect the invoice and Prepare durable operation with an exact decimal amount (empty for void). Review the saved ID/action/amount and Execute / retry same operation. Native addon-role access, Manage Invoice permission, POST and admin CSRF are required; profile creation additionally needs Configure Payment Gateways.

An HTTP 2xx remains pending until authenticated settlement confirms the requested delta. New IDs are blocked while an operation is unresolved; retry its original ID. The standard WHMCS refund callback returns an actionable error pointing to this screen because its official input has no durable refund-request ID. The addon owns both mutation and native bookkeeping, avoiding a second native refund after callback return.

Monetary refunds do not invent product/VAT credit notes or WHMCS billing-note allocations. Handle those under native accounting policy without repeating the gateway refund. Held partial-capture refunds require accounting review. Split refunds may need reconciliation for a nonrefundable method.

Postcommit effects and retention

Native payment emails and create/unsuspend actions are deferred during financial application. After commit, the worker checks the confirmed paid invoice/client, native service status and current order hold, then uses documented ModuleCreate / ModuleUnsuspend APIs once. Fraud/cancelled/custom order holds remain under review. Already Active services are skipped; Pending/Suspended respectively permit the action. Durable job claims precede remote commands. A crash/timeout leaves sending/uncertain for review, without blind retries.

Renew/change-package/custom provisioning remains held for native operator review. The addon shows job IDs/references and permits acknowledgment after the operator checks actual delivery/remote service state. Acknowledgment records review; it never sends another command. Installed third-party hooks and actual WHMCS billing-note/renewal/email behavior must pass licensed acceptance.

Deactivation preserves profiles, ledger, events, operations, outbox and native financial history. Keep the independent key and merchant access for the retention period. No automatic table-drop uninstall is shipped.

Build and validation

composer install --working-dir=tools --no-interaction
php tools/build.php
php tools/check-artifact.php
php tools/vendor/bin/phpcs --standard=phpcs.xml
php tools/vendor/bin/phpstan analyse -c phpstan.neon --no-progress
composer audit --working-dir=tools

Default builds resolve the published lunixi/php-sdk:0.29.5 package. Maintainers can instead pass an explicit reviewed SDK checkout to the build command. The input path never enters the ZIP; scoped runtime has no sibling checkout/site Composer dependency.

Database/contract tests require explicit LUNIXI_CONTRACT_TEST=1, an external connection JSON and a disposable database named lunixi_whmcs. They recreate its tables and check concurrency, exact balances, rollback/replay, identity/environment/rotation, native argument contracts, capture/refund/void, async uncertainty and postcommit effects. PHPStan 8 uses narrow official WHMCS declarations; native runtime source is not redistributed.

Run the supplied licensed acceptance runner on an operator-provided licensed disposable installation, then complete the TEST-merchant browser/ACL/accounting/notification matrix. See repository acceptance (opens in a new tab) and security policy (opens in a new tab).

Financial outcomes that remain uncertain are held for review. FAILED alone does not authorize a new attempt: the public payment read does not expose definitive provider outcome certainty. Unresolved financial retries stop after 23 hours; authenticated GET reconciliation remains available. A timeout, token expiry or browser callback never generates a fresh financial key.

The informational POST return runs before WHMCS session/CSRF/runtime bootstrap. It reads only invoiceid from the URL query, discards at most 1 MiB of raw body and ignores every submitted field. It performs no invoice lookup, gateway request or financial write, and sends an empty relative 303 to return.php?invoiceid=.... The resumed GET still bootstraps WHMCS and checks native invoice ownership before redirecting to the invoice. Initial checkout keeps its native CSRF requirement. Configure matching proxy body limits; PHP may buffer POST input before this entry point executes.

Release 0.1.2 checks passed 36 tests / 146 assertions using real MariaDB/InnoDB, Illuminate and the scoped PHP SDK 0.29.5. They include the actual embedded HTML/JavaScript object/mount/XSS contract and a real HTTP server executing the built return entry point without WHMCS bootstrap: forged fields, an empty POST and an oversized POST. PHPStan level 8, PHPCS, dependency audits and merchant ZIP/secret checks also pass. These are official-contract tests, not licensed WHMCS acceptance.