Esempio n. 1
0
/**
 * Stores a PayPal Standard Subscription ID or Billing Agreement ID in the post meta of a given order and the user meta of the order's user.
 *
 * @param int|object A WC_Order or WC_Subscription object or the ID of a WC_Order or WC_Subscription object
 * @param string A PayPal Standard Subscription ID or Express Checkout Billing Agreement ID
 * @since 2.0
 */
function wcs_set_paypal_id($order, $paypal_subscription_id)
{
    if (!is_object($order)) {
        $order = wc_get_order($order);
    }
    if (wcs_is_paypal_profile_a($paypal_subscription_id, 'billing_agreement')) {
        if (!in_array($paypal_subscription_id, get_user_meta($order->get_user_id(), '_paypal_subscription_id', false))) {
            add_user_meta($order->get_user_id(), '_paypal_subscription_id', $paypal_subscription_id);
        }
    }
    return update_post_meta($order->id, '_paypal_subscription_id', $paypal_subscription_id);
}
 /**
  * Include the PayPal payment meta data required to process automatic recurring payments so that store managers can
  * manually set up automatic recurring payments for a customer via the Edit Subscription screen.
  *
  * @param array $payment_meta associative array of meta data required for automatic payments
  * @param WC_Subscription $subscription An instance of a subscription object
  * @return array
  * @since 2.0
  */
 public static function add_payment_meta_details($payment_meta, $subscription)
 {
     $subscription_id = get_post_meta($subscription->id, '_paypal_subscription_id', true);
     if (wcs_is_paypal_profile_a($subscription_id, 'billing_agreement') || empty($subscription_id)) {
         $label = 'PayPal Billing Agreement ID';
         $disabled = false;
     } else {
         $label = 'PayPal Standard Subscription ID';
         $disabled = true;
     }
     $payment_meta['paypal'] = array('post_meta' => array('_paypal_subscription_id' => array('value' => $subscription_id, 'label' => $label, 'disabled' => $disabled)));
     return $payment_meta;
 }
 /**
  * Check whether the cart needs payment even if the order total is $0 because it's a subscription switch request for a subscription using
  * PayPal Standard as the subscription.
  *
  * @param bool $needs_payment The existing flag for whether the cart needs payment or not.
  * @param WC_Cart $cart The WooCommerce cart object.
  * @return bool
  */
 public static function cart_needs_payment($needs_payment, $cart)
 {
     $cart_switch_items = WC_Subscriptions_Switcher::cart_contains_switches();
     if (false === $needs_payment && 0 == $cart->total && false !== $cart_switch_items && !empty($_POST['_wpnonce']) && wp_verify_nonce($_POST['_wpnonce'], 'woocommerce-process_checkout') && isset($_POST['payment_method']) && 'paypal' == $_POST['payment_method'] && 'yes' !== get_option(WC_Subscriptions_Admin::$option_prefix . '_turn_off_automatic_payments', 'no')) {
         foreach ($cart_switch_items as $cart_switch_details) {
             $subscription = wcs_get_subscription($cart_switch_details['subscription_id']);
             if ('paypal' === $subscription->payment_method && !wcs_is_paypal_profile_a(wcs_get_paypal_id($subscription->id), 'billing_agreement')) {
                 $needs_payment = true;
                 break;
             }
         }
     }
     return $needs_payment;
 }
 /**
  * Performs an Express Checkout NVP API operation as passed in $api_method.
  *
  * Although the PayPal Standard API provides no facility for cancelling a subscription, the PayPal
  * Express Checkout NVP API can be used.
  *
  * @since 2.0
  */
 public static function update_subscription_status($subscription, $new_status)
 {
     $profile_id = wcs_get_paypal_id($subscription->id);
     if (wcs_is_paypal_profile_a($profile_id, 'billing_agreement')) {
         // Nothing to do here, leave the billing agreement active at PayPal for use with other subscriptions and just change the status in the store
         $status_updated = true;
     } elseif (!empty($profile_id)) {
         // We need to change the subscriptions status at PayPal, which is doing via Express Checkout APIs, despite the subscription having been created with PayPal Standard
         $response = self::get_api()->manage_recurring_payments_profile_status($profile_id, $new_status, $subscription);
         if (!$response->has_api_error()) {
             $status_updated = true;
         } else {
             $status_updated = false;
         }
     } else {
         $status_updated = false;
     }
     return $status_updated;
 }
 /**
  * Add additional feature support at the subscription level instead of just the gateway level because some subscriptions may have been
  * setup with PayPal Standard while others may have been setup with Billing Agreements to use with Reference Transactions.
  *
  * @since 2.0
  */
 public static function add_feature_support_for_subscription($is_supported, $feature, $subscription)
 {
     if ('paypal' === $subscription->payment_method && WCS_PayPal::are_credentials_set()) {
         $paypal_profile_id = wcs_get_paypal_id($subscription->id);
         $is_billing_agreement = wcs_is_paypal_profile_a($paypal_profile_id, 'billing_agreement');
         if ('gateway_scheduled_payments' === $feature && $is_billing_agreement) {
             $is_supported = false;
         } elseif (in_array($feature, self::$standard_supported_features)) {
             if (wcs_is_paypal_profile_a($paypal_profile_id, 'out_of_date_id')) {
                 $is_supported = false;
             } else {
                 $is_supported = true;
             }
         } elseif (in_array($feature, self::$reference_transaction_supported_features)) {
             if ($is_billing_agreement) {
                 $is_supported = true;
             } else {
                 $is_supported = false;
             }
         }
     }
     return $is_supported;
 }
 /**
  * Cancel a specific PayPal Standard Subscription Profile with PayPal.
  *
  * Used when switching payment methods with PayPal Standard to make sure that
  * the old subscription's profile ID is cancelled, not the new one.
  *
  * @param WC_Subscription A subscription object
  * @param string A PayPal Subscription Profile ID
  * @since 2.0
  */
 protected static function cancel_subscription($subscription, $old_paypal_subscriber_id)
 {
     // No need to cancel billing agreements
     if (wcs_is_paypal_profile_a($old_paypal_subscriber_id, 'billing_agreement')) {
         return;
     }
     $current_profile_id = wcs_get_paypal_id($subscription->id);
     // Update the subscription using the old profile ID
     wcs_set_paypal_id($subscription, $old_paypal_subscriber_id);
     // Call update_subscription_status() directly as we don't want the notes added by WCS_PayPal_Status_Manager::cancel_subscription()
     WCS_PayPal_Status_Manager::update_subscription_status($subscription, 'Cancel');
     // Restore the current profile ID
     wcs_set_paypal_id($subscription, $current_profile_id);
 }
 /**
  * Cancel subscriptions with PayPal Standard after the order has been successfully switched.
  *
  * @param int $order_id
  * @param string $old_status
  * @param string $new_status
  * @since 2.0.15
  */
 public static function maybe_cancel_paypal_after_switch($order_id, $old_status, $new_status)
 {
     $order_completed = in_array($new_status, array(apply_filters('woocommerce_payment_complete_order_status', 'processing', $order_id), 'processing', 'completed')) && in_array($old_status, apply_filters('woocommerce_valid_order_statuses_for_payment', array('pending', 'on-hold', 'failed')));
     if ($order_completed && wcs_order_contains_switch($order_id) && 'paypal_standard' == get_post_meta($order_id, '_old_payment_method', true)) {
         $old_profile_id = get_post_meta($order_id, '_old_paypal_subscription_id', true);
         if (!empty($old_profile_id)) {
             $subscriptions = wcs_get_subscriptions_for_order($order_id, array('order_type' => 'switch'));
             foreach ($subscriptions as $subscription) {
                 if (!wcs_is_paypal_profile_a($old_profile_id, 'billing_agreement')) {
                     $new_payment_method = $subscription->payment_method;
                     $new_profile_id = get_post_meta($subscription->id, '_paypal_subscription_id', true);
                     // grab the current paypal subscription id in case it's a billing agreement
                     update_post_meta($subscription->id, '_payment_method', 'paypal');
                     update_post_meta($subscription->id, '_paypal_subscription_id', $old_profile_id);
                     WCS_PayPal_Status_Manager::suspend_subscription($subscription);
                     // restore payment meta to the new data
                     update_post_meta($subscription->id, '_payment_method', $new_payment_method);
                     update_post_meta($subscription->id, '_paypal_subscription_id', $new_profile_id);
                 }
             }
         }
     }
 }
 /**
  * Maybe adds a warning message to subscription script parameters which is used in a Javascript dialog if the
  * payment method of the subscription is set to be changed. The warning message is only added if the subscriptions
  * payment gateway is PayPal Standard.
  *
  * @param array $script_parameters The script parameters used in subscription meta boxes.
  * @return array $script_parameters
  * @since 2.0
  */
 public static function maybe_add_change_payment_method_warning($script_parameters)
 {
     global $post;
     $subscription = wcs_get_subscription($post);
     if ('paypal' === $subscription->payment_method) {
         $paypal_profile_id = wcs_get_paypal_id($subscription->id);
         $is_paypal_standard = !wcs_is_paypal_profile_a($paypal_profile_id, 'billing_agreement');
         if ($is_paypal_standard) {
             $script_parameters['change_payment_method_warning'] = __("Are you sure you want to change the payment method from PayPal standard?\n\nThis will suspend the subscription at PayPal.", 'woocommerce-subscriptions');
         }
     }
     return $script_parameters;
 }
 /**
  * Prints link to the PayPal's profile related to the provided subscription
  *
  * @param WC_Subscription $subscription
  */
 public static function profile_link($subscription)
 {
     if (wcs_is_subscription($subscription) && 'paypal' == $subscription->payment_method) {
         $paypal_profile_id = wcs_get_paypal_id($subscription);
         if (!empty($paypal_profile_id)) {
             $url = '';
             if (false === wcs_is_paypal_profile_a($paypal_profile_id, 'billing_agreement')) {
                 // Standard subscription
                 $url = 'https://www.paypal.com/?cmd=_profile-recurring-payments&encrypted_profile_id=' . $paypal_profile_id;
             } else {
                 if (wcs_is_paypal_profile_a($paypal_profile_id, 'billing_agreement')) {
                     // Reference Transaction subscription
                     $url = 'https://www.paypal.com/?cmd=_profile-merchant-pull&encrypted_profile_id=' . $paypal_profile_id . '&mp_id=' . $paypal_profile_id . '&return_to=merchant&flag_flow=merchant';
                 }
             }
             echo '<div class="address">';
             echo '<p class="paypal_subscription_info"><strong>';
             echo esc_html(__('PayPal Subscription ID:', 'woocommerce-subscriptions'));
             echo '</strong>';
             if (!empty($url)) {
                 echo '<a href="' . esc_url($url) . '" target="_blank">' . esc_html($paypal_profile_id) . '</a>';
             } else {
                 echo esc_html($paypal_profile_id);
             }
             echo '</p></div>';
         }
     }
 }
 /**
  * Process a PayPal Standard Subscription IPN request
  *
  * @param array $transaction_details Post data after wp_unslash
  * @since 2.0
  */
 protected function process_ipn_request($transaction_details)
 {
     // Get the subscription ID and order_key with backward compatibility
     $subscription_id_and_key = self::get_order_id_and_key($transaction_details, 'shop_subscription');
     $subscription = wcs_get_subscription($subscription_id_and_key['order_id']);
     $subscription_key = $subscription_id_and_key['order_key'];
     // We have an invalid $subscription, probably because invoice_prefix has changed since the subscription was first created, so get the subscription by order key
     if (!isset($subscription->id)) {
         $subscription = wcs_get_subscription(wc_get_order_id_by_order_key($subscription_key));
     }
     if (empty($subscription)) {
         WC_Gateway_Paypal::log('Subscription IPN Error: Could not find matching Subscription.');
         exit;
     }
     if ($subscription->order_key != $subscription_key) {
         WC_Gateway_Paypal::log('Subscription IPN Error: Subscription Key does not match invoice.');
         exit;
     }
     $is_renewal_sign_up_after_failure = false;
     // If the invoice ID doesn't match the default invoice ID and contains the string '-wcsfrp-', the IPN is for a subscription payment to fix up a failed payment
     if (in_array($transaction_details['txn_type'], array('subscr_signup', 'subscr_payment')) && false !== strpos($transaction_details['invoice'], '-wcsfrp-')) {
         $renewal_order = wc_get_order(substr($transaction_details['invoice'], strrpos($transaction_details['invoice'], '-') + 1));
         // check if the failed signup has been previously recorded
         if ($renewal_order->id != get_post_meta($subscription->id, '_paypal_failed_sign_up_recorded', true)) {
             $is_renewal_sign_up_after_failure = true;
         }
     }
     // If the invoice ID doesn't match the default invoice ID and contains the string '-wcscpm-', the IPN is for a subscription payment method change
     if ('subscr_signup' == $transaction_details['txn_type'] && false !== strpos($transaction_details['invoice'], '-wcscpm-')) {
         $is_payment_change = true;
     } else {
         $is_payment_change = false;
     }
     if ($is_renewal_sign_up_after_failure || $is_payment_change) {
         // Store the old profile ID on the order (for the first IPN message that comes through)
         $existing_profile_id = wcs_get_paypal_id($subscription);
         if (empty($existing_profile_id) || $existing_profile_id !== $transaction_details['subscr_id']) {
             update_post_meta($subscription->id, '_old_paypal_subscriber_id', $existing_profile_id);
             update_post_meta($subscription->id, '_old_payment_method', $subscription->payment_method);
         }
     }
     // Ignore IPN messages when the payment method isn't PayPal
     if ('paypal' != $subscription->payment_method) {
         // The 'recurring_payment_suspended' transaction is actually an Express Checkout transaction type, but PayPal also send it for PayPal Standard Subscriptions suspended by admins at PayPal, so we need to handle it *if* the subscription has PayPal as the payment method, or leave it if the subscription is using a different payment method (because it might be using PayPal Express Checkout or PayPal Digital Goods)
         if ('recurring_payment_suspended' == $transaction_details['txn_type']) {
             WC_Gateway_Paypal::log('"recurring_payment_suspended" IPN ignored: recurring payment method is not "PayPal". Returning to allow another extension to process the IPN, like PayPal Digital Goods.');
             return;
         } elseif (false === $is_renewal_sign_up_after_failure && false === $is_payment_change) {
             WC_Gateway_Paypal::log('IPN ignored, recurring payment method has changed.');
             exit;
         }
     }
     if (isset($transaction_details['ipn_track_id'])) {
         // Make sure the IPN request has not already been handled
         $handled_ipn_requests = get_post_meta($subscription->id, '_paypal_ipn_tracking_ids', true);
         if (empty($handled_ipn_requests)) {
             $handled_ipn_requests = array();
         }
         // The 'ipn_track_id' is not a unique ID and is shared between different transaction types, so create a unique ID by prepending the transaction type
         $ipn_id = $transaction_details['txn_type'] . '_' . $transaction_details['ipn_track_id'];
         if (in_array($ipn_id, $handled_ipn_requests)) {
             WC_Gateway_Paypal::log('Subscription IPN Error: IPN ' . $ipn_id . ' message has already been correctly handled.');
             exit;
         }
     }
     if (isset($transaction_details['txn_id'])) {
         // Make sure the IPN request has not already been handled
         $handled_transactions = get_post_meta($subscription->id, '_paypal_transaction_ids', true);
         if (empty($handled_transactions)) {
             $handled_transactions = array();
         }
         $transaction_id = $transaction_details['txn_id'];
         if (isset($transaction_details['txn_type'])) {
             $transaction_id .= '_' . $transaction_details['txn_type'];
         }
         // The same transaction ID is used for different payment statuses, so make sure we handle it only once. See: http://stackoverflow.com/questions/9240235/paypal-ipn-unique-identifier
         if (isset($transaction_details['payment_status'])) {
             $transaction_id .= '_' . $transaction_details['payment_status'];
         }
         if (in_array($transaction_id, $handled_transactions)) {
             WC_Gateway_Paypal::log('Subscription IPN Error: transaction ' . $transaction_id . ' has already been correctly handled.');
             exit;
         }
     }
     // Save the profile ID if it's not a cancellation/expiration request
     if (isset($transaction_details['subscr_id']) && !in_array($transaction_details['txn_type'], array('subscr_cancel', 'subscr_eot'))) {
         wcs_set_paypal_id($subscription, $transaction_details['subscr_id']);
         if (wcs_is_paypal_profile_a($transaction_details['subscr_id'], 'out_of_date_id') && 'disabled' != get_option('wcs_paypal_invalid_profile_id')) {
             update_option('wcs_paypal_invalid_profile_id', 'yes');
         }
     }
     $is_first_payment = $subscription->get_completed_payment_count() < 1 ? true : false;
     if ($subscription->has_status('switched')) {
         WC_Gateway_Paypal::log('IPN ignored, subscription has been switched.');
         exit;
     }
     switch ($transaction_details['txn_type']) {
         case 'subscr_signup':
             // Store PayPal Details on Subscription and Order
             $this->save_paypal_meta_data($subscription, $transaction_details);
             $this->save_paypal_meta_data($subscription->order, $transaction_details);
             // When there is a free trial & no initial payment amount, we need to mark the order as paid and activate the subscription
             if (!$is_payment_change && !$is_renewal_sign_up_after_failure && 0 == $subscription->order->get_total()) {
                 // Safe to assume the subscription has an order here because otherwise we wouldn't get a 'subscr_signup' IPN
                 $subscription->order->payment_complete();
                 // No 'txn_id' value for 'subscr_signup' IPN messages
                 update_post_meta($subscription->id, '_paypal_first_ipn_ignored_for_pdt', 'true');
             }
             // Payment completed
             if ($is_payment_change) {
                 // Set PayPal as the new payment method
                 WC_Subscriptions_Change_Payment_Gateway::update_payment_method($subscription, 'paypal');
                 // We need to cancel the subscription now that the method has been changed successfully
                 if ('paypal' == get_post_meta($subscription->id, '_old_payment_method', true)) {
                     self::cancel_subscription($subscription, get_post_meta($subscription->id, '_old_paypal_subscriber_id', true));
                 }
                 $subscription->add_order_note(__('IPN subscription payment method changed to PayPal.', 'woocommerce-subscriptions'));
             } else {
                 $subscription->add_order_note(__('IPN subscription sign up completed.', 'woocommerce-subscriptions'));
             }
             if ($is_payment_change) {
                 WC_Gateway_Paypal::log('IPN subscription payment method changed for subscription ' . $subscription->id);
             } else {
                 WC_Gateway_Paypal::log('IPN subscription sign up completed for subscription ' . $subscription->id);
             }
             break;
         case 'subscr_payment':
             if (!$is_first_payment && !$is_renewal_sign_up_after_failure) {
                 // Generate a renewal order to record the payment (and determine how much is due)
                 $renewal_order = wcs_create_renewal_order($subscription);
                 // Set PayPal as the payment method (we can't use $renewal_order->set_payment_method() here as it requires an object we don't have)
                 $available_gateways = WC()->payment_gateways->get_available_payment_gateways();
                 $renewal_order->set_payment_method($available_gateways['paypal']);
             }
             if ('completed' == strtolower($transaction_details['payment_status'])) {
                 // Store PayPal Details
                 $this->save_paypal_meta_data($subscription, $transaction_details);
                 // Subscription Payment completed
                 $subscription->add_order_note(__('IPN subscription payment completed.', 'woocommerce-subscriptions'));
                 WC_Gateway_Paypal::log('IPN subscription payment completed for subscription ' . $subscription->id);
                 // First payment on order, process payment & activate subscription
                 if ($is_first_payment) {
                     $subscription->order->payment_complete($transaction_details['txn_id']);
                     // Store PayPal Details on Order
                     $this->save_paypal_meta_data($subscription->order, $transaction_details);
                     // IPN got here first or PDT will never arrive. Normally PDT would have arrived, so the first IPN would not be the first payment. In case the the first payment is an IPN, we need to make sure to not ignore the second one
                     update_post_meta($subscription->id, '_paypal_first_ipn_ignored_for_pdt', 'true');
                     // Ignore the first IPN message if the PDT should have handled it (if it didn't handle it, it will have been dealt with as first payment), but set a flag to make sure we only ignore it once
                 } elseif ($subscription->get_completed_payment_count() == 1 && '' !== WCS_PayPal::get_option('identity_token') && 'true' != get_post_meta($subscription->id, '_paypal_first_ipn_ignored_for_pdt', true) && false === $is_renewal_sign_up_after_failure) {
                     WC_Gateway_Paypal::log('IPN subscription payment ignored for subscription ' . $subscription->id . ' due to PDT previously handling the payment.');
                     update_post_meta($subscription->id, '_paypal_first_ipn_ignored_for_pdt', 'true');
                     // Process the payment if the subscription is active
                 } elseif (!$subscription->has_status(array('cancelled', 'expired', 'switched', 'trash'))) {
                     // We don't need to reactivate the subscription because Subs didn't suspend it
                     remove_action('woocommerce_subscription_activated_paypal', __CLASS__ . '::reactivate_subscription');
                     if (true === $is_renewal_sign_up_after_failure && is_object($renewal_order)) {
                         update_post_meta($subscription->id, '_paypal_failed_sign_up_recorded', $renewal_order->id);
                         // We need to cancel the old subscription now that the method has been changed successfully
                         if ('paypal' == get_post_meta($subscription->id, '_old_payment_method', true)) {
                             $profile_id = get_post_meta($subscription->id, '_old_paypal_subscriber_id', true);
                             // Make sure we don't cancel the current profile
                             if ($profile_id !== $transaction_details['subscr_id']) {
                                 self::cancel_subscription($subscription, $profile_id);
                             }
                             $subscription->add_order_note(__('IPN subscription failing payment method changed.', 'woocommerce-subscriptions'));
                         }
                     }
                     $renewal_order->payment_complete($transaction_details['txn_id']);
                     $renewal_order->add_order_note(__('IPN subscription payment completed.', 'woocommerce-subscriptions'));
                     wcs_set_paypal_id($renewal_order, $transaction_details['subscr_id']);
                     add_action('woocommerce_subscription_activated_paypal', __CLASS__ . '::reactivate_subscription');
                 }
             } elseif (in_array(strtolower($transaction_details['payment_status']), array('pending', 'failed'))) {
                 // Subscription Payment completed
                 // translators: placeholder is payment status (e.g. "completed")
                 $subscription->add_order_note(sprintf(_x('IPN subscription payment %s.', 'used in order note', 'woocommerce-subscriptions'), $transaction_details['payment_status']));
                 if (!$is_first_payment) {
                     update_post_meta($renewal_order->id, '_transaction_id', $transaction_details['txn_id']);
                     // translators: placeholder is payment status (e.g. "completed")
                     $renewal_order->add_order_note(sprintf(_x('IPN subscription payment %s.', 'used in order note', 'woocommerce-subscriptions'), $transaction_details['payment_status']));
                     $subscription->payment_failed();
                 }
                 WC_Gateway_Paypal::log('IPN subscription payment failed for subscription ' . $subscription->id);
             } else {
                 WC_Gateway_Paypal::log('IPN subscription payment notification received for subscription ' . $subscription->id . ' with status ' . $transaction_details['payment_status']);
             }
             break;
             // Admins can suspend subscription at PayPal triggering this IPN
         // Admins can suspend subscription at PayPal triggering this IPN
         case 'recurring_payment_suspended':
             if (!$subscription->has_status('on-hold')) {
                 // We don't need to suspend the subscription at PayPal because it's already on-hold there
                 remove_action('woocommerce_subscription_on-hold_paypal', __CLASS__ . '::suspend_subscription');
                 $subscription->update_status('on-hold', __('IPN subscription suspended.', 'woocommerce-subscriptions'));
                 add_action('woocommerce_subscription_activated_paypal', __CLASS__ . '::reactivate_subscription');
                 WC_Gateway_Paypal::log('IPN subscription suspended for subscription ' . $subscription->id);
             } else {
                 WC_Gateway_Paypal::log(sprintf('IPN "recurring_payment_suspended" ignored for subscription %d. Subscription already on-hold.', $subscription->id));
             }
             break;
         case 'subscr_cancel':
             // Make sure the subscription hasn't been linked to a new payment method
             if (wcs_get_paypal_id($subscription) != $transaction_details['subscr_id']) {
                 WC_Gateway_Paypal::log('IPN subscription cancellation request ignored - new PayPal Profile ID linked to this subscription, for subscription ' . $subscription->id);
             } else {
                 $subscription->cancel_order(__('IPN subscription cancelled.', 'woocommerce-subscriptions'));
                 WC_Gateway_Paypal::log('IPN subscription cancelled for subscription ' . $subscription->id);
             }
             break;
         case 'subscr_eot':
             // Subscription ended, either due to failed payments or expiration
             WC_Gateway_Paypal::log('IPN EOT request ignored for subscription ' . $subscription->id);
             break;
         case 'subscr_failed':
             // Subscription sign up failed
         // Subscription sign up failed
         case 'recurring_payment_suspended_due_to_max_failed_payment':
             // Subscription sign up failed
             WC_Gateway_Paypal::log('IPN subscription payment failure for subscription ' . $subscription->id);
             // Subscription Payment completed
             $subscription->add_order_note(__('IPN subscription payment failure.', 'woocommerce-subscriptions'));
             $subscription->payment_failed();
             break;
     }
     // Store the transaction IDs to avoid handling requests duplicated by PayPal
     if (isset($transaction_details['ipn_track_id'])) {
         $handled_ipn_requests[] = $ipn_id;
         update_post_meta($subscription->id, '_paypal_ipn_tracking_ids', $handled_ipn_requests);
     }
     if (isset($transaction_details['txn_id'])) {
         $handled_transactions[] = $transaction_id;
         update_post_meta($subscription->id, '_paypal_transaction_ids', $handled_transactions);
     }
     // Log completion
     $log_message = 'IPN subscription request processed for ' . $subscription->id;
     if (isset($ipn_id) && !empty($ipn_id)) {
         $log_message .= sprintf(' (%s)', $ipn_id);
     }
     WC_Gateway_Paypal::log($log_message);
     // Prevent default IPN handling for subscription txn_types
     exit;
 }