Subscription internals

Updated

Subscriptions (class-subscriptions.php) registers its hooks only when WooCommerce Subscriptions is active:

HookMethod
woocommerce_subscription_status_updated (10, 3)status_updated()
wcs_view_subscription_actions (10, 2)customer_actions()
template_redirectupdate_payment_method_redirect()
woocommerce_subscriptions_can_item_be_switched (10, 3)no_switching()
wpinterface_paddle_woo_link_subscription (10, 2)run_link_check()

Linking

Paddle creates its subscription when the first transaction is paid. Subscriptions::link( $order, $profile_id, $next ) writes _wpinterface_paddle_subscription_id, _wpinterface_paddle_api and _wpinterface_paddle_sandbox on every Paddle subscription of the order (parent and resubscribe), adds a note, and stores _wpinterface_paddle_next_billed. Lookups go the other way with Orders::subscription_by_profile().

When the parent order is paid, WooCommerce Subscriptions activates the subscription; status_updated() sees pending → active and sets the next payment to Paddle’s date. set_next_payment() only applies times more than 60 seconds ahead and only to active or on-hold subscriptions.

Status mapping

Paddle → WooCommerce (applied with $from_paddle set, so they aren’t sent back):

PaddleWooCommerce
First payment confirmedParent order paid; Subscriptions activates; next payment = Paddle’s date
Renewal paidRenewal order (wcs_create_renewal_order()), payment_complete(); next payment = Paddle’s next billing time
Renewal failed, past dueactive → on-hold (mark_failing())
Pausedactive → on-hold, _wpinterface_paddle_paused = 1; a pause only scheduled sets the flag and adds a note
Active or trialing againon-hold → active; flag cleared; next payment moved only if Paddle’s date is earlier
Cancellation scheduledactive or on-hold → pending-cancel, end date = Paddle’s date
Cancelled nowcancelled

WooCommerce → Paddle (only for linked subscriptions and not while $from_paddle):

WooCommerce changeSent to Paddle
→ pending-cancelCancel at the end of the period
→ cancelled (not from pending-cancel)Classic: users_cancel. Billing: immediately if cancel_timing is immediately, else at the next billing period
active → on-holdPause at the end of the period, flag set
on-hold or pending-cancel → activeResume (Billing: POST …/resume when paused, otherwise PATCH { scheduled_change: null }; Classic pause=false), flag cleared

A failure is logged and noted on the subscription (“make the same change in your Paddle dashboard”); the WooCommerce change is not undone. cancel_from_paddle() does nothing while the transient wpinterface_paddle_woo_cancelling_{id} exists.

Renewal orders

Each renewal Paddle collects becomes a renewal order with the Paddle transaction or order ID as its transaction ID and the Paddle meta (API, sandbox, transaction ID, total, tax, earnings, and for Billing the line items). Renewal orders can be refunded like parent orders. If Paddle charged in a different currency than the subscription, a note is added.

Paddle attaches subscription_id to the transaction a few seconds after the payment. When the payment is confirmed earlier (status poll) and no subscription webhook links it, Subscriptions::schedule_link_check( $order_id, 1 ) schedules the Action Scheduler action wpinterface_paddle_woo_link_subscription in 60 × attempt seconds. run_link_check() calls Billing_Driver::link_subscription_later() and reschedules until attempt 8.

Restrictions

  • One subscription and one subscription product per order: checkout_subscription() throws otherwise, and the checkout can’t start.
  • No switching: no_switching() returns false for Paddle subscriptions.
  • Classic: subscriptions only in USD, EUR and GBP (the gateway is hidden otherwise).

Updating the payment method

customer_actions() adds “Update payment method” to linked, active or on-hold Paddle subscriptions unless disable_customer_portal is yes, and removes change_payment_method. The link carries a nonce; update_payment_method_redirect() verifies it and that the logged-in user owns the subscription, then redirects to driver->update_payment_method_url(). Billing reads management_urls.update_payment_method from GET /subscriptions/{id}; Classic uses the stored update URL.

Was this page helpful?