Skip to main content

Subscriptions

Overview​

Subscriptions adds a subscription product type and the machinery that keeps a recurring payment running: a billing period and interval, an optional free trial, an optional signup fee, and a next-payment date the renewal job works from.

A renewal creates a new WooCommerce order and takes payment against the stored gateway token. When that fails, the subscription does not simply stop — a retry schedule makes further attempts, each one recorded with its result and the gateway's response, so a failure has a history rather than a single outcome.

Every status change is written to its own log with the reason and who made it, and status transitions go through a guard rather than being set freely.

Customers manage their own subscriptions from their account: cancel, pause, resume, and update the billing or shipping address on a live subscription.

It is for stores selling anything on a recurring basis.

Availability​

ItemValue
Module keysubscriptions
TierPremium
Entitlement keysubscriptions
Admin tabsubscriptions
Enabled optionaiowc_module_enabled_subscriptions (off until turned on)
REST namespaceaiowc/v1
Product typeSubscription

Enabling the module creates the four tables below and schedules its jobs.

registerHooks() performs no licence check of its own; the module registry applies the entitlement gate centrally. It registers the product type, the frontend, the jobs and the email-automation triggers.

Settings​

This module declares no DEFAULTS constant. Its settings are read and written through GET/PUT /subscriptions/settings.

The subscription​

FieldMeaning
statusWhere it is in its life. pending at creation.
parent_order_id, last_order_idThe order that created it, and the most recent renewal.
start_date, trial_end_date, next_payment_date, end_dateThe schedule.
billing_period, billing_intervalmonth every 1 by default — so "every 3 months" is period plus interval.
recurring_total, recurring_tax, recurring_shippingWhat recurs, held apart from the signup fee.
signup_feeCharged once, at the start.
payment_method, payment_tokenThe gateway and its token — not card details.
billing_address, shipping_addressHeld on the subscription, so a change applies to future renewals without touching past orders.
requires_manual_renewalSet where the gateway cannot charge automatically.
retry_countHow many recovery attempts have been made.

Storing payment_token rather than card data is the right shape: the token is what the gateway issued, and the plugin never holds the instrument itself.

Renewals and retries​

The renewal job creates a renewal order and charges it. On failure the retry table takes over: each attempt records its number, when it was scheduled and attempted, the result and the gateway's response.

requires_manual_renewal is the honest escape hatch — a gateway with no token-based charging cannot be billed automatically, and the subscription says so rather than failing silently every period.

A renewal can also be forced from the admin with POST /subscriptions/{id}/force-renewal, which is what an administrator uses to recover a subscription by hand.

Status handling​

Transitions are guarded by the aiowc_subscription_can_transition filter, so a store can refuse a transition its own rules do not allow. Every change writes a row to the status log with the old and new status, a reason and who made it.

Payment gateways can be filtered per subscription through aiowc_subscription_filter_payment_gateways, and the module also filters woocommerce_available_payment_gateways — so a gateway that cannot support recurring payments is not offered for a subscription purchase.

Reporting​

GET /subscriptions/overview computes monthly recurring revenue and a churn rate from the subscription book — cancellations measured against cancellations plus active subscriptions — alongside counts by status. That is real reporting derived from the data rather than a count of rows.

Admin screen​

The Subscriptions tab lists subscriptions and shows the overview with MRR and churn; cancels, pauses, resumes, reschedules and force-renews one; reads its orders, its status history and its retry history; and edits the settings. A diagnostics route reports the module's own configuration state.

REST API endpoints​

All routes are on aiowc/v1 and answer with the { success, data, message } envelope.

Administrator routes — manage_woocommerce​

MethodPathPurposeRequired args
GET/subscriptionsEvery subscription.–
GET/subscriptions/{id}One subscription.id
POST/subscriptions/{id}/cancelCancel it.id
POST/subscriptions/{id}/pausePause it.id
POST/subscriptions/{id}/resumeResume it.id
POST/subscriptions/{id}/rescheduleMove the next payment.id, next_payment_date
POST/subscriptions/{id}/force-renewalRenew it now.id
GET/subscriptions/{id}/ordersIts orders.id
GET/subscriptions/{id}/status-historyIts status log.id
GET/subscriptions/{id}/retry-historyIts payment attempts.id
GET/subscriptions/overviewMRR, churn and status counts.–
GET/subscriptions/diagnosticsThe module's own health detail.–
GET / PATCH / PUT / POST/subscriptions/settingsRead and write the settings.–

Customer routes — customer permission check​

MethodPathPurpose
GET/subscriptions/meThe caller's subscriptions.
GET/subscriptions/me/{id}One of their subscriptions.
POST/subscriptions/me/{id}/cancelCancel their own.
POST/subscriptions/me/{id}/pausePause their own.
POST/subscriptions/me/{id}/resumeResume their own.
PATCH / PUT / POST/subscriptions/me/{id}/billing-addressUpdate the billing address.
PATCH / PUT / POST/subscriptions/me/{id}/shipping-addressUpdate the shipping address.

The customer routes are a separate /me/ branch behind a customer permission check, rather than the administrator routes with a looser permission. That separation is what prevents one customer reading another's subscription by guessing an id.

WooCommerce integration​

AreaHooks
Product typeproduct_type_selector, woocommerce_product_class, woocommerce_product_data_tabs / _panels, woocommerce_product_options_general_product_data, woocommerce_process_product_meta_subscription
Product pagewoocommerce_subscription_add_to_cart
Cartwoocommerce_add_cart_item_data, _get_cart_item_from_session, _add_to_cart_validation, _get_item_data, _cart_item_price, _cart_item_subtotal, _cart_calculate_fees, _cart_totals_after_order_total
Checkoutwoocommerce_checkout_process, _checkout_create_order_line_item, _checkout_order_created, woocommerce_checkout_registration_required, woocommerce_review_order_before_submit, _review_order_after_order_total
Gatewayswoocommerce_available_payment_gateways, woocommerce_before_pay_action
Paymentwoocommerce_payment_complete, woocommerce_order_status_pending_to_processing
Order statuswoocommerce_order_status_processing, _completed, _cancelled, _failed
Accountwoocommerce_account_menu_items and the module's own account endpoint

woocommerce_checkout_registration_required is notable: a subscription needs an account to manage it later, so the module forces registration for a cart containing one.

The module registers no shortcode.

Database schema​

TableHolds
{prefix}aiowc_subscriptionsThe subscription, as described above.
{prefix}aiowc_subscription_itemsWhat is subscribed to: product, variation, name, quantity and recurring amounts.
{prefix}aiowc_subscription_status_logEvery status change: old and new status, reason, who made it.
{prefix}aiowc_subscription_retry_logEvery recovery attempt: attempt number, when scheduled and attempted, result and gateway response.

Items carry their own name and recurring amounts, so a subscription still describes what was bought at the price agreed, even after the product is renamed or repriced.

Background jobs​

HookWork
aiowc_subscription_renewalCreates and charges renewal orders that are due.
aiowc_subscription_retryRe-attempts a failed renewal on the retry schedule.
aiowc_subscription_renewal_reminderWarns a customer that a renewal is coming.
aiowc_subscription_cleanupPrunes old rows.

Action hooks for integrators​

Lifecycle: aiowc_subscription_created, _activated, _cancelled, _pending_cancel, _paused, _resumed, _reactivated, _on_hold, _expired, _rescheduled, _status_changed.

Payments: aiowc_subscription_payment_failed, _renewal_order_created, _renewal_payment_complete, _payment_method_updated, _manual_renewal_required, and aiowc_process_subscription_payment.

Filters: aiowc_subscription_can_transition, _filter_payment_gateways, _allow_cart_mixing, _price_html, _add_to_cart_text, _single_add_to_cart_text, _add_to_cart_url, _customer_action.

aiowc_subscription_allow_cart_mixing decides whether a subscription and a one-off product may share a cart, which is a store policy rather than a technical constraint — so it is a filter.

Entitlement limits​

subscriptions is an on/off grant with no cap on the number of subscriptions, items or renewals.

Health check​

The module reports a warning when its tables are missing or WooCommerce is inactive, and otherwise reports that it is functioning normally. The diagnostics route gives more detail.

Known gaps​

  • There is no variable subscription product type. A subscription is a single product type; a plan with several billing options is modelled as separate products rather than as variations of one.
  • A customer cannot change their payment method through the module's own routes. They can cancel, pause, resume and update addresses; updating the stored payment token depends on the gateway's own flows, and aiowc_subscription_payment_method_updated exists for something else to fire.
  • Automatic renewal depends on the gateway supporting token-based charging. Where it does not, requires_manual_renewal is set and the customer must pay each renewal by hand — an honest fallback, but one that should be understood before choosing a gateway.
  • The module has no settings constant, so the defaults are not stated in one place in the code.
  • MRR and churn are computed over the whole subscription book rather than for a chosen period, so they answer "where does the book stand" rather than "how did last month compare".