This project is not covered by Drupal’s security advisory policy.
Billing Hub
Gateway-agnostic billing layer for Drupal 10 / 11. Manages subscriptions,
invoices, tax, and dunning. Integrates with Membership Manager for access
control. Supports Stripe, PayPal, Braintree, and custom payment gateways via a Plugin API.
Requirements
- Drupal 10.2+ or Drupal 11
- PHP 8.2+
drupal:user(core)membership_managermodule
Gateway dependencies
# Stripe composer require stripe/stripe-php # PayPal composer require paypal/paypal-server-sdk
Installation
composer require drupal/billing_hub drush en billing_hub billing_hub_stripe drush cr
Visit /admin/billing to configure.
Architecture Overview
CheckoutService → BillingManagerService → GatewayPlugin BillingPortalController → BillingManagerService → SubscriptionService WebhookController → WebhookDispatcherService → BillingManagerService DunningService → Queue → DunningQueueWorker → GatewayPlugin::retryInvoice() TaxService ← BillingPlan + TaxRate AnalyticsService ← billing_subscription + billing_invoice tables
BillingManagerService is the central entry point.
Billing Plans
| Field | Description |
|---|---|
| Machine name | Used in code |
| Gateway | Gateway plugin |
| Price | Base price |
| Currency | ISO 4217 |
| Interval | day / week / month / year |
| Interval count | Billing frequency |
| Trial days | Trial duration |
| Proration | Enable credits |
| Dunning policy | cancel / downgrade / suspend |
| Tax mode | exclusive / inclusive / none |
| Membership plan | Linked membership |
Gateway Configuration
| Field | Description |
|---|---|
| Plugin ID | stripe / paypal / braintree |
| Test mode | Sandbox mode |
| Settings | API keys |
Stripe example
secret_key: sk_live_... publishable_key: pk_live_... webhook_secret: whsec_... test_mode: false
Checkout Flow
PHP Example
$checkout = \Drupal::service('billing_hub.checkout'); $preview = $checkout->preview( plan_id: 'pro_monthly', country_code: 'DE', tax_id: 'DE123456789' ); $result = $checkout->checkout( user: $user, plan_id: 'pro_monthly' );
AJAX Preview
fetch('/billing/checkout/pro_monthly/preview', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ country_code: 'DE' }) });
Subscription Lifecycle
active → canceled active → paused → active active → past_due → unpaid trialing → active
Webhooks
| Gateway | URL |
|---|---|
| Stripe | /billing/webhook/stripe |
| PayPal | /billing/webhook/paypal |
| Braintree | /billing/webhook/braintree |
Dunning
Retry schedule: 0, 1, 3, 7, 14 days
| Policy | Effect |
|---|---|
| cancel | Subscription canceled |
| downgrade | Assign fallback plan |
| suspend | Keep record |
Tax
Modes
| Mode | Description |
|---|---|
| exclusive | Tax added |
| inclusive | Tax included |
| none | No tax |
VAT validation hook
function mymodule_billing_hub_vat_validate(string $vat_number): bool { return my_vies_client()->validate($vat_number); }
Analytics
$analytics = \Drupal::service('billing_hub.analytics'); $analytics->getMRR(); $analytics->getARR(); $analytics->getChurnRate(30);
User Billing Portal
| Route | Description |
|---|---|
| /billing/portal | Overview |
| /billing/portal/invoices | Invoices |
| /billing/portal/cancel/{id} | Cancel |
| /billing/portal/change-plan/{id} | Change plan |
Membership Integration
BillingPlan.membership_plan_id → assignMembership() → MembershipManagerService::assign()
Custom Gateway
/** * @Gateway( * id = "my_gateway", * label = @Translation("My Gateway") * ) */ class MyGateway extends GatewayPluginBase {}
Hooks
Coupon
function mymodule_billing_hub_coupon_validate(string $code): float { return 10.0; }
Redirect
function mymodule_billing_hub_checkout_success_url(...) { return '/members/welcome'; }
Maintainers
Dmytro Porokhnya
License
GPL-2.0-or-later
Project information
- Project categories: Integrations
- Ecosystem: Membership Manager
- Created by darkdim on , updated
This project is not covered by the security advisory policy.
Use at your own risk! It may have publicly disclosed vulnerabilities.
