Skip to content

Commerce Architecture

This document describes the technical architecture of the core-commerce package, which provides billing, subscriptions, and payment processing for the Host UK platform.

Overview

The commerce module implements a multi-gateway payment system supporting cryptocurrency (BTCPay) and traditional card payments (Stripe). It handles the complete commerce lifecycle from checkout to recurring billing, dunning, and refunds.

┌─────────────────────────────────────────────────────────────────┐
│                        Commerce Module                          │
├─────────────────────────────────────────────────────────────────┤
│  Services Layer                                                 │
│  ┌─────────────┐ ┌──────────────┐ ┌───────────────┐            │
│  │ Commerce    │ │ Subscription │ │ Dunning       │            │
│  │ Service     │ │ Service      │ │ Service       │            │
│  └─────────────┘ └──────────────┘ └───────────────┘            │
│  ┌─────────────┐ ┌──────────────┐ ┌───────────────┐            │
│  │ Invoice     │ │ Coupon       │ │ Tax           │            │
│  │ Service     │ │ Service      │ │ Service       │            │
│  └─────────────┘ └──────────────┘ └───────────────┘            │
├─────────────────────────────────────────────────────────────────┤
│  Gateway Layer                                                  │
│  ┌──────────────────────┐  ┌──────────────────────┐            │
│  │ BTCPayGateway        │  │ StripeGateway        │            │
│  │ (Primary)            │  │ (Secondary)          │            │
│  └──────────────────────┘  └──────────────────────┘            │
│              │                          │                       │
│              └────────────┬─────────────┘                       │
│                           │                                     │
│              ┌────────────▼─────────────┐                       │
│              │ PaymentGatewayContract   │                       │
│              └──────────────────────────┘                       │
└─────────────────────────────────────────────────────────────────┘

Core Concepts

Orderable Interface

The commerce system uses polymorphic relationships via the Orderable contract. Both Workspace and User models can place orders, enabling:

  • Workspace orders: Subscription packages, team features
  • User orders: Individual boosts, one-time purchases
php
interface Orderable
{
    public function getBillingName(): string;
    public function getBillingEmail(): string;
    public function getBillingAddress(): array;
    public function getTaxCountry(): ?string;
}

Order Lifecycle

┌──────────┐    ┌────────────┐    ┌──────────┐    ┌────────┐
│ pending  │───▶│ processing │───▶│   paid   │───▶│refunded│
└──────────┘    └────────────┘    └──────────┘    └────────┘
     │               │
     │               │
     ▼               ▼
┌──────────┐    ┌──────────┐
│cancelled │    │  failed  │
└──────────┘    └──────────┘
  1. pending: Order created, awaiting checkout
  2. processing: Customer redirected to payment gateway
  3. paid: Payment confirmed, entitlements provisioned
  4. failed: Payment declined or expired
  5. cancelled: Customer abandoned checkout
  6. refunded: Full refund processed

Subscription States

┌────────┐    ┌──────────┐    ┌────────┐    ┌───────────┐
│ active │───▶│ past_due │───▶│ paused │───▶│ cancelled │
└────────┘    └──────────┘    └────────┘    └───────────┘
     │              │              │
     ▼              │              │
┌──────────┐        │              │
│ trialing │────────┘              │
└──────────┘                       │
     │                             │
     └─────────────────────────────┘
  • active: Subscription in good standing
  • trialing: Within trial period (no payment required)
  • past_due: Payment failed, within retry window
  • paused: Billing paused (dunning or user-initiated)
  • cancelled: Subscription ended

Service Layer

CommerceService

Main orchestration service. Coordinates order creation, checkout, and fulfillment.

php
// Create an order
$order = $commerce->createOrder($workspace, $package, 'monthly', $coupon);

// Create checkout session (redirects to gateway)
$checkout = $commerce->createCheckout($order, 'btcpay', $successUrl, $cancelUrl);

// Fulfill order after payment (called by webhook)
$commerce->fulfillOrder($order, $payment);

Key responsibilities:

  • Gateway selection and initialization
  • Customer management across gateways
  • Order-to-entitlement provisioning
  • Currency formatting and conversion

SubscriptionService

Manages subscription lifecycle without gateway interaction.

php
// Create local subscription record
$subscription = $subscriptions->create($workspacePackage, 'monthly');

// Handle plan changes with proration
$result = $subscriptions->changePlan($subscription, $newPackage, prorate: true);

// Pause/unpause with limits
$subscriptions->pause($subscription);
$subscriptions->unpause($subscription);

Proration calculation:

creditAmount = currentPrice * (daysRemaining / totalPeriodDays)
proratedNewCost = newPrice * (daysRemaining / totalPeriodDays)
netAmount = proratedNewCost - creditAmount

DunningService

Handles failed payment recovery with exponential backoff.

Day 0:  Payment fails → subscription marked past_due
Day 1:  First retry
Day 3:  Second retry
Day 7:  Third retry → subscription paused
Day 14: Workspace suspended (features restricted)
Day 30: Subscription cancelled

Configuration in config.php:

php
'dunning' => [
    'retry_days' => [1, 3, 7],
    'suspend_after_days' => 14,
    'cancel_after_days' => 30,
    'initial_grace_hours' => 24,
],

TaxService

Jurisdiction-based tax calculation supporting:

  • UK VAT (20%)
  • EU VAT via VIES validation
  • US state sales tax (nexus-based)
  • Australian GST (10%)

B2B reverse charge is applied automatically when a valid VAT number is provided for EU customers.

php
$taxResult = $taxService->calculate($workspace, $amount);
// Returns: TaxResult with taxAmount, taxRate, jurisdiction, isExempt

Payment Gateways

PaymentGatewayContract

All gateways implement this interface ensuring consistent behavior:

php
interface PaymentGatewayContract
{
    // Identity
    public function getIdentifier(): string;
    public function isEnabled(): bool;

    // Customer management
    public function createCustomer(Workspace $workspace): string;

    // Checkout
    public function createCheckoutSession(Order $order, ...): array;
    public function getCheckoutSession(string $sessionId): array;

    // Payments
    public function charge(Workspace $workspace, int $amountCents, ...): Payment;
    public function chargePaymentMethod(PaymentMethod $pm, ...): Payment;

    // Subscriptions
    public function createSubscription(Workspace $workspace, ...): Subscription;
    public function cancelSubscription(Subscription $sub, bool $immediately): void;

    // Webhooks
    public function verifyWebhookSignature(string $payload, string $sig): bool;
    public function parseWebhookEvent(string $payload): array;
}

BTCPayGateway (Primary)

Cryptocurrency payment gateway supporting BTC, LTC, XMR.

Characteristics:

  • No saved payment methods (each payment is unique)
  • No automatic recurring billing (requires customer action)
  • Invoice-based workflow with expiry
  • HMAC signature verification for webhooks

Webhook Events:

  • InvoiceCreated → No action
  • InvoiceReceivedPayment → Order status: processing
  • InvoiceProcessing → Waiting for confirmations
  • InvoiceSettled → Fulfill order
  • InvoiceExpired → Mark order failed

StripeGateway (Secondary)

Traditional card payment gateway.

Characteristics:

  • Saved payment methods for recurring
  • Automatic subscription billing
  • Setup intents for card-on-file
  • Stripe Customer Portal integration

Webhook Events:

  • checkout.session.completed → Fulfill order
  • invoice.paid → Renew subscription
  • invoice.payment_failed → Trigger dunning
  • customer.subscription.deleted → Revoke entitlements

Data Models

Entity Relationship

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│  Workspace  │────▶│   Order     │────▶│  OrderItem  │
└─────────────┘     └─────────────┘     └─────────────┘
       │                   │
       │                   ▼
       │            ┌─────────────┐     ┌─────────────┐
       │            │   Invoice   │────▶│InvoiceItem  │
       │            └─────────────┘     └─────────────┘
       │                   │
       │                   ▼
       │            ┌─────────────┐     ┌─────────────┐
       └───────────▶│   Payment   │────▶│   Refund    │
                    └─────────────┘     └─────────────┘


┌─────────────┐     ┌─────────────┐
│   Coupon    │────▶│ CouponUsage │
└─────────────┘     └─────────────┘

Multi-Entity Commerce (M1/M2/M3)

The commerce module supports a hierarchical entity structure:

  • M1 (Master Company): Source of truth, owns product catalog
  • M2 (Facade/Storefront): Selects from M1 catalog, can override content
  • M3 (Dropshipper): Full inheritance, no management responsibility
          ┌─────────┐
          │   M1    │ ← Product catalog owner
          └────┬────┘

      ┌────────┴────────┐
      │                 │
┌─────▼─────┐     ┌─────▼─────┐
│    M2     │     │    M2     │ ← Storefronts
└─────┬─────┘     └───────────┘

┌─────▼─────┐
│    M3     │ ← Dropshipper
└───────────┘

Permission matrix controls which operations each entity type can perform, with a "training mode" for undefined permissions.

Event System

Domain Events

php
// Dispatched automatically on model changes
SubscriptionCreated::class RewardAgentReferralOnSubscription
SubscriptionRenewed::class ResetUsageOnRenewal
OrderPaid::class CreateReferralCommission

Listeners

  • ProvisionSocialHostSubscription: Product-specific provisioning logic
  • RewardAgentReferralOnSubscription: Attribute referral for new subscriptions
  • ResetUsageOnRenewal: Clear usage counters on billing period reset
  • CreateReferralCommission: Calculate affiliate commission on paid orders

Directory Structure

core-commerce/
├── Boot.php                 # ServiceProvider, event registration
├── config.php               # All configuration (currencies, gateways, tax)
├── Concerns/                # Traits for models
├── Console/                 # Artisan commands (dunning, reminders)
├── Contracts/               # Interfaces (Orderable)
├── Controllers/             # HTTP controllers
│   ├── Api/                 # REST API endpoints
│   └── Webhooks/            # Gateway webhook handlers
├── Data/                    # DTOs and value objects
├── Events/                  # Domain events
├── Exceptions/              # Custom exceptions
├── Jobs/                    # Queue jobs
├── Lang/                    # Translations
├── Listeners/               # Event listeners
├── Mail/                    # Mailable classes
├── Mcp/                     # MCP tool handlers
├── Middleware/              # HTTP middleware
├── Migrations/              # Database migrations
├── Models/                  # Eloquent models
├── Notifications/           # Laravel notifications
├── routes/                  # Route definitions
├── Services/                # Business logic layer
│   └── PaymentGateway/      # Gateway implementations
├── tests/                   # Pest tests
└── View/                    # Blade templates and Livewire components
    ├── Blade/               # Blade templates
    └── Modal/               # Livewire components (Admin/Web)

Configuration

All commerce configuration lives in config.php:

php
return [
    'currency' => 'GBP',           // Default currency
    'currencies' => [...],          // Supported currencies, exchange rates
    'gateways' => [
        'btcpay' => [...],          // Primary gateway
        'stripe' => [...],          // Secondary gateway
    ],
    'billing' => [...],             // Invoice prefixes, due days
    'dunning' => [...],             // Retry schedule, suspension timing
    'tax' => [...],                 // Tax rates, VAT validation
    'subscriptions' => [...],       // Proration, pause limits
    'checkout' => [...],            // Session TTL, country restrictions
    'features' => [...],            // Toggle coupons, refunds, trials
    'usage_billing' => [...],       // Metered billing settings
    'matrix' => [...],              // M1/M2/M3 permission matrix
];

Testing

Tests use Pest with RefreshDatabase trait:

bash
# Run all tests
composer test

# Run specific test file
vendor/bin/pest tests/Feature/CheckoutFlowTest.php

# Run tests matching pattern
vendor/bin/pest --filter="proration"

Test categories:

  • CheckoutFlowTest: End-to-end order flow
  • SubscriptionServiceTest: Subscription lifecycle, proration
  • DunningServiceTest: Payment recovery flows
  • WebhookTest: Gateway webhook handling
  • TaxServiceTest: Tax calculation, VAT validation
  • CouponServiceTest: Discount application
  • RefundServiceTest: Refund processing

Released under the EUPL-1.2 License.