Skip to main content
Any product variant in Site Store Pro can be configured as a recurring subscription by linking it to a Stripe, Paddle, or PayPal Plan/Price ID, or by enabling automatic Stripe/Paddle/PayPal on-the-fly plan creation. This document covers how subscription variants are detected, the mixed-cart policy, checkout routing, line-item tracking, database schema, cancellation mechanics, and the webhook events that drive automated subscription lifecycle management.

1. Overview

Subscription billing in Site Store Pro is configured at the variant level. Any product variant can be turned into a recurring subscription by linking it to a gateway Price/Plan ID or by letting the system automatically generate the subscription item and pricing on-the-fly.

2. How a Variant Becomes a Subscription

A variant is treated as a subscription when any gateway Price/Plan ID is configured, when a recurring billing interval is defined, or when auto-creation is enabled:
The cart and checkout router call isSubscriptionVariant() on every item in the cart to determine single-intent routing and enforce the single/mixed-cart policies.

3. Admin Configuration by Gateway

Navigate to Admin → Products → Edit Product → Prices & Variants → Open or Create a variant → Payment Processor IDs section.

1. Stripe

Enter the Price IDs you already created in the Stripe Dashboard (Products → Prices).Use this when you want full control over the Stripe product catalog.

2. Paddle Billing (Automatic Non-Catalog Items & Catalog Price IDs)

Paddle Billing supports both pre-configured catalog Price IDs and automatic non-catalog subscription item creation:
If no Paddle Price ID is entered, the system automatically creates the subscription item and recurring price on Paddle on-the-fly during checkout.
  • How it works: The system constructs a dynamic non-catalog transaction item containing the variant title, price, currency, billing interval (paddle_interval: month, year, week, day), frequency multiplier (paddle_frequency), and trial terms.
  • No Dashboard Setup Required: You do not need to log into the Paddle Dashboard to pre-create products or price IDs.
  • Discount & Currency Friendly: Automatically handles item-level discounts, order-level coupons, and cross-border VAT adjustments dynamically on the recurring price.

3. PayPal Subscriptions


4. Mixed-Cart Policy

Subscription items and regular (one-time) items cannot be combined in the same cart. This is enforced at cart-add time with a user-facing error message. The customer must remove conflicting items before proceeding.
This restriction exists because subscription checkout routes through a dedicated gateway subscription agreement method (createSubscription, createTransaction(price_id=...), or PayPal Subscriptions SDK) and requires a single-intent recurring agreement.

5. Checkout Routing & Creation Flows

The checkout system automatically detects subscription variants and routes to the correct gateway flow:

Stripe Subscription Creation Flow

  1. Customer Resolution: Looks up users.stripe_customer_id. If missing, creates a customer in Stripe and saves the ID.
  2. Price Selection: Uses configured stripe_sandbox_price_id / stripe_live_price_id or creates a Product + Price on-the-fly.
  3. Trial Setup: Applies trial_period_days if stripe_trial_enabled = 1.
  4. Client Secret: Returns the client_secret from the subscription’s latest invoice’s PaymentIntent for Stripe Elements confirmation.

Paddle Subscription Flow

  1. Price Linking & Dynamic Auto-Creation:
    • If a paddle_price_id is present and matches the final price, it is sent as the catalog item.
    • If no Paddle Price ID is entered, the system automatically generates a dynamic non-catalog recurring item with the exact calculated unit amount, interval (month, year, week), frequency, and trial period.
  2. Checkout Overlay: Passes the transaction_id to Paddle.js to render the checkout.
  3. Subscription Confirmation: Once completed, Paddle provisions the recurring subscription and returns the subscription_id (sub_...) and initial transaction ID.

PayPal Subscription Flow

  1. Plan Linking: Passes the resolved paypalPlanId (P-...) to the PayPal SDK buttons with vault=true&intent=subscription.
  2. Subscription Activation: PayPal returns the Subscription ID (I-...), which is verified via GET /v1/billing/subscriptions/{id} and recorded as active.

6. Database Schema Reference

order_details Table (Subscription Line Items)

product_variants Table (Configuration)


7. Webhook Lifecycle Management

Subscription lifecycle events are delivered via webhooks to synchronize cancellations and renewals:

Stripe (POST /webhooks/stripe)

  • customer.subscription.created: Initial subscription activation.
  • customer.subscription.updated: Subscription plan updates or status changes.
  • customer.subscription.deleted: Direct dashboard cancellation — sets active_subscription = 0, subscription_status = 'cancelled'.
  • invoice.payment_succeeded: Recurring renewal payment confirmed — logs new renewal transaction in order_payments.
  • invoice.payment_failed: Payment failure / dunning.

Paddle (POST /webhooks/paddle)

  • subscription.created: Initial activation.
  • subscription.updated: Plan / pricing modifications.
  • subscription.canceled: Direct dashboard cancellation — sets active_subscription = 0, subscription_status = 'cancelled'.
  • subscription.payment_failed: Dunning notification.

PayPal (POST /webhooks/paypal)

  • BILLING.SUBSCRIPTION.ACTIVATED: Subscription confirmed active on Order record.
  • BILLING.SUBSCRIPTION.CANCELLED: Customer or admin cancelled subscription inside PayPal — sets active_subscription = 0, subscription_status = 'cancelled'.
  • BILLING.SUBSCRIPTION.SUSPENDED: Subscription suspended.
  • BILLING.SUBSCRIPTION.EXPIRED: Fixed-cycle subscription completion.
  • PAYMENT.SALE.COMPLETED: Automatic recurring renewal payment — creates a linked renewal record in order_payments.

8. Customer & Admin UI Integration

1. Customer Account Manager (/account)

  • Controller: app/Livewire/UserDashboard.php
  • View: resources/views/livewire/user-dashboard.blade.php
  • Displays Active Subscription badge with an animated pulse indicator on subscription orders.
  • Inline Cancel Subscription button with native confirmation modal and localized text labels (@label).
  • Prevents non-owners from cancelling subscriptions via backend authorization checks.

2. Order Status Tracker Plugin (Guest & Customer Order Lookup)

  • Plugin Class: app/Plugins/Display/OrderStatusTrackerPlugin.php
  • Displays live subscription status and provides a verified Cancel Subscription action on public tracking pages.

3. Admin Order Details (/admin/ecommerce/orders/{id})

  • Controller: app/Livewire/AdminOrderDetails.php
  • View: resources/views/livewire/admin-order-details.blade.php
  • Displays Active Subscription or Cancelled Sub badges on line items.
  • Staff can click Cancel Sub to immediately revoke the agreement with the payment processor.

4. Subscriptions & Recurring Billing Admin Report (/admin/ecommerce/reports)

  • Controller: app/Livewire/ReportSubscriptions.php
  • View: resources/views/livewire/report-subscriptions.blade.php
  • KPI Metrics: Total Subscriptions, Active Subscriptions, Cancelled Subscriptions, and Active Monthly Recurring Value (MRV).
  • Date & Gateway Filtering: Filter by 30/60/90/120/YTD or Custom Date Ranges, status (All, Active, Cancelled), and provider (All, Stripe, Paddle, PayPal).
  • Past Payments Audit History: Expandable drawer on each subscription row listing all past payment renewal records from order_payments.
  • Direct Actions & Exports: Cancel subscriptions directly from the table and export filtered datasets to CSV or Excel (XLSX).

Subscription Cancellation For Stripe, Paddle and Paypal

The platform supports:
  1. Customer-Initiated Cancellation: Direct, one-click cancellation with confirmation in the Customer Account Portal (/account) and the Order Status Tracker Lookup Plugin.
  2. Admin-Initiated Cancellation: Staff-controlled cancellation buttons in Admin Order Details (/admin/ecommerce/orders/{id}) and the Subscriptions Management Report (/admin/ecommerce/reports).
  3. Provider Direct Dashboard Cancellation Sync: Automated webhook listeners that detect when an administrator or customer cancels a subscription directly inside the Stripe Dashboard, Paddle Dashboard, or PayPal Business Portal.
  4. Dynamic Multi-Gateway Resolution: A unified SubscriptionService that determines the correct provider dynamically and executes the appropriate API call without hardcoding.
  5. Subscription Tracking & Reporting: Real-time line item status tracking via active_subscription in order_details and a dedicated Admin Subscriptions Report with complete past payment audit trails.

Architecture & Cancellation Mechanisms by Gateway


A. Stripe Billing

1. In-App API Cancellation

  • Location: app/Services/Payments/Processors/StripeProcessor.php
  • Method: cancelSubscription(string $subscriptionId): bool
  • Mechanism: Calls the Stripe PHP SDK:
  • Behavior: Immediately terminates the recurring subscription agreement in Stripe.

2. Direct Stripe Dashboard Cancellation (Webhooks)

  • Location: app/Http/Controllers/StripeWebhookController.php
  • Endpoint: POST /webhooks/stripe
  • Events Listened To:
    • customer.subscription.deleted: Triggered immediately when an admin cancels a subscription in the Stripe Dashboard.
    • customer.subscription.updated: Triggered when subscription status changes to canceled or paused.
  • Database Action:
    • Matches order_details.subscription_plan_id or order_payments.authorization_code.
    • Sets active_subscription = 0 and subscription_status = 'cancelled'.

B. Paddle Billing

1. In-App API Cancellation

  • Location: app/Services/Payments/Processors/PaddleProcessor.php
  • Method: cancelSubscription(string $subscriptionId, string $effectiveFrom = 'immediately'): bool
  • Mechanism: Issues an authenticated HTTP POST request to Paddle API:
  • Behavior: Immediately cancels the recurring subscription in Paddle.

2. Direct Paddle Dashboard Cancellation (Webhooks)

  • Location: app/Http/Controllers/PaddleWebhookController.php
  • Endpoint: POST /webhooks/paddle
  • Events Listened To:
    • subscription.canceled: Triggered when an admin cancels the subscription in the Paddle Dashboard.
    • subscription.updated: Checks for data.status === 'canceled' or 'past_due'.
  • Database Action:
    • Matches order_details.subscription_plan_id or order_payments.authorization_code.
    • Sets active_subscription = 0 and subscription_status = 'cancelled'.

C. PayPal Subscriptions

1. In-App API Cancellation

  • Location: app/Services/Payments/Processors/PayPalProcessor.php
  • Method: cancelSubscription(string $subscriptionId, string $reason = 'Cancelled by customer', ?bool $forceSandbox = null): bool
  • Mechanism: Issues an authenticated HTTP POST request using OAuth2 Bearer token:
  • Behavior: PayPal returns HTTP 204 No Content, and the billing agreement is cancelled immediately.

2. Direct PayPal Dashboard Cancellation (Webhooks)

  • Location: app/Http/Controllers/PayPalWebhookController.php
  • Endpoint: POST /webhooks/paypal
  • Events Listened To:
    • BILLING.SUBSCRIPTION.CANCELLED: Triggered when an admin or customer cancels the subscription inside their PayPal Business / Personal account.
    • BILLING.SUBSCRIPTION.SUSPENDED: Triggered when suspended by admin or PayPal risk.
    • BILLING.SUBSCRIPTION.EXPIRED: Triggered when a fixed-cycle subscription ends.
  • Database Action:
    • Matches order_details.subscription_plan_id or order_payments.authorization_code.
    • Sets active_subscription = 0 and subscription_status = 'cancelled'.

3. Dynamic Gateway Resolver (SubscriptionService)

  • File: app/Services/Payments/SubscriptionService.php
  • Primary Method: cancelSubscription(OrderDetail $orderDetail, string $reason = '...'): bool
  • Resolution Strategy:
    1. Checks $orderDetail->subscription_provider ('stripe', 'paddle', 'paypal').
    2. Fallback heuristic on agreement ID prefixes:
      • sub_ or seti_ -> Stripe
      • I- or P- -> PayPal
      • sub_01 or pri_ -> Paddle
    3. Fallback to order_payments table inspecting payment_method string and transaction IDs.
    4. Fallback to product_variants configuration fields.
    5. Resolves processor instance dynamically from PaymentProcessorManager and executes remote cancellation.
    6. Updates order_details.active_subscription = 0 and order_details.subscription_status = 'cancelled'.

Code & File Inventory

Subscription Expiration & Access Revocation

In Site Store Pro, recurring subscriptions can grant access to digital downloads and gated CMS content pages. When a recurring subscription is cancelled or lapses, the platform immediately revokes customer access to prevent unauthorized downloads or page viewing.

Automatic Expiration Workflow

When a subscription is cancelled—whether initiated by the customer, staff, or incoming payment processor webhook—the system executes the following revocations:

A. Digital Product Download Expiration (order_details.download_expiration)

  • The download_expiration column on the subscription’s order_details record is set to the previous day (now()->subDay()->endOfDay()).
  • Setting the timestamp to the previous day ensures that any customer attempts to download the file are immediately rejected, even across varying server/client timezone offsets.
  • When an expired customer attempts to download via /download/{orderDetail}/{token}, ProductDownloadController validates the timestamp and returns a 403 status.

B. Gated Content Access Token Expiration (content_access_tokens.expires_at)

  • If the subscription includes a gated content page access token (in content_access_tokens), its expires_at timestamp is also updated to the previous day (now()->subDay()->endOfDay()).
  • When an expired user attempts to access the gated content link (/content-access/{token}), ContentAccessController detects the expired token and displays the branded 403 error page.

Cancellation Trigger Points

Access revocation is automatically synchronized across all cancellation pathways:
  1. Customer Account Portal ([app/Livewire/UserDashboard.php]): Customer clicks Cancel Subscription in their order history.
  2. Admin Order Details ([app/Livewire/AdminOrderDetails.php]): Admin clicks Cancel Sub on an active subscription line item.
  3. Admin Subscriptions Report ([app/Livewire/ReportSubscriptions.php]): Admin cancels recurring billing directly from the subscriptions ledger.
  4. Gateway Lifecycle Webhooks:
    • Stripe ([app/Http/Controllers/StripeWebhookController.php]): customer.subscription.deleted, incomplete_expired, unpaid.
    • Paddle ([app/Http/Controllers/PaddleWebhookController.php]): subscription.canceled, past_due, paused.
    • PayPal ([app/Http/Controllers/PayPalWebhookController.php]): BILLING.SUBSCRIPTION.CANCELLED, BILLING.SUBSCRIPTION.EXPIRED, BILLING.SUBSCRIPTION.SUSPENDED.

Branded Error Page & Expiration Handling

When a customer visits an expired link, instead of encountering a fatal server exception or generic error, Site Store Pro serves a responsive, branded template:
  • Template: [resources/views/errors/403.blade.php] & [resources/views/errors/410.blade.php]
  • Status Code: 403 Forbidden / 410 Gone
  • User-Facing Message: “This content access link has expired.”
  • Action Buttons: Provides immediate navigation back to the storefront (Return to Store) and the customer account portal (My Account).

Admin Management & Manual Overrides

Store administrators can manually extend, reinstate, or remove expirations directly from the order details interface (/admin/ecommerce/orders/{id}):

A. Dual Expirations on Order Line Items

When a purchased item grants both a downloadable file and gated CMS content page access (or only one of the two), the line item row displays independent controls for each:
  • File Download (download_expiration):
    • Displays file download expiry status badge (Active, Expired, or Lifetime).
    • Click Edit File Expiry to adjust the download access cutoff date.
  • Gated Page Access (content_access_tokens.expires_at):
    • Displays gated page access expiry status badge (Active, Expired, or Lifetime).
    • Click Edit Page Expiry to adjust the gated URL redemption cutoff date.
Both modals support custom date/time selection alongside 1-click presets:
  • +30 Days
  • +90 Days
  • +1 Year
  • Expire (Yesterday) — immediately revokes access.
  • No Expiry — grants permanent lifetime access.

B. Content Access Tokens Management Card

Below the order items table, an administrative card lists all gated content tokens issued for the order:
  • Line Item / Product: Associated store product.
  • Access Link & Destination: Gated token URL (with copy/test link) and final destination CMS URL.
  • First Accessed: Timestamp of initial redemption.
  • Expires: Expiration status badge (Active, Expired, or No Expiration).
  • Actions:
    • Edit Expiry: Opens the token expiration modal to adjust or remove expiry.
    • Regenerate: Generates a new secure UUID token and issues a fresh 90-day expiry window.

Technical Reference & Database Schema

Database Columns