Subscriptions (class-subscriptions.php) registers its hooks only when WooCommerce Subscriptions is active:
| Hook | Method |
|---|---|
woocommerce_subscription_status_updated (10, 3) | status_updated() |
wcs_view_subscription_actions (10, 2) | customer_actions() |
template_redirect | update_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):
| Paddle | WooCommerce |
|---|---|
| First payment confirmed | Parent order paid; Subscriptions activates; next payment = Paddle’s date |
| Renewal paid | Renewal order (wcs_create_renewal_order()), payment_complete(); next payment = Paddle’s next billing time |
| Renewal failed, past due | active → on-hold (mark_failing()) |
| Paused | active → on-hold, _wpinterface_paddle_paused = 1; a pause only scheduled sets the flag and adds a note |
| Active or trialing again | on-hold → active; flag cleared; next payment moved only if Paddle’s date is earlier |
| Cancellation scheduled | active or on-hold → pending-cancel, end date = Paddle’s date |
| Cancelled now | cancelled |
WooCommerce → Paddle (only for linked subscriptions and not while $from_paddle):
| WooCommerce change | Sent to Paddle |
|---|---|
| → pending-cancel | Cancel 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-hold | Pause at the end of the period, flag set |
| on-hold or pending-cancel → active | Resume (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.
Delayed link check
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.