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_manager module

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

Releases