VoltexaHubDocumentation1.8.0Install the beta
Browse documentation

Reference

Billing reference

Exact provider setup, webhook and entitlement lifecycle behavior.

Includes the complete docs/BILLING.md reference from this build. Application paths refer to your own installation.

On this page

Payments and memberships#

VoltexaHub supports Stripe and PayPal hosted checkout for recurring memberships, one-time memberships and fixed-amount donations. Payment card details stay with the provider. New payments are disabled by default; an unconfigured installation makes no provider requests. Donations never grant group membership. Supported currencies are USD, EUR, GBP, CAD, AUD and NZD, all with two decimal places; prices must use exact 10.00 notation. Tax calculation, coupons, trials, prorations, plan upgrades and multi-price subscriptions are outside this initial billing scope.

Configure and offer plans#

Set BILLING_ENABLED=true only after provider setup and sandbox verification. Stripe needs STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET; PayPal needs PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PAYPAL_WEBHOOK_ID and PAYPAL_MODE=sandbox|live. Store these in the environment, never public settings, plugin manifests, logs or source control. Use an HTTPS APP_URL, database queue and shared database cache (QUEUE_CONNECTION=database, CACHE_STORE=database) on every web/worker instance. The queue worker needs the same billing configuration. See worker operations.

In Administration → Payments → Plans → Create plan, select a dedicated ordinary member group, enter its price/currency and configure duration or recurrence. Use a Stripe Price ID for all Stripe plans; use a PayPal Plan ID for recurring memberships. PayPal one-time memberships/donations use the configured amount. Provider price/plan terms are checked before checkout: exact amount, currency and interval, no trial/setup fees or additional taxes. PayPal supports one regular cycle only. Enable the community's “Allow new payments” control on Payments → Overview and the plan after sandbox verification. Existing plans cannot be repriced or have their benefits edited; create a replacement and disable the old plan. Provider-side price changes must use a replacement too. Disabling a plan prevents new checkout and retries of its pending attempts; existing paid benefits, cancellation and reconciliation continue. Review payment records and recheck actions under Payments → Transactions.

Configure Stripe snapshot webhooks with API version 2025-06-30.basil at /billing/webhooks/stripe. Include checkout session completion/expiry/async success/failure, invoice paid/payment failure, subscription updated/deleted, charge refunded and charge dispute created/updated events. The built-in API client pins this version. Configure the PayPal REST app webhook at /billing/webhooks/paypal for checkout orders, subscription activation/update/cancellation/suspension/payment failure, completed/refunded/reversed sales/captures and customer disputes. Keep its registered webhook ID and credentials together for the selected mode. PayPal verification sends the original event bytes in the signed verification envelope and uses PayPal's fixed API host. No arbitrary certificate URL is fetched by VoltexaHub.

Configure Stripe's customer portal for payment-method management. The app requests the payment-method-update flow and handles renewal cancellation itself. PayPal management opens the provider's automatic-payments account page. Members use Account menu → Memberships & payments to buy, check payment, manage payment methods or confirm cancellation. Repeated clicks/retries reuse the same pending persisted source and provider idempotency key, including after a timeout. Completed donations allow another payment; one-time membership can be purchased again after expiry. Current one-time memberships (including lifetime) and recurring enrollments reject duplicate checkout in the same plan. Returning from checkout queues verification and never grants access directly. A pending PayPal order is captured only after the provider confirms approval.

Access and lifecycle#

Billing stores independent paid entitlements and never adds, removes or overwrites manual group_user membership. Current paid entitlements are combined with manual groups by every core permission path, including search/counts and batched recipients. Existing explicit denies and bans still win. Paid groups cannot be built-in groups or grant anything outside forum.view, thread.create, post.reply, post.vote, post.edit.own; unknown plugin abilities fail closed. A later staff/unknown permission added to a paid group immediately excludes it from paid access. Administrators can still assign that group manually.

Recurring access requires a verified successful payment and its paid-through period, never subscription status alone. No trial grants exist. Failed renewal does not extend access. Cancellation stops future renewal while preserving already-confirmed paid time; no credit or automatic refund is issued by cancellation. One-time membership lasts its configured days from confirmed payment time or indefinitely when explicitly configured as lifetime. Purchases of different plans are independent sources and durations do not stack; refunding one does not remove a different paid source or manual membership. Expiry is checked on every permission read even while queues are unavailable.

Signed refunds/reversals/disputes immediately and conservatively revoke the affected source. Revocation is sticky: old success events and later provider status changes cannot reinstate it. Even a partial refund or subsequently resolved dispute requires a new purchase to regain benefits from that source; handle exceptions manually with ordinary admin membership controls. A provider-side dispute/refund does not delete the account or its posts. Deleting an account removes local billing/entitlement rows but does not cancel provider subscriptions; cancel at the provider before deleting the account. Financial records retained by providers are governed by their own policies.

Webhooks verify signatures, durably deduplicate event IDs and enqueue canonical provider reads. Jobs hold the source row lock through bounded canonical fetch/application, so concurrent or out-of-order events cannot overwrite newer state with a previously fetched response. Stripe examines the newest paid invoice/payment charge; PayPal verifies the last successful payment against transactions and checks its disputes. Older refund/dispute events remain important: the reconciliation history is bounded to the latest confirmed payment, not an unlimited financial-history audit. Reconciliation runs every 15 minutes per source with a fair batch of at most 100 queued per minute. Unique jobs use the shared cache to suppress duplicate pending/retrying jobs for up to 24 hours during outages. Successful/retried jobs release uniqueness; a queue manually cleared without processing may require up to 24 hours before automatic recovery. Expired uniqueness and interrupted provider responses never bypass the database source lock or grant unverified access.

Verification and recovery#

Provider failures leave paid time unchanged and never grant additional time. Webhook receipts remain pending until canonical reconciliation succeeds. Check php artisan queue:failed, repair configuration/connectivity, retry failed job IDs and run php artisan billing:enqueue for bounded recovery. The bundled worker supervises queue processing and a reconciliation loop; it safely waits for billing schema during rolling deployment. Restart workers after changing keys, provider code or plans installed through plugins. Global checkout disable retains records and paid access and continues configured webhooks/reconciliation; it is not a billing-cancellation or access-revocation switch.

Before accepting real payments, verify each provider's sandbox checkout, successful payment, recurring renewal/cancellation, refund/dispute, webhook delivery and worker application end to end. Automated HTTP contract tests do not prove an actual merchant account, product, webhook or settlement is configured correctly. No sandbox credentials are bundled. Core stores only plan terms, provider/source references, status/times and minimal webhook receipt metadata; no public voter or payer list and no raw webhook payload retention. Provider extensions can add other payment methods, including crypto, with the same confirmed-payment and revocation safeguards.

Primary references: Stripe Checkout, Stripe webhooks, Stripe invoice payments, PayPal webhooks, PayPal subscriptions, PayPal orders, PayPal transactions.

Configure a paid forum#

The default Members group allows forum.view globally. Adding a paid-group allow alone therefore does not make a forum private. A working setup is: change the Members global forum.view rule to deny; add explicit Members allows on every public forum/category; add an explicit paid-group allow on the paid forum. The paid forum's scoped allow overrides the inherited global member deny. Give Guests a scoped deny on the paid forum as well. Do not add a Members deny on that same paid forum: a deny at the same scope wins over any paid-group allow. Verify directory, discussion, search and counts as a member before purchase, after purchase and after expiry. Use Permissions explanation controls to inspect the winning scope and group, and keep an administrator account for recovery.