Developer documentation · alpha

OSubscribe v1.0.0-alpha

A complete recurring-revenue engine for WooCommerce: subscriptions of every cadence, smart dunning that recovers failed charges, save flows that reduce cancels, fixed-installment payment plans, a customer self-service portal, and daily-rollup MRR / churn analytics.

WordPress plugin WP 6.4 · PHP 8.1+ · WC 8.0+ GPL-2.0+ v1.0.0-alpha · feature-complete
01 · Overview

What OSubscribe does

Adds recurring-revenue infrastructure to WooCommerce: subscriptions, billing, dunning, retention flows, payment plans, customer portal, and analytics. Premium plugin, GPL-licensed code, sold once with lifetime updates.

🔁
Recurring billing
Day, week, month, year cadences with anchor-day control
Trial periods
Configurable trials with automatic conversion to paid
💳
Stripe + PayPal
Full webhook receivers; offline gateway for B2B
🔂
Smart dunning
Filterable retry schedule, card-update emails, recovery hooks
🛟
Save flows
Pause, discount, downgrade offers when customers hit cancel
📅
Payment plans
Sell over fixed installments with access revocation on default
👤
Customer portal
Pause, resume, swap card, view invoices, download history
🪄
Magic-link entry
Email a one-tap portal link; no password reset friction
📈
MRR / churn analytics
Daily rollup table with new / expansion / churned MRR
🔒
Idempotent charges
Every charge has a unique idempotency key
📜
Audit log
Every state change recorded per entity with actor
🛠️
WP-CLI
Ops commands for charges, dunning, metrics rebuild
02 · Installation

Getting installed

1
Install and activate WooCommerce 8.0+ if it isn’t already running.
2
Upload the osubscribe folder to /wp-content/plugins/.
3
Activate through Plugins → Installed Plugins.
4
On activation OSubscribe creates 13 custom database tables, declares HPOS compatibility, registers the cron tick (osub_cron_tick), and sets sensible defaults in osub_settings.
5
Open OSubscribe → Settings to configure currency, gateway credentials, dunning schedule, and save flow defaults.

Requirements

WordPress
6.4+
PHP
8.1+
WooCommerce
8.0+
Tested up to 9.5
MySQL
5.7+
MariaDB 10.3+
03 · Quick Start

From zero to first renewal

1
In OSubscribe → Settings → Gateways, paste your Stripe publishable + secret keys. Webhook endpoint is /?osub_webhook=stripe.
2
Mark a WooCommerce product as a subscription product (cadence + price). On checkout, OSubscribe creates a row in osub_subscriptions and schedules the next charge in osub_charges.
3
Drop the [osubscribe_account] shortcode on a page to give customers their portal. Wire the page ID into osub_settings.customer_portal_page_id.
4
The cron tick runs every five minutes and processes all charges where next_charge_at is due. Or trigger manually with wp osub tick.
OSubscribe declares HPOS compatibility on before_woocommerce_init; it works with both legacy wp_posts orders and the new wc_orders table.
04 · File Structure

Plugin architecture

osubscribe/ ├── osubscribe.php Plugin bootstrap, HPOS declaration ├── includes/ │ ├── class-osub-bootstrap.php Wires every subsystem on plugins_loaded │ ├── class-osub-db.php Schema (13 tables) + dbDelta migrations │ ├── class-osub-activator.php Activation: tables + cron + defaults │ ├── class-osub-deactivator.php Cron cleanup on deactivate │ ├── class-osub-settings.php osub_settings option (defaults + merge) │ ├── class-osub-capabilities.php Custom cap: osub_manage │ ├── class-osub-cron.php Cron tick handler (5-minute schedule) │ ├── class-osub-logger.php Audit + debug log │ ├── class-osub-subscription.php Subscription entity │ ├── class-osub-subscription-repo.php CRUD + lifecycle transitions │ ├── class-osub-charge.php Charge entity (idempotent) │ ├── class-osub-charge-repo.php Charge CRUD + scheduling │ ├── admin/ Themed admin (Dashboard/Subs/Renewals/Dunning/Save/Plans/Reports/...) │ ├── analytics/ Daily metrics rollup (osub_metrics_daily) │ ├── billing/ Charge engine: gateway dispatch, idempotency, retry │ ├── cli/ WP-CLI commands │ ├── dunning/ Failed-charge recovery engine │ ├── events/ Action emitter (osub/ namespace) │ ├── gateways/ Stripe + PayPal + offline adapters │ ├── integrations/ WooCommerce + OMailer + OLoyalty hooks │ ├── migration/ Importer for legacy WC Subscriptions │ ├── notifications/ Customer + admin email hooks │ ├── payment-plans/ Fixed-installment plan engine │ ├── products/ Subscription product type registration │ ├── rest/ /wp-json/osub/v1/ endpoints │ ├── retention/ Cancel-deflection orchestration │ ├── save-flows/ Save flow definitions + runtime │ └── self-service/ [osubscribe_account] portal + WC My Account hooks ├── lib/orravo-core/ Shared mini-core for all Orravo plugins ├── templates/ Frontend portal + email templates ├── languages/ .pot file for translation └── assets/css + js Themed admin styles + portal JS
05 · Database Tables

13 database tables

All prefixed with {prefix}osub_. Created on activation via OSub_DB::activate(). Indexes are tuned for the four hot queries: charges due in next N hours, subs in dunning, subs for a given user, and charges for a given subscription.

TablePurpose
osub_subscriptionsCore subscription record: user, status, cadence, anchor, next_charge_at, gateway, payment_method_id, metadata
osub_subscription_itemsLine items per subscription: product_id, variation_id, quantity, unit_price
osub_chargesEvery charge attempt: scheduled_at, attempted_at, status, gateway_charge_id, idempotency_key (UNIQUE), failure_code, next_retry_at
osub_dunning_runsOpen dunning runs per failed charge: attempts, outcome (recovering, recovered, exhausted)
osub_schedule_changesAudit row for every schedule change: type (pause, resume, swap, cancel), payload, actor
osub_save_flowsSave flow definitions: name, config JSON, status (draft, active, archived)
osub_save_flow_runsOne row per offer presented: subscription, flow, reason, offer payload, outcome (accepted, declined, abandoned)
osub_payment_plansFixed-installment plan definitions: name, schedule JSON, total amount, access policy
osub_metrics_dailyDaily MRR rollup keyed by date: MRR, new MRR, expansion, contraction, churned, recovery counts
osub_payment_tokensTokenized payment methods per user: gateway, token, last4, brand, expiry, is_default
osub_magic_linksOne-tap portal entry tokens: SHA-style token, purpose, payload, expires_at, consumed_at
osub_audit_logEvery state change per entity: entity_type, entity_id, event, actor, payload

A 13th table (osub_subscription_items) sits in the same migration to keep multi-line subscriptions cleanly normalized.

06 · Admin Interface

Admin interface

A custom top-level menu (osubscribe) with submenus for every operational view. Themed to match OMailer (Inter Tight + JetBrains Mono + Instrument Serif, dark by default with light toggle, orange accent).

SubmenuPage slugPurpose
DashboardosubscribeMRR, active subs, dunning queue depth, recent activity
Subscriptionsosub-subscriptionsSearchable subscription list with status filters and bulk actions
Renewalsosub-renewalsUpcoming charges grouped by date
Dunningosub-dunningOpen dunning runs, attempt history, manual retry
Save Flowsosub-save-flowsSave flow CRUD + presentation analytics
Payment Plansosub-payment-plansFixed-installment plan CRUD + outstanding A/R
Reportsosub-reportsMRR, churn, recovery, payment plan revenue
Migrationosub-migrationImporter for legacy WooCommerce Subscriptions
Integrationsosub-integrationsWired hooks into OMailer, OLoyalty, OIntel
Settingsosub-settingsAll plugin configuration (single osub_settings option)
07 · Settings Reference

Settings reference

All settings stored in wp_options under key osub_settings. Defaults set on first activation; merge-safe on upgrade.

Currency & portal

KeyTypeDescription
currencystring3-letter ISO. Defaults to woocommerce_currency.
customer_portal_page_idintWP page ID hosting [osubscribe_account] shortcode.

Dunning

KeyDefaultDescription
dunning_max_retries4Max retry attempts before marking exhausted.
dunning_retry_schedule[3, 5, 7, 14]Days between attempts. Filterable per subscription via osub/dunning/retry_schedule.

Retention & analytics

KeyDefaultDescription
enable_save_flowstrueShow save flow offers on cancel.
enable_payment_planstrueAllow products to be sold as payment plans.
mrr_compute_dailytrueRun the daily metrics rollup on cron.
08 · Subscription lifecycle

Subscription lifecycle

Subscriptions move through a small, well-defined state machine. Every transition fires osub/subscription/transitioned with the from + to state, plus a state-specific action.

StatusMeaningTransitions to
pendingCreated but not yet billedactive, cancelled
trialingInside the trial windowactive, cancelled
activeBilling on schedulepast_due, paused, cancelled
past_dueLast charge failed; in dunningactive, cancelled
pausedCustomer-requested pause; no billingactive, cancelled
cancelledFinal state; no further billing(terminal)
PHP// Lifecycle hooks fire alongside the generic transitioned event.
do_action( 'osub/subscription/created',   $sub_id );
do_action( 'osub/subscription/renewed',   $sub_id, $charge_id );
do_action( 'osub/subscription/paused',    $sub_id );
do_action( 'osub/subscription/resumed',   $sub_id );
do_action( 'osub/subscription/cancelled', $sub_id, $reason );
do_action( 'osub/subscription/transitioned', $sub_id, $from, $to );
09 · Billing engine

Billing engine

The cron tick runs every five minutes. It selects subscriptions where next_charge_at ≤ NOW() in batches of 50 (filterable via osub/scheduler/batch_size) and dispatches each to its gateway with a unique idempotency key.

1
Cron tick fires (osub_cron_tick) on a 5-minute schedule.
2
Repository query: subscriptions with status = active and next_charge_at in the past, ordered by oldest due first.
3
For each subscription, a row is inserted into osub_charges with a generated idempotency_key (UNIQUE constraint).
4
Gateway adapter is dispatched with the key. Stripe and PayPal both honor idempotency natively, so a second tick with the same key is a no-op.
5
On success: osub/charge/succeeded fires, the subscription’s next_charge_at is advanced, billings_completed is incremented.
6
On failure: osub/charge/failed fires, dunning enrollment kicks in.
ℹ️Use osub/charge/should_attempt to short-circuit a charge attempt (e.g. skip during a billing freeze window). Returning false postpones the charge by 24 hours.
10 · Dunning

Dunning engine

When a charge fails, OSubscribe enters the subscription into a dunning run (row in osub_dunning_runs) and schedules retries on a configurable schedule. Default is 4 attempts at 3, 5, 7, and 14 days.

Run outcomes

OutcomeMeaning
recoveringOpen run; attempts are still scheduled
recoveredA retry succeeded; subscription returns to active
exhaustedAll attempts failed; subscription transitions to cancelled

Customer-facing email cadence

  • On first failure: card-update email with magic-link to portal
  • On attempt 3: reminder email with explicit copy about coming cancellation
  • On exhaustion: cancellation confirmation with reactivation link
PHP// Per-subscription retry schedule override
add_filter( 'osub/dunning/retry_schedule', function( $schedule, $subscription ) {
    if ( $subscription->amount_recurring() > 100 ) {
        return [ 2, 5, 9, 14, 21 ];
    }
    return $schedule;
}, 10, 2 );

// Outcome listeners
add_action( 'osub/dunning/recovered', function( $sub_id, $attempts ) { /* notify */ }, 10, 2 );
add_action( 'osub/dunning/exhausted', function( $sub_id, $last_failure ) { /* notify */ }, 10, 2 );
11 · Save flows

Save flows

When a customer hits cancel in the portal, OSubscribe presents the first eligible save flow offer. Each presentation is logged to osub_save_flow_runs with reason, offer payload, and outcome.

OutcomeMeaning
acceptedCustomer accepted the offer; subscription stays
declinedCustomer rejected and proceeded to cancel
abandonedCustomer closed the dialog without choosing
PHPuse Orravo\OSub\SaveFlows\Engine;

Engine::register([
    'name'     => 'Pause instead of cancel',
    'eligible' => fn( $subscription ) =>
        $subscription->interval_unit() === 'month'
        && $subscription->billings_completed() >= 3,
    'offer'    => [
        'type'          => 'pause',
        'duration_days' => 60,
        'headline'      => 'Take a 60-day break instead?',
    ],
]);
12 · Payment plans

Payment plans

Sell a fixed-price item over a finite number of installments. Unlike a subscription, a plan has a known total and a clear end date. Access policy can revoke content if the customer defaults.

PHPuse Orravo\OSub\PaymentPlans\Plan;

Plan::save([
    'name'         => 'Annual Course, 4 payments',
    'total_amount' => 480.00,
    'currency'     => 'USD',
    'schedule'     => [
        [ 'amount' => 120, 'days_after_signup' => 0 ],
        [ 'amount' => 120, 'days_after_signup' => 30 ],
        [ 'amount' => 120, 'days_after_signup' => 60 ],
        [ 'amount' => 120, 'days_after_signup' => 90 ],
    ],
    'access_policy' => [ 'revoke_on_default' => true ],
]);
13 · MRR analytics

MRR analytics

A daily cron job rolls every active subscription and charge into osub_metrics_daily (one row per date). The Reports admin page reads only this table, so the dashboards are fast even on stores with many subscribers.

ColumnMeaning
mrrTotal monthly recurring revenue at end of day
new_mrrMRR added by new subscriptions today
expansion_mrrMRR added by upgrades
contraction_mrrMRR removed by downgrades
churned_mrrMRR removed by cancellations
active_subscribersCount of active subs at end of day
dunning_recoveriesNumber of dunning runs closed as recovered
dunning_recoveries_amtDollar amount recovered today

Rebuild the entire history from raw events with wp osub metrics rebuild --from=2026-01-01. Useful after schema migrations or backfills.

14 · Customer portal

Self-service portal

A single shortcode ([osubscribe_account]) renders the customer’s portal: active subscriptions, billing history, payment methods, save flow entry. Requires the user to be logged in.

  • Pause / resume: customer-initiated; logs to osub_schedule_changes
  • Swap payment method: tokenized card update via the gateway’s SetupIntent flow
  • Cancel: triggers any eligible save flow before completing
  • Download invoice: per-charge PDF rendered from a template
🪄Magic-link entry: send ?osub_magic={token} in support emails. The token is consumed on click and the customer lands inside the portal without a password reset round-trip.
15 · WooCommerce integration

WooCommerce integration

  • HPOS-compatible: declares custom_order_tables compatibility on before_woocommerce_init
  • Subscription product type: registered alongside Simple, Variable, Grouped
  • WooCommerce My Account: a Subscriptions tab is added under /my-account/
  • Order linkage: every charge can produce a paid WooCommerce order (parent_order_id on the subscription, order_id on the charge)
  • Coupons: Woo coupons applied at checkout flow through to the recurring price (configurable: first cycle only vs. forever)
  • Importer: OSub_Migration reads existing WooCommerce Subscriptions data and back-fills osub_subscriptions + osub_charges
16 · Gateways

Payment gateways

GatewayAdapterWebhook endpoint
StripeOSub\Gateways\Stripe/?osub_webhook=stripe
PayPalOSub\Gateways\PayPal/?osub_webhook=paypal
OfflineOSub\Gateways\Offline(none; manual reconciliation)

Register additional gateways through osub/gateways/register. The contract is small: implement charge( $subscription, $idempotency_key ) and verify_webhook( $request ).

17 · Shortcodes

Shortcodes

[osubscribe_account]
Renders the full customer portal: active subscriptions, billing history, payment methods, cancel + pause + swap controls. Requires login.
[osubscribe_account]
18 · REST API

REST API endpoints

Base namespace: /wp-json/osub/v1/. Admin endpoints require osub_manage capability; /me/ endpoints require login.

MethodEndpointPurpose
GET/subscriptionsList subscriptions with status, gateway, search filters
GET/subscriptions/{id}Single subscription with items, charges, schedule changes
POST/subscriptions/{id}/pausePause a subscription
POST/subscriptions/{id}/resumeResume a paused subscription
POST/subscriptions/{id}/cancelCancel; triggers save flow if eligible
GET/me/subscriptionsThe logged-in customer’s subscriptions
POST/me/subscriptions/{id}/swap_methodCustomer self-service payment method swap
GET/chargesList charges with status filter and date range
GET/reports/summaryMRR, churn, recovery summary card
GET/reports/mrrDaily MRR series for charting
GET/dunning/queueOpen dunning runs sorted by next retry
POST/webhooks/{gateway}Inbound gateway webhook receiver
19 · Developer Hooks

Developer hooks

Action hooks

PHP// Bootstrap
do_action( 'osub/booted' );
do_action( 'osub/event', $event_name, $payload );
do_action( 'osub/event/' . $event_name, $payload );

// Subscription lifecycle
do_action( 'osub/subscription/created',     $sub_id );
do_action( 'osub/subscription/renewed',     $sub_id, $charge_id );
do_action( 'osub/subscription/paused',      $sub_id );
do_action( 'osub/subscription/resumed',     $sub_id );
do_action( 'osub/subscription/cancelled',   $sub_id, $reason );
do_action( 'osub/subscription/transitioned', $sub_id, $from, $to );

// Charges
do_action( 'osub/charge/attempted',  $sub_id, $charge );
do_action( 'osub/charge/succeeded',  $sub_id, $charge );
do_action( 'osub/charge/failed',     $sub_id, $charge );

// Dunning
do_action( 'osub/dunning/entered',   $sub_id, $run_id );
do_action( 'osub/dunning/recovered', $sub_id, $attempts );
do_action( 'osub/dunning/exhausted', $sub_id, $last_failure );

// Save flows
do_action( 'osub/save_flow/offered',  $sub_id, $flow_id, $offer );
do_action( 'osub/save_flow/accepted', $sub_id, $flow_id );
do_action( 'osub/save_flow/declined', $sub_id, $flow_id );

// Payment methods + magic links
do_action( 'osub/payment_method/updated', $user_id, $token_id );
do_action( 'osub/magic_link/consumed',    $user_id, $token, $purpose );

// Webhooks
do_action( 'osub/webhook/stripe', $event );
do_action( 'osub/webhook/paypal', $event );

// Gateway registration extension point
do_action( 'osub/gateways/register', $registry );

Filters

PHP// Skip a charge attempt entirely (postpones by 24 hours)
apply_filters( 'osub/charge/should_attempt', $bool, $subscription );

// Per-subscription retry schedule override
apply_filters( 'osub/dunning/retry_schedule', $schedule, $subscription );

// Cron tick batch size
apply_filters( 'osub/scheduler/batch_size', 50 );
20 · WP-CLI

WP-CLI surface

SHELL# Subscription operations
wp osub subscription show 1842
wp osub subscription pause 1842
wp osub subscription resume 1842

# Charge operations
wp osub charge retry 7321 --force

# Dunning
wp osub dunning queue

# Metrics
wp osub metrics rebuild --from=2026-01-01

# Migration (legacy WC Subscriptions)
wp osub migrate dry-run
wp osub migrate run --batch=200

# Demo / testing
wp osub seed-demo
wp osub tick
21 · Competitor Comparison

Versus the alternatives

FeatureWC SubscriptionsStripe SubsSubscriptionProOSubscribe
Smart dunningbasicbasic
Customer portaladd-onhostedlimited
Save flowsnonoadd-on
Payment plansadd-onnoadd-on
MRR / churn analyticsnoin dashboardadd-on
Magic-link portal entrynonono
Idempotency keys per chargenono
HPOS compatibilitynopartial
Pricing$249/yr$10/mo + 0.5%$199/yr$99 once
22 · Pricing Model

Pricing model

One purchase, three tier sizes, lifetime updates. No subscriber-count tax. No per-charge fees. Buy once, install on the sites in your tier, get every future update free.

// solo
$99 once
Single · 1 production site
  • 1 production WooCommerce store
  • All 13 tables, all 22 features
  • Lifetime updates
  • Email support · 48h
// scale
$599 once
Unlimited · any site count
  • Unlimited production sites
  • Custom gateway integration sprint
  • Webhook + analytics starter pack
  • Priority support · same-day
23 · Changelog

Changelog

v1.0.0-alpha.2
2026-05-07 · Latest
  • Initial public alpha
  • 13 database tables shipped: subscriptions, subscription_items, charges, dunning_runs, schedule_changes, save_flows, save_flow_runs, payment_plans, metrics_daily, payment_tokens, magic_links, audit_log
  • Stripe + PayPal + offline gateway adapters
  • Smart dunning engine with filterable retry schedule
  • Save flow runtime + admin CRUD
  • Payment plan engine with access policy
  • Daily MRR / churn rollup table
  • Customer portal shortcode [osubscribe_account]
  • Magic-link tokens for one-tap portal entry
  • WP-CLI surface (wp osub subscription/charge/dunning/metrics/migrate/seed-demo/tick)
  • HPOS compatibility declared
  • WooCommerce subscription product type
  • REST API under /wp-json/osub/v1/
  • Audit log table for every state change
  • Idempotency keys on every charge
✦ Need help?

Got a question about OSubscribe?

Reach out directly. Kenneth replies within 24 hours.