Skip to content

Commit

Permalink
Prevents querying for stripe billing subscriptions on every page load…
Browse files Browse the repository at this point in the history
… and changes how our Stripe Billing code is loaded. (#10358)
  • Loading branch information
mattallan authored Feb 18, 2025
1 parent fbdc898 commit b1baeb0
Show file tree
Hide file tree
Showing 11 changed files with 123 additions and 42 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: fix

Improve how Stripe Billing integration is loaded to prevent unnecessary queries on every page load.
34 changes: 29 additions & 5 deletions includes/class-wc-payments-webhook-processing-service.php
Original file line number Diff line number Diff line change
Expand Up @@ -191,13 +191,9 @@ public function process( array $event_body ) {
$this->process_webhook_payment_intent_amount_capturable_updated( $event_body );
break;
case 'invoice.upcoming':
WC_Payments_Subscriptions::get_event_handler()->handle_invoice_upcoming( $event_body );
break;
case 'invoice.paid':
WC_Payments_Subscriptions::get_event_handler()->handle_invoice_paid( $event_body );
break;
case 'invoice.payment_failed':
WC_Payments_Subscriptions::get_event_handler()->handle_invoice_payment_failed( $event_body );
$this->process_webhook_stripe_billing_invoice( $event_type, $event_body );
break;
}

Expand Down Expand Up @@ -884,4 +880,32 @@ private function process_webhook_refund_triggered_externally( array $event_body
// Process the refund in the order service.
$this->order_service->add_note_and_metadata_for_refund( $order, $wc_refund, $refund_id, $refund_balance_transaction_id );
}

/**
* Process webhook for Stripe Billing invoice events.
*
* @param string $event_type The type of event that triggered the webhook.
* @param array $event_body The event that triggered the webhook.
*
* @return void
*
* @throws Invalid_Webhook_Data_Exception When the linked subscription is not found.
*/
private function process_webhook_stripe_billing_invoice( $event_type, $event_body ) {
if ( ! class_exists( 'WC_Payments_Subscriptions' ) ) {
return;
}

switch ( $event_type ) {
case 'invoice.upcoming':
WC_Payments_Subscriptions::get_event_handler()->handle_invoice_upcoming( $event_body );
break;
case 'invoice.paid':
WC_Payments_Subscriptions::get_event_handler()->handle_invoice_paid( $event_body );
break;
case 'invoice.payment_failed':
WC_Payments_Subscriptions::get_event_handler()->handle_invoice_payment_failed( $event_body );
break;
}
}
}
2 changes: 1 addition & 1 deletion includes/class-wc-payments.php
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,7 @@ function () {
}

// Load Stripe Billing subscription integration.
if ( self::should_load_stripe_billing_integration() ) {
if ( WC_Payments_Features::is_stripe_billing_eligible() ) {
include_once WCPAY_ABSPATH . '/includes/subscriptions/class-wc-payments-subscriptions.php';
WC_Payments_Subscriptions::init( self::$api_client, self::$customer_service, self::$order_service, self::$account, self::$token_service );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -876,17 +876,12 @@ public function update_subscription_token( $updated, $subscription, $new_token )
* @return bool True if the renewal order is linked to a renewal order. Otherwise false.
*/
private function is_wcpay_subscription_renewal_order( WC_Order $renewal_order ) {
/**
* Check if WC_Payments_Subscription_Service class exists first before fetching the subscription for the renewal order.
*
* This class is only loaded when the store has the Stripe Billing feature turned on or has existing
* WCPay Subscriptions @see WC_Payments::should_load_stripe_billing_integration().
*/
if ( ! class_exists( 'WC_Payments_Subscription_Service' ) ) {
// Renewal orders copy metadata from the parent subscription, so we can first check if it has the `_wcpay_subscription_id` meta.
if ( ! class_exists( 'WC_Payments_Subscription_Service' ) || ! $renewal_order->meta_exists( WC_Payments_Subscription_Service::SUBSCRIPTION_ID_META_KEY ) ) {
return false;
}

// Check if the renewal order is linked to a subscription which is a WCPay Subscription.
// Confirm the renewal order is linked to a subscription which is a WCPay Subscription.
foreach ( wcs_get_subscriptions_for_renewal_order( $renewal_order ) as $subscription ) {
if ( WC_Payments_Subscription_Service::is_wcpay_subscription( $subscription ) ) {
return true;
Expand Down
10 changes: 8 additions & 2 deletions includes/subscriptions/class-wc-payments-invoice-service.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,10 @@ public function __construct(
return;
}

add_action( 'woocommerce_order_payment_status_changed', [ $this, 'maybe_record_invoice_payment' ], 10, 1 );
add_action( 'woocommerce_renewal_order_payment_complete', [ $this, 'maybe_record_invoice_payment' ], 11, 1 );
if ( WC_Payments_Features::should_use_stripe_billing() ) {
add_action( 'woocommerce_order_payment_status_changed', [ $this, 'maybe_record_invoice_payment' ], 10, 1 );
add_action( 'woocommerce_renewal_order_payment_complete', [ $this, 'maybe_record_invoice_payment' ], 11, 1 );
}
}

/**
Expand Down Expand Up @@ -189,6 +191,10 @@ public function mark_pending_invoice_paid_for_subscription( WC_Subscription $sub
* @throws API_Exception If the request to mark the invoice as paid fails.
*/
public function maybe_record_invoice_payment( int $order_id ) {
if ( ! function_exists( 'wcs_get_subscriptions_for_order' ) ) {
return;
}

$order = wc_get_order( $order_id );

if ( ! $order || self::get_order_invoice_id( $order ) ) {
Expand Down
16 changes: 14 additions & 2 deletions includes/subscriptions/class-wc-payments-product-service.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,11 @@ public function __construct( WC_Payments_API_Client $payments_api_client ) {
if ( WC_Payments_Features::should_use_stripe_billing() ) {
add_action( 'shutdown', [ $this, 'create_or_update_products' ] );
add_action( 'untrashed_post', [ $this, 'maybe_unarchive_product' ] );
add_action( 'wp_trash_post', [ $this, 'maybe_archive_product' ] );

$this->add_product_update_listeners();
}

add_action( 'wp_trash_post', [ $this, 'maybe_archive_product' ] );
add_filter( 'woocommerce_duplicate_product_exclude_meta', [ $this, 'exclude_meta_wcpay_product' ] );
}

Expand Down Expand Up @@ -201,6 +201,10 @@ public static function exclude_meta_wcpay_product( $meta_keys ) {
* @param int $product_id The ID of the product to handle.
*/
public function maybe_schedule_product_create_or_update( int $product_id ) {
if ( ! class_exists( 'WC_Subscriptions_Product' ) ) {
return;
}

// Skip products which have already been scheduled or aren't subscriptions.
$product = wc_get_product( $product_id );
if ( ! $product || isset( $this->products_to_update[ $product_id ] ) || ! WC_Subscriptions_Product::is_subscription( $product ) ) {
Expand Down Expand Up @@ -292,7 +296,7 @@ public function create_product_for_item_type( string $type ) {
* @param WC_Product $product The product to update.
*/
public function update_products( WC_Product $product ) {
if ( ! WC_Subscriptions_Product::is_subscription( $product ) ) {
if ( ! class_exists( 'WC_Subscriptions_Product' ) || ! WC_Subscriptions_Product::is_subscription( $product ) ) {
return;
}

Expand Down Expand Up @@ -343,6 +347,10 @@ public function update_products( WC_Product $product ) {
* @param int $post_id The ID of the post to handle. Only subscription product IDs will be archived in WC Pay.
*/
public function maybe_archive_product( int $post_id ) {
if ( ! class_exists( 'WC_Subscriptions_Product' ) ) {
return;
}

$product = wc_get_product( $post_id );

if ( $product && WC_Subscriptions_Product::is_subscription( $product ) ) {
Expand All @@ -360,6 +368,10 @@ public function maybe_archive_product( int $post_id ) {
* @param int $post_id The ID of the post to handle. Only Subscription product post IDs will be unarchived in WC Pay.
*/
public function maybe_unarchive_product( int $post_id ) {
if ( ! class_exists( 'WC_Subscriptions_Product' ) ) {
return;
}

$product = wc_get_product( $post_id );

if ( $product && WC_Subscriptions_Product::is_subscription( $product ) ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ class WC_Payments_Subscription_Change_Payment_Method_Handler {
* Constructor.
*/
public function __construct() {
if ( ! WC_Payments_Features::should_use_stripe_billing() ) {
return;
}

// Add an "Update card" action to all WCPay billing subscriptions with a failed renewal order.
add_filter( 'wcs_view_subscription_actions', [ $this, 'update_subscription_change_payment_button' ], 15, 2 );
add_filter( 'woocommerce_can_subscription_be_updated_to_new-payment-method', [ $this, 'can_update_payment_method' ], 15, 2 );
Expand Down Expand Up @@ -63,7 +67,7 @@ public function update_subscription_change_payment_button( $actions, $subscripti
*/
public function update_order_pay_button( $actions, $order ) {
// If the order isn't payable, there's nothing to update.
if ( ! isset( $actions['pay'] ) ) {
if ( ! isset( $actions['pay'] ) || ! function_exists( 'wcs_get_subscriptions_for_order' ) ) {
return $actions;
}

Expand Down Expand Up @@ -113,6 +117,10 @@ public function redirect_pay_for_order_to_update_payment_method() {
return;
}

if ( ! function_exists( 'wcs_get_subscriptions_for_order' ) ) {
return;
}

$order_id = ( isset( $wp->query_vars['order-pay'] ) ) ? absint( $wp->query_vars['order-pay'] ) : absint( $_GET['order_id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$order_key = wc_clean( wp_unslash( $_GET['key'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$order = wc_get_order( $order_id );
Expand Down Expand Up @@ -178,7 +186,7 @@ public function change_payment_method_page_notice( string $message, WC_Subscript
*/
private function does_subscription_need_payment_updated( $subscription ) {
// We're only interested in WC Pay subscriptions that are on hold due to a failed payment.
if ( ! $subscription->has_status( 'on-hold' ) || ! WC_Payments_Subscription_Service::is_wcpay_subscription( $subscription ) ) {
if ( ! is_a( $subscription, 'WC_Subscription' ) || ! $subscription->has_status( 'on-hold' ) || ! WC_Payments_Subscription_Service::is_wcpay_subscription( $subscription ) ) {
return false;
}

Expand Down Expand Up @@ -211,7 +219,7 @@ private function get_subscription_update_payment_url( $subscription ) {
*/
public function change_payment_method_form_submit_text( $button_text ) {

if ( isset( $_GET['change_payment_method'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
if ( isset( $_GET['change_payment_method'] ) && function_exists( 'wcs_get_subscription' ) ) { // phpcs:ignore WordPress.Security.NonceVerification
$subscription = wcs_get_subscription( wc_clean( wp_unslash( $_GET['change_payment_method'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification

if ( $subscription && $this->does_subscription_need_payment_updated( $subscription ) ) {
Expand Down
59 changes: 41 additions & 18 deletions includes/subscriptions/class-wc-payments-subscription-service.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,22 +144,25 @@ public function __construct(
add_action( 'woocommerce_subscription_payment_method_updated', [ $this, 'maybe_create_subscription_from_update_payment_method' ], 10, 2 );
}

add_action( 'woocommerce_subscription_status_cancelled', [ $this, 'cancel_subscription' ] );
add_action( 'woocommerce_subscription_status_expired', [ $this, 'cancel_subscription' ] );
add_action( 'woocommerce_subscription_status_on-hold', [ $this, 'handle_subscription_status_on_hold' ] );
add_action( 'woocommerce_subscription_status_pending-cancel', [ $this, 'set_pending_cancel_for_subscription' ] );
add_action( 'woocommerce_subscription_status_pending-cancel_to_active', [ $this, 'reactivate_subscription' ] );
add_action( 'woocommerce_subscription_status_on-hold_to_active', [ $this, 'reactivate_subscription' ] );

// Save the new token on the WCPay subscription when it's added to a WC subscription.
add_action( 'woocommerce_payment_token_added_to_order', [ $this, 'update_wcpay_subscription_payment_method' ], 10, 3 );
add_filter( 'woocommerce_subscription_payment_gateway_supports', [ $this, 'prevent_wcpay_subscription_changes' ], 10, 3 );
add_filter( 'woocommerce_order_actions', [ $this, 'prevent_wcpay_manual_renewal' ], 11, 1 );

add_action( 'woocommerce_payments_changed_subscription_payment_method', [ $this, 'maybe_attempt_payment_for_subscription' ], 10, 2 );
add_action( 'woocommerce_admin_order_data_after_billing_address', [ $this, 'show_wcpay_subscription_id' ] );

add_action( 'woocommerce_subscription_payment_method_updated_from_' . WC_Payment_Gateway_WCPay::GATEWAY_ID, [ $this, 'maybe_cancel_subscription' ], 10, 2 );
if ( class_exists( 'WC_Subscription' ) ) {
// Save the new token on the WCPay subscription when it's added to a WC subscription.
add_action( 'woocommerce_payment_token_added_to_order', [ $this, 'update_wcpay_subscription_payment_method' ], 10, 3 );

add_action( 'woocommerce_subscription_status_cancelled', [ $this, 'cancel_subscription' ] );
add_action( 'woocommerce_subscription_status_expired', [ $this, 'cancel_subscription' ] );
add_action( 'woocommerce_subscription_status_on-hold', [ $this, 'handle_subscription_status_on_hold' ] );
add_action( 'woocommerce_subscription_status_pending-cancel', [ $this, 'set_pending_cancel_for_subscription' ] );
add_action( 'woocommerce_subscription_status_pending-cancel_to_active', [ $this, 'reactivate_subscription' ] );
add_action( 'woocommerce_subscription_status_on-hold_to_active', [ $this, 'reactivate_subscription' ] );

add_filter( 'woocommerce_subscription_payment_gateway_supports', [ $this, 'prevent_wcpay_subscription_changes' ], 10, 3 );
add_filter( 'woocommerce_order_actions', [ $this, 'prevent_wcpay_manual_renewal' ], 11, 1 );

add_action( 'woocommerce_payments_changed_subscription_payment_method', [ $this, 'maybe_attempt_payment_for_subscription' ], 10, 2 );
add_action( 'woocommerce_admin_order_data_after_billing_address', [ $this, 'show_wcpay_subscription_id' ] );

add_action( 'woocommerce_subscription_payment_method_updated_from_' . WC_Payment_Gateway_WCPay::GATEWAY_ID, [ $this, 'maybe_cancel_subscription' ], 10, 2 );
}
}

/**
Expand All @@ -173,6 +176,10 @@ public static function has_delayed_payment( WC_Subscription $subscription ) {
$trial_end = $subscription->get_time( 'trial_end' );
$has_sync = false;

if ( ! class_exists( 'WC_Subscriptions_Synchroniser' ) ) {
return $has_sync;
}

if ( WC_Subscriptions_Synchroniser::is_syncing_enabled() && WC_Subscriptions_Synchroniser::subscription_contains_synced_product( $subscription ) ) {
$has_sync = true;

Expand All @@ -198,6 +205,10 @@ public static function has_delayed_payment( WC_Subscription $subscription ) {
* @return WC_Subscription|bool The WC subscription or false if it can't be found.
*/
public static function get_subscription_from_wcpay_subscription_id( string $wcpay_subscription_id ) {
if ( ! function_exists( 'wcs_get_subscriptions' ) ) {
return false;
}

$subscriptions = wcs_get_subscriptions(
[
'subscriptions_per_page' => 1,
Expand Down Expand Up @@ -576,6 +587,10 @@ public function set_pending_cancel_for_subscription( WC_Subscription $subscripti
* @param WC_Payment_Token $token Payment Token object.
*/
public function update_wcpay_subscription_payment_method( int $subscription_id, int $token_id, WC_Payment_Token $token ) {
if ( ! function_exists( 'wcs_get_subscription' ) ) {
return;
}

$subscription = wcs_get_subscription( $subscription_id );

if ( $subscription && self::is_wcpay_subscription( $subscription ) ) {
Expand Down Expand Up @@ -603,7 +618,7 @@ public function update_wcpay_subscription_payment_method( int $subscription_id,
*/
public function maybe_attempt_payment_for_subscription( $subscription, WC_Payment_Token $token ) {

if ( ! wcs_is_subscription( $subscription ) ) {
if ( ! function_exists( 'wcs_is_subscription' ) || ! wcs_is_subscription( $subscription ) ) {
return;
}

Expand Down Expand Up @@ -677,6 +692,10 @@ public function prevent_wcpay_subscription_changes( bool $supported, string $fea
public function prevent_wcpay_manual_renewal( array $actions ) {
global $theorder;

if ( ! function_exists( 'wcs_is_subscription' ) || ! $theorder ) {
return $actions;
}

if ( wcs_is_subscription( $theorder ) && self::is_wcpay_subscription( $theorder ) ) {
unset(
$actions['wcs_create_pending_parent'],
Expand All @@ -693,7 +712,7 @@ public function prevent_wcpay_manual_renewal( array $actions ) {
* @param WC_Order|WC_Subscription $order The order object.
*/
public function show_wcpay_subscription_id( WC_Order $order ) {
if ( ! wcs_is_subscription( $order ) || ! self::is_wcpay_subscription( $order ) ) {
if ( ! function_exists( 'wcs_is_subscription' ) || ! wcs_is_subscription( $order ) || ! self::is_wcpay_subscription( $order ) ) {
return;
}

Expand Down Expand Up @@ -740,6 +759,10 @@ public function update_dates_to_match_wcpay_subscription( array $wcpay_subscript
* @param int $order_id WC Order ID.
*/
public function create_subscription_for_manual_renewal( int $order_id ) {
if ( ! function_exists( 'wcs_get_subscriptions_for_renewal_order' ) ) {
return;
}

$subscriptions = wcs_get_subscriptions_for_renewal_order( $order_id );

foreach ( $subscriptions as $subscription_id => $subscription ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ public function enqueue_scripts_and_styles() {
$screen = get_current_screen();

// Only enqueue the scripts on the admin subscriptions screen.
if ( ! $screen || 'edit-shop_subscription' !== $screen->id || wcs_do_subscriptions_exist() ) {
if ( ! $screen || 'edit-shop_subscription' !== $screen->id ) {
return;
}

if ( ! function_exists( 'wcs_do_subscriptions_exist' ) || wcs_do_subscriptions_exist() ) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,18 @@ class WC_Payments_Subscriptions_Onboarding_Handler {
* @param WC_Payments_Account $account account service instance.
*/
public function __construct( WC_Payments_Account $account ) {
$this->account = $account;

if ( ! WC_Payments_Features::should_use_stripe_billing() ) {
return;
}

// This action is triggered on product save but after other required subscriptions logic is triggered.
add_action( 'woocommerce_admin_process_product_object', [ $this, 'product_save' ] );
add_action( 'woocommerce_payments_account_refreshed', [ $this, 'account_data_refreshed' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_modal_scripts_and_styles' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_toast_script' ] );
add_filter( 'woocommerce_subscriptions_admin_pointer_script_parameters', [ $this, 'filter_admin_pointer_script_parameters' ] );

$this->account = $account;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ function ( $parent_order ) use ( $order ) {
$mock_subscription->payment_method = 'woocommerce_payments';

$mock_subscription->update_meta_data( '_wcpay_subscription_id', 'test_is_wcpay_subscription' );
$order->update_meta_data( '_wcpay_subscription_id', 'test_is_wcpay_subscription' );

WC_Subscriptions::set_wcs_get_subscriptions_for_renewal_order(
function ( $id ) use ( $mock_subscription ) {
Expand Down

0 comments on commit b1baeb0

Please sign in to comment.