/**
  * Store any messages or errors added by other plugins, particularly important for those occasions when the new payment
  * method caused and error or failure.
  *
  * @since 1.4
  */
 public static function store_pay_shortcode_mesages()
 {
     if (wc_notice_count('notice') > 0) {
         self::$woocommerce_messages = wc_get_notices('success');
         self::$woocommerce_messages += wc_get_notices('notice');
     }
     if (wc_notice_count('error') > 0) {
         self::$woocommerce_errors = wc_get_notices('error');
     }
 }
 /**
  * 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 ('recurring_payment_suspended_due_to_max_failed_payment' == $transaction_details['txn_type'] && empty($subscription)) {
         WC_Gateway_Paypal::log('Returning as "recurring_payment_suspended_due_to_max_failed_payment" transaction is for a subscription created with Express Checkout');
         return;
     }
     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;
     }
     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;
         }
         // Make sure we're not in the process of handling this IPN request on a server under extreme load and therefore, taking more than a minute to process it (which is the amount of time PayPal allows before resending the IPN request)
         $ipn_lock_transient_name = 'wcs_pp_' . $ipn_id;
         // transient names need to be less than 45 characters and the $ipn_id will be around 30 characters, e.g. subscr_payment_5ab4c38e1f39d
         if ('in-progress' == get_transient($ipn_lock_transient_name) && 'recurring_payment_suspended_due_to_max_failed_payment' !== $transaction_details['txn_type']) {
             WC_Gateway_Paypal::log('Subscription IPN Error: an older IPN request with ID ' . $ipn_id . ' is still in progress.');
             // We need to send an error code to make sure PayPal does retry the IPN after our lock expires, in case something is actually going wrong and the server isn't just taking a long time to process the request
             status_header(503);
             exit;
         }
         // Set a transient to block IPNs with this transaction ID for the next 5 minutes
         set_transient($ipn_lock_transient_name, 'in-progress', apply_filters('woocommerce_subscriptions_paypal_ipn_request_lock_time', 5 * MINUTE_IN_SECONDS));
     }
     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;
         }
     }
     WC_Gateway_Paypal::log('Subscription transaction details: ' . print_r($transaction_details, true));
     WC_Gateway_Paypal::log('Subscription Transaction Type: ' . $transaction_details['txn_type']);
     $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;
     }
     // 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 ($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);
         }
     }
     // 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(_x('IPN subscription payment method changed to PayPal.', 'when it is a payment change, and there is a subscr_signup message, this will be a confirmation message that PayPal accepted it being the new payment method', '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) {
                 if ($subscription->has_status('active')) {
                     remove_action('woocommerce_subscription_on-hold_paypal', 'WCS_PayPal_Status_Manager::suspend_subscription');
                     $subscription->update_status('on-hold');
                     add_action('woocommerce_subscription_on-hold_paypal', 'WCS_PayPal_Status_Manager::suspend_subscription');
                 }
                 // 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'))) {
                     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'));
                         }
                     }
                     try {
                         // to cover the case when PayPal drank too much coffee and sent IPNs early - needs to happen before $renewal_order->payment_complete
                         $update_dates = array();
                         if ($subscription->get_time('trial_end') > gmdate('U')) {
                             $update_dates['trial_end'] = gmdate('Y-m-d H:i:s', gmdate('U') - 1);
                             WC_Gateway_Paypal::log(sprintf('IPN subscription payment for subscription %d: trial_end is in futute (date: %s) setting to %s.', $subscription->id, $subscription->get_date('trial_end'), $update_dates['trial_end']));
                         } else {
                             WC_Gateway_Paypal::log(sprintf('IPN subscription payment for subscription %d: trial_end is in past (date: %s).', $subscription->id, $subscription->get_date('trial_end')));
                         }
                         if ($subscription->get_time('next_payment') > gmdate('U')) {
                             $update_dates['next_payment'] = gmdate('Y-m-d H:i:s', gmdate('U') - 1);
                             WC_Gateway_Paypal::log(sprintf('IPN subscription payment for subscription %d: next_payment is in future (date: %s) setting to %s.', $subscription->id, $subscription->get_date('next_payment'), $update_dates['next_payment']));
                         } else {
                             WC_Gateway_Paypal::log(sprintf('IPN subscription payment for subscription %d: next_payment is in past (date: %s).', $subscription->id, $subscription->get_date('next_payment')));
                         }
                         if (!empty($update_dates)) {
                             $subscription->update_dates($update_dates);
                         }
                     } catch (Exception $e) {
                         WC_Gateway_Paypal::log(sprintf('IPN subscription payment exception subscription %d: %s.', $subscription->id, $e->getMessage()));
                     }
                     remove_action('woocommerce_subscription_activated_paypal', 'WCS_PayPal_Status_Manager::reactivate_subscription');
                     try {
                         $renewal_order->payment_complete($transaction_details['txn_id']);
                     } catch (Exception $e) {
                         WC_Gateway_Paypal::log(sprintf('IPN subscription payment exception calling $renewal_order->payment_complete() for subscription %d: %s.', $subscription->id, $e->getMessage()));
                     }
                     $renewal_order->add_order_note(__('IPN subscription payment completed.', 'woocommerce-subscriptions'));
                     add_action('woocommerce_subscription_activated_paypal', 'WCS_PayPal_Status_Manager::reactivate_subscription');
                     wcs_set_paypal_id($renewal_order, $transaction_details['subscr_id']);
                 }
             } 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('active')) {
                 // We don't need to suspend the subscription at PayPal because it's already on-hold there
                 remove_action('woocommerce_subscription_on-hold_paypal', 'WCS_PayPal_Status_Manager::suspend_subscription');
                 $subscription->update_status('on-hold', __('IPN subscription suspended.', 'woocommerce-subscriptions'));
                 add_action('woocommerce_subscription_on-hold_paypal', 'WCS_PayPal_Status_Manager::suspend_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 %s.', $subscription->id, $subscription->get_status()));
             }
             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':
             // Recurring payment failed
             $ipn_failure_note = __('IPN subscription payment failure.', 'woocommerce-subscriptions');
             if (!$is_first_payment && !$is_renewal_sign_up_after_failure && $subscription->has_status('active')) {
                 // Generate a renewal order to record the failed payment
                 $renewal_order = wcs_create_renewal_order($subscription);
                 // Set PayPal as the payment method
                 $available_gateways = WC()->payment_gateways->get_available_payment_gateways();
                 $renewal_order->set_payment_method($available_gateways['paypal']);
                 $renewal_order->add_order_note($ipn_failure_note);
             }
             WC_Gateway_Paypal::log('IPN subscription payment failure for subscription ' . $subscription->id);
             // Subscription Payment completed
             $subscription->add_order_note($ipn_failure_note);
             try {
                 $subscription->payment_failed();
             } catch (Exception $e) {
                 WC_Gateway_Paypal::log(sprintf('IPN subscription payment failure, unable to process payment failure. Exception: %s ', $e->getMessage()));
             }
             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);
     }
     // And delete the transient that's preventing other IPN's being processed
     if (isset($ipn_lock_transient_name)) {
         delete_transient($ipn_lock_transient_name);
     }
     // 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;
 }
 /**
  * Keep a record of an order's dates if we're marking it as completed during a request to change the payment method.
  *
  * @since 1.4
  */
 public static function store_original_order_dates($new_order_status, $order_id)
 {
     if (self::$is_request_to_change_payment) {
         $order = new WC_Order($order_id);
         $post = get_post($order_id);
         self::$original_order_dates = array('_paid_date' => WC_Subscriptions_Order::get_meta($order, 'paid_date'), '_completed_date' => WC_Subscriptions_Order::get_meta($order, 'completed_date'), 'post_date' => $order->order_date, 'post_date_gmt' => $post->post_date_gmt);
     }
     return $new_order_status;
 }
 /**
  * Keep a record of an order's dates if we're marking it as completed during a request to change the payment method.
  *
  * @since 1.4
  */
 public static function store_original_order_dates($new_order_status, $order_id)
 {
     if (self::$is_request_to_change_payment) {
         $order = new WC_Order($order_id);
         $post = get_post($order_id);
         self::$original_order_dates = array('_paid_date' => $order->order_custom_fields['_paid_date'][0], '_completed_date' => $order->order_custom_fields['_completed_date'][0], 'post_date' => $order->order_date, 'post_date_gmt' => $post->post_date_gmt);
     }
     return $new_order_status;
 }
Exemplo n.º 5
0
 /**
  * Handle WC API requests where we need to run a reference transaction API operation
  *
  * @since 2.0
  */
 public static function handle_wc_api()
 {
     if (!isset($_GET['action'])) {
         return;
     }
     switch ($_GET['action']) {
         // called when the customer is returned from PayPal after authorizing their payment, used for retrieving the customer's checkout details
         case 'create_billing_agreement':
             // bail if no token
             if (!isset($_GET['token'])) {
                 return;
             }
             // get token to retrieve checkout details with
             $token = esc_attr($_GET['token']);
             try {
                 $express_checkout_details_response = self::get_api()->get_express_checkout_details($token);
                 // Make sure the billing agreement was accepted
                 if (1 == $express_checkout_details_response->get_billing_agreement_status()) {
                     $order = $express_checkout_details_response->get_order();
                     if (is_null($order)) {
                         throw new Exception(__('Unable to find order for PayPal billing agreement.', 'woocommerce-subscriptions'));
                     }
                     // we need to process an initial payment
                     if ($order->get_total() > 0 && !wcs_is_subscription($order)) {
                         $billing_agreement_response = self::get_api()->do_express_checkout($token, $order, array('payment_action' => 'Sale', 'payer_id' => $express_checkout_details_response->get_payer_id()));
                     } else {
                         $billing_agreement_response = self::get_api()->create_billing_agreement($token);
                     }
                     if ($billing_agreement_response->has_api_error()) {
                         throw new Exception($billing_agreement_response->get_api_error_message(), $billing_agreement_response->get_api_error_code());
                     }
                     // We're changing the payment method for a subscription, make sure we update it before updating the billing agreement ID so that an old PayPal subscription can be cancelled if the existing payment method is also PayPal
                     if (wcs_is_subscription($order)) {
                         WC_Subscriptions_Change_Payment_Gateway::update_payment_method($order, 'paypal');
                         $redirect_url = add_query_arg('utm_nooverride', '1', $order->get_view_order_url());
                     }
                     // Make sure PayPal is set as the payment method on the order and subscription
                     $available_gateways = WC()->payment_gateways->get_available_payment_gateways();
                     $payment_method = isset($available_gateways[self::instance()->get_id()]) ? $available_gateways[self::instance()->get_id()] : false;
                     $order->set_payment_method($payment_method);
                     // Store the billing agreement ID on the order and subscriptions
                     wcs_set_paypal_id($order, $billing_agreement_response->get_billing_agreement_id());
                     foreach (wcs_get_subscriptions_for_order($order, array('order_type' => 'any')) as $subscription) {
                         $subscription->set_payment_method($payment_method);
                         wcs_set_paypal_id($subscription, $billing_agreement_response->get_billing_agreement_id());
                     }
                     if (!wcs_is_subscription($order)) {
                         if (0 == $order->get_total()) {
                             $order->payment_complete();
                         } else {
                             self::process_subscription_payment_response($order, $billing_agreement_response);
                         }
                         $redirect_url = add_query_arg('utm_nooverride', '1', $order->get_checkout_order_received_url());
                     }
                     // redirect customer to order received page
                     wp_safe_redirect(esc_url_raw($redirect_url));
                 } else {
                     wp_safe_redirect(WC()->cart->get_cart_url());
                 }
             } catch (Exception $e) {
                 wc_add_notice(__('An error occurred, please try again or try an alternate form of payment.', 'woocommerce-subscriptions'), 'error');
                 wp_redirect(WC()->cart->get_cart_url());
             }
             exit;
         case 'reference_transaction_account_check':
             exit;
     }
 }
 /**
  * When a PayPal IPN messaged is received for a subscription transaction, 
  * check the transaction details and 
  *
  * @since 1.0
  */
 public static function process_paypal_ipn_request($transaction_details)
 {
     global $wpdb;
     $transaction_details = stripslashes_deep($transaction_details);
     if (!in_array($transaction_details['txn_type'], array('subscr_signup', 'subscr_payment', 'subscr_cancel', 'subscr_eot', 'subscr_failed', 'subscr_modify', 'recurring_payment_suspended_due_to_max_failed_payment'))) {
         return;
     }
     if ('recurring_payment_suspended_due_to_max_failed_payment' == $transaction_details['txn_type'] && isset($transaction_details['rp_invoice_id'])) {
         self::$log->add('paypal', 'Returning as "recurring_payment_suspended_due_to_max_failed_payment" transaction is for a subscription created with Express Checkout');
         return;
     }
     // Get the $order_id & $order_key with backward compatibility
     extract(self::get_order_id_and_key($transaction_details));
     $transaction_details['txn_type'] = strtolower($transaction_details['txn_type']);
     if (self::$debug) {
         self::$log->add('paypal', 'Subscription Transaction Type: ' . $transaction_details['txn_type']);
     }
     if (self::$debug) {
         self::$log->add('paypal', 'Subscription transaction details: ' . print_r($transaction_details, true));
     }
     $order = new WC_Order($order_id);
     // We have an invalid $order_id, probably because invoice_prefix has changed since the subscription was first created, so get the order by order key
     if (!isset($order->id)) {
         $order_id = function_exists('woocommerce_get_order_id_by_order_key') ? woocommerce_get_order_id_by_order_key($order_key) : $wpdb->get_var("SELECT post_id FROM {$wpdb->prefix}postmeta WHERE meta_key = '_order_key' AND meta_value = '{$order_key}'");
         $order = new WC_Order($order_id);
     }
     if ($order->order_key !== $order_key) {
         if (self::$debug) {
             self::$log->add('paypal', 'Subscription IPN Error: Order 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 (false !== strpos($transaction_details['invoice'], '-wcsfrp-') && in_array($transaction_details['txn_type'], array('subscr_signup', 'subscr_payment'))) {
         $renewal_order = new WC_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($order->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 (false !== strpos($transaction_details['invoice'], '-wcscpm-') && 'subscr_signup' == $transaction_details['txn_type']) {
         $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 = self::get_subscriptions_paypal_id($order);
         if (empty($existing_profile_id) || $existing_profile_id !== $transaction_details['subscr_id']) {
             update_post_meta($order->id, '_old_paypal_subscriber_id', $existing_profile_id);
             update_post_meta($order->id, '_old_recurring_payment_method', $order->recurring_payment_method);
         }
     }
     if ('paypal' != $order->recurring_payment_method && false === $is_renewal_sign_up_after_failure && false === $is_payment_change) {
         if (self::$debug) {
             self::$log->add('paypal', '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($order_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)) {
             if (self::$debug) {
                 self::$log->add('paypal', '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($order_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)) {
             if (self::$debug) {
                 self::$log->add('paypal', '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'))) {
         update_post_meta($order_id, 'PayPal Subscriber ID', $transaction_details['subscr_id']);
         if ('S-' == substr($transaction_details['subscr_id'], 0, 2) && 'disabled' != get_option('wcs_paypal_invalid_profile_id')) {
             update_option('wcs_paypal_invalid_profile_id', 'yes');
         }
     }
     // Get the subscription this IPN message relates to
     $subscriptions_in_order = WC_Subscriptions_Order::get_recurring_items($order);
     $subscription_item = array_pop($subscriptions_in_order);
     $product_id = WC_Subscriptions_Order::get_items_product_id($subscription_item);
     $subscription_key = WC_Subscriptions_Manager::get_subscription_key($order->id, $product_id);
     $subscription = WC_Subscriptions_Manager::get_subscription($subscription_key);
     $is_first_payment = empty($subscription['completed_payments']) ? true : false;
     if ('switched' === $subscription['status']) {
         if (self::$debug) {
             self::$log->add('paypal', 'IPN ignored, subscription has been switched.');
         }
         exit;
     }
     switch ($transaction_details['txn_type']) {
         case 'subscr_signup':
             // Store PayPal Details
             update_post_meta($order_id, 'Payer PayPal address', $transaction_details['payer_email']);
             update_post_meta($order_id, 'Payer PayPal first name', $transaction_details['first_name']);
             update_post_meta($order_id, 'Payer PayPal last name', $transaction_details['last_name']);
             $switched_subscription_key = get_post_meta($order_id, '_switched_subscription_key', true);
             $no_initial_payment = 0 == WC_Subscriptions_Order::get_total_initial_payment($order) ? true : false;
             // 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 && (!empty($switched_subscription_key) || $no_initial_payment)) {
                 $order->payment_complete($transaction_details['txn_id']);
             }
             // Payment completed
             if ($is_payment_change) {
                 // Set PayPal as the new payment method
                 WC_Subscriptions_Change_Payment_Gateway::update_recurring_payment_method($subscription_key, $order, 'paypal');
                 $old_payment_method = get_post_meta($order->id, '_old_recurring_payment_method', true);
                 // We need to cancel the subscription now that the method has been changed successfully
                 if ('paypal' == $old_payment_method) {
                     $existing_profile_id = get_post_meta($order->id, '_old_paypal_subscriber_id', true);
                     self::cancel_subscription_with_paypal($order, $product_id, $existing_profile_id);
                 }
                 $order->add_order_note(__('IPN subscription payment method changed to PayPal.', 'woocommerce-subscriptions'));
             } else {
                 $order->add_order_note(__('IPN subscription sign up completed.', 'woocommerce-subscriptions'));
             }
             if (self::$debug) {
                 if ($is_payment_change) {
                     self::$log->add('paypal', 'IPN subscription payment method changed for order ' . $order_id);
                 } else {
                     self::$log->add('paypal', 'IPN subscription sign up completed for order ' . $order_id);
                 }
             }
             break;
         case 'subscr_payment':
             if ('completed' == strtolower($transaction_details['payment_status'])) {
                 // Store PayPal Details
                 update_post_meta($order_id, '_transaction_id', $transaction_details['txn_id']);
                 update_post_meta($order_id, 'PayPal Transaction ID', $transaction_details['txn_id']);
                 update_post_meta($order_id, 'Payer PayPal first name', $transaction_details['first_name']);
                 update_post_meta($order_id, 'Payer PayPal last name', $transaction_details['last_name']);
                 update_post_meta($order_id, 'PayPal Payment type', $transaction_details['payment_type']);
                 // Subscription Payment completed
                 $order->add_order_note(__('IPN subscription payment completed.', 'woocommerce-subscriptions'));
                 if (self::$debug) {
                     self::$log->add('paypal', 'IPN subscription payment completed for order ' . $order_id);
                 }
                 // First payment on order, process payment & activate subscription
                 if ($is_first_payment) {
                     $order->payment_complete($transaction_details['txn_id']);
                     WC_Subscriptions_Manager::activate_subscriptions_for_order($order);
                     // 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 (count($subscription['completed_payments']) == 1 && !empty(self::$paypal_settings['identity_token']) && 'true' != get_post_meta($order_id, '_paypal_first_ipn_ignored_for_pdt', true) && false === $is_renewal_sign_up_after_failure) {
                     if (self::$debug) {
                         self::$log->add('paypal', 'IPN subscription payment ignored for order ' . $order_id . ' due to PDT previously handling the payment.');
                     }
                     update_post_meta($order_id, '_paypal_first_ipn_ignored_for_pdt', 'true');
                     // Process the payment if the subscription is active
                 } elseif (!in_array($subscription['status'], array('cancelled', 'expired', 'switched', 'trash'))) {
                     // We don't need to reactivate the subscription because Subs didn't suspend it
                     remove_action('reactivated_subscription_paypal', __CLASS__ . '::reactivate_subscription_with_paypal', 10, 2);
                     if (true === $is_renewal_sign_up_after_failure && is_object($renewal_order)) {
                         $old_payment_method = get_post_meta($order->id, '_old_recurring_payment_method', true);
                         update_post_meta($order->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' == $old_payment_method) {
                             $profile_id = get_post_meta($order->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_with_paypal($order, $product_id, $profile_id);
                             }
                             $order->add_order_note(__('IPN subscription failing payment method changed.', 'woocommerce-subscriptions'));
                         }
                         $renewal_order->payment_complete($transaction_details['txn_id']);
                         // WC_Subscriptions_Manager::process_subscription_payments_on_order will be called with by WC_Subscriptions_Renewal_Order::process_subscription_payment_on_child_order()
                         update_post_meta($renewal_order->id, 'PayPal Subscriber ID', $transaction_details['subscr_id']);
                     } else {
                         WC_Subscriptions_Manager::process_subscription_payments_on_order($order);
                     }
                     // Make sure the next payment date is synchronised with when PayPal processes the payments
                     WC_Subscriptions_Manager::set_next_payment_date($subscription_key, $order->customer_user);
                     add_action('reactivated_subscription_paypal', __CLASS__ . '::reactivate_subscription_with_paypal', 10, 2);
                 }
             } elseif ('failed' == strtolower($transaction_details['payment_status'])) {
                 // Subscription Payment completed
                 $order->add_order_note(__('IPN subscription payment failed.', 'woocommerce-subscriptions'));
                 if (self::$debug) {
                     self::$log->add('paypal', 'IPN subscription payment failed for order ' . $order_id);
                 }
                 // First payment on order, don't generate a renewal order
                 if ($is_first_payment) {
                     remove_action('processed_subscription_payment_failure', 'WC_Subscriptions_Renewal_Order::generate_failed_payment_renewal_order', 10, 2);
                 }
                 WC_Subscriptions_Manager::process_subscription_payment_failure_on_order($order);
             } else {
                 if (self::$debug) {
                     self::$log->add('paypal', 'IPN subscription payment notification received for order ' . $order_id . ' with status ' . $transaction_details['payment_status']);
                 }
             }
             break;
         case 'subscr_cancel':
             // Make sure the subscription hasn't been linked to a new payment method
             if ($transaction_details['subscr_id'] != self::get_subscriptions_paypal_id($order)) {
                 if (self::$debug) {
                     self::$log->add('paypal', 'IPN subscription cancellation request ignored - new PayPal Profile ID linked to this subscription, for order ' . $order_id);
                 }
             } else {
                 WC_Subscriptions_Manager::cancel_subscriptions_for_order($order);
                 // Subscription Cancellation Completed
                 $order->add_order_note(__('IPN subscription cancelled for order.', 'woocommerce-subscriptions'));
                 if (self::$debug) {
                     self::$log->add('paypal', 'IPN subscription cancelled for order ' . $order_id);
                 }
             }
             break;
         case 'subscr_eot':
             // Subscription ended, either due to failed payments or expiration
             if (self::$debug) {
                 self::$log->add('paypal', 'IPN EOT request ignored for order ' . $order_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
             if (self::$debug) {
                 self::$log->add('paypal', 'IPN subscription payment failure for order ' . $order_id);
             }
             // Subscription Payment completed
             $order->add_order_note(__('IPN subscription payment failure.', 'woocommerce-subscriptions'));
             // First payment on order, don't generate a renewal order
             if ($is_first_payment) {
                 remove_action('processed_subscription_payment_failure', 'WC_Subscriptions_Renewal_Order::generate_failed_payment_renewal_order', 10, 2);
             }
             WC_Subscriptions_Manager::process_subscription_payment_failure_on_order($order);
             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($order_id, '_paypal_ipn_tracking_ids', $handled_ipn_requests);
     }
     if (isset($transaction_details['txn_id'])) {
         $handled_transactions[] = $transaction_id;
         update_post_meta($order_id, '_paypal_transaction_ids', $handled_transactions);
     }
     // Prevent default IPN handling for subscription txn_types
     exit;
 }