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: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
- Use Existing Price IDs
- Auto-Create Stripe Product
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:- Automatic Non-Catalog Subscription (Zero Setup)
- Use Pre-Created Catalog Price IDs
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
- On-Demand Plan Generator (Recommended)
- Use Pre-Created PayPal Plan IDs
Configure the recurring terms in the variant editor and click Generate Sandbox Plan or Generate Live Plan:
The system creates the catalog product and recurring billing plan via the PayPal Subscriptions REST API and populates
paypal_sandbox_plan_id or paypal_live_plan_id automatically.4. Mixed-Cart Policy
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
- Customer Resolution: Looks up
users.stripe_customer_id. If missing, creates a customer in Stripe and saves the ID. - Price Selection: Uses configured
stripe_sandbox_price_id/stripe_live_price_idor creates a Product + Price on-the-fly. - Trial Setup: Applies
trial_period_daysifstripe_trial_enabled = 1. - Client Secret: Returns the
client_secretfrom the subscription’s latest invoice’sPaymentIntentfor Stripe Elements confirmation.
Paddle Subscription Flow
- Price Linking & Dynamic Auto-Creation:
- If a
paddle_price_idis 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.
- If a
- Checkout Overlay: Passes the
transaction_idto Paddle.js to render the checkout. - Subscription Confirmation: Once completed, Paddle provisions the recurring subscription and returns the
subscription_id(sub_...) and initial transaction ID.
PayPal Subscription Flow
- Plan Linking: Passes the resolved
paypalPlanId(P-...) to the PayPal SDK buttons withvault=true&intent=subscription. - Subscription Activation: PayPal returns the Subscription ID (
I-...), which is verified viaGET /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 — setsactive_subscription = 0,subscription_status = 'cancelled'.invoice.payment_succeeded: Recurring renewal payment confirmed — logs new renewal transaction inorder_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 — setsactive_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 — setsactive_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 inorder_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:- Customer-Initiated Cancellation: Direct, one-click cancellation with confirmation in the Customer Account Portal (
/account) and the Order Status Tracker Lookup Plugin. - Admin-Initiated Cancellation: Staff-controlled cancellation buttons in Admin Order Details (
/admin/ecommerce/orders/{id}) and the Subscriptions Management Report (/admin/ecommerce/reports). - 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.
- Dynamic Multi-Gateway Resolution: A unified
SubscriptionServicethat determines the correct provider dynamically and executes the appropriate API call without hardcoding. - Subscription Tracking & Reporting: Real-time line item status tracking via
active_subscriptioninorder_detailsand 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 tocanceledorpaused.
- Database Action:
- Matches
order_details.subscription_plan_idororder_payments.authorization_code. - Sets
active_subscription = 0andsubscription_status = 'cancelled'.
- Matches
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 fordata.status === 'canceled'or'past_due'.
- Database Action:
- Matches
order_details.subscription_plan_idororder_payments.authorization_code. - Sets
active_subscription = 0andsubscription_status = 'cancelled'.
- Matches
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_idororder_payments.authorization_code. - Sets
active_subscription = 0andsubscription_status = 'cancelled'.
- Matches
3. Dynamic Gateway Resolver (SubscriptionService)
- File:
app/Services/Payments/SubscriptionService.php - Primary Method:
cancelSubscription(OrderDetail $orderDetail, string $reason = '...'): bool - Resolution Strategy:
- Checks
$orderDetail->subscription_provider('stripe','paddle','paypal'). - Fallback heuristic on agreement ID prefixes:
sub_orseti_-> StripeI-orP--> PayPalsub_01orpri_-> Paddle
- Fallback to
order_paymentstable inspectingpayment_methodstring and transaction IDs. - Fallback to
product_variantsconfiguration fields. - Resolves processor instance dynamically from
PaymentProcessorManagerand executes remote cancellation. - Updates
order_details.active_subscription = 0andorder_details.subscription_status = 'cancelled'.
- Checks
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_expirationcolumn on the subscription’sorder_detailsrecord 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},ProductDownloadControllervalidates the timestamp and returns a403status.
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), itsexpires_attimestamp is also updated to the previous day (now()->subDay()->endOfDay()). - When an expired user attempts to access the gated content link (
/content-access/{token}),ContentAccessControllerdetects the expired token and displays the branded403error page.
Cancellation Trigger Points
Access revocation is automatically synchronized across all cancellation pathways:- Customer Account Portal ([
app/Livewire/UserDashboard.php]): Customer clicks Cancel Subscription in their order history. - Admin Order Details ([
app/Livewire/AdminOrderDetails.php]): Admin clicks Cancel Sub on an active subscription line item. - Admin Subscriptions Report ([
app/Livewire/ReportSubscriptions.php]): Admin cancels recurring billing directly from the subscriptions ledger. - 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.
- Stripe ([
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.
+30 Days+90 Days+1 YearExpire (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.
