Subscription Purchase - Asynchronous Handling
Overview
When a subscription payment is completed, the system asynchronously initializes several key attributes:
- Copies owed (number of issues the subscriber is entitled to)
- Deferred amount (revenue to be recognized over the subscription term)
- Issue shipping basis (how issues are determined for shipping)
- Shipping information (address and soundex for duplicate detection)
These are handled asynchronously outside of the standard subscription flow to avoid delaying checkout and therefore improving the customer experience.
Initialization Flow
1. Payment Completion Hook
The process begins when a payment is completed for a new subscription:
// Hook into payment completion
add_action('woocommerce_subscription_payment_complete', 'subscription_payment_complete', 30, 1);
2. Asynchronous Action Scheduling
Our subscription controller schedules an asynchronous action to handle the initialization:
public function subscription_payment_complete($subscription) {
// Only process original orders, not renewals
if ($subscription->get_payment_count() > 1 || false === $subscription->get_parent()) {
return;
}
// Schedule async action with 20 second delay
as_schedule_single_action(
time() + 20,
'jc_new_subscription_payment_complete_async',
['subscription_id' => $subscription->get_id()],
'jc_subscriptions'
);
}
3. Hook Registration
The async action handler for the subscription initialization is registered on the scheduled action (which will fire at least 20 seconds after subscription purchase):
// Register the async action handler
$this->loader->add_action(
'jc_new_subscription_payment_complete_async',
$subscription_controller,
'new_subscription_payment_complete',
10,
1
);
The new_subscription_payment_complete
function handles the initialization.
public function new_subscription_payment_complete( $subscription_id ) {
$tsj_subscription = tsj_subscriptions_get_subscription( $subscription_id );
$wc_subscription = wcs_get_subscription( $subscription_id );
/**
* Check if subscription is really new -- we've had some renewals come through as new (caching?)
*/
if ( $tsj_subscription->get_start_issue_number() != 0 ) {
wc_get_logger()->error(
'Subscription already initialised - not a new subscription #' . $subscription_id . ' start issue number ' . $tsj_subscription->get_start_issue_number(), ['source' => 'Journal Subscriptions']
);
return;
}
...
4. Copies Owed Calculation
The number of copies owed is calculated:
// Increment copies owed by issues per term
$number_issues = $tsj_subscription->get_issues_per_term();
if ( $tsj_subscription->get_includes_current_issue() ) {
$number_issues++;
}
$tsj_subscription->set_copies_owed( apply_filters( 'tsj_subscription_copies_owed', $number_issues, $tsj_subscription ) );
5. Deferred Amount Assignment
The deferred amount is set:
$parent = $wc_subscription->get_parent();
$deferred_amount = $parent->get_total();
$tsj_subscription->set_deferred_amount( $deferred_amount );
Additional tasks such as setting the shipping soundex & address keys, premium status and sku are performed.
6. Post-Initialization Tasks
After saving the subscription, additional async actions are scheduled. Expiry group & entitlement handling are hooked on this further async action:
// Save the subscription
$tsj_subscription->save();
...
// Schedule post-initialization tasks
as_enqueue_async_action(
'journal_subscription_created',
[
'tsj_subscription' => $tsj_subscription->get_id(),
'notes' => $notes
],
'jc_subscriptions'
);