/**
  * Get PayPal Args for passing to PP
  *
  * Based on the HTML Variables documented here: https://developer.paypal.com/webapps/developer/docs/classic/paypal-payments-standard/integration-guide/Appx_websitestandard_htmlvariables/#id08A6HI00JQU
  *
  * @param WC_Order $order
  * @return array
  */
 public static function get_paypal_args($paypal_args, $order)
 {
     $is_payment_change = WC_Subscriptions_Change_Payment_Gateway::$is_request_to_change_payment;
     $order_contains_failed_renewal = false;
     // Payment method changes act on the subscription not the original order
     if ($is_payment_change) {
         $subscriptions = array(wcs_get_subscription($order->id));
         $subscription = array_pop($subscriptions);
         $order = $subscription->order;
         // We need the subscription's total
         remove_filter('woocommerce_order_amount_total', 'WC_Subscriptions_Change_Payment_Gateway::maybe_zero_total', 11, 2);
     } else {
         // Otherwise the order is the $order
         if ($cart_item = wcs_cart_contains_failed_renewal_order_payment() || false !== WC_Subscriptions_Renewal_Order::get_failed_order_replaced_by($order->id)) {
             $subscriptions = wcs_get_subscriptions_for_renewal_order($order);
             $order_contains_failed_renewal = true;
         } else {
             $subscriptions = wcs_get_subscriptions_for_order($order);
         }
         // Only one subscription allowed per order with PayPal
         $subscription = array_pop($subscriptions);
     }
     if ($order_contains_failed_renewal || !empty($subscription) && $subscription->get_total() > 0 && 'yes' !== get_option(WC_Subscriptions_Admin::$option_prefix . '_turn_off_automatic_payments', 'no')) {
         // It's a subscription
         $paypal_args['cmd'] = '_xclick-subscriptions';
         // Store the subscription ID in the args sent to PayPal so we can access them later
         $paypal_args['custom'] = wcs_json_encode(array('order_id' => $order->id, 'order_key' => $order->order_key, 'subscription_id' => $subscription->id, 'subscription_key' => $subscription->order_key));
         foreach ($subscription->get_items() as $item) {
             if ($item['qty'] > 1) {
                 $item_names[] = $item['qty'] . ' x ' . wcs_get_paypal_item_name($item['name']);
             } elseif ($item['qty'] > 0) {
                 $item_names[] = wcs_get_paypal_item_name($item['name']);
             }
         }
         // translators: 1$: subscription ID, 2$: order ID, 3$: names of items, comma separated
         $paypal_args['item_name'] = wcs_get_paypal_item_name(sprintf(_x('Subscription %1$s (Order %2$s) - %3$s', 'item name sent to paypal', 'woocommerce-subscriptions'), $subscription->get_order_number(), $order->get_order_number(), implode(', ', $item_names)));
         $unconverted_periods = array('billing_period' => $subscription->billing_period, 'trial_period' => $subscription->trial_period);
         $converted_periods = array();
         // Convert period strings into PayPay's format
         foreach ($unconverted_periods as $key => $period) {
             switch (strtolower($period)) {
                 case 'day':
                     $converted_periods[$key] = 'D';
                     break;
                 case 'week':
                     $converted_periods[$key] = 'W';
                     break;
                 case 'year':
                     $converted_periods[$key] = 'Y';
                     break;
                 case 'month':
                 default:
                     $converted_periods[$key] = 'M';
                     break;
             }
         }
         $price_per_period = $subscription->get_total();
         $subscription_interval = $subscription->billing_interval;
         $start_timestamp = $subscription->get_time('start');
         $trial_end_timestamp = $subscription->get_time('trial_end');
         $next_payment_timestamp = $subscription->get_time('next_payment');
         $is_synced_subscription = WC_Subscriptions_Synchroniser::subscription_contains_synced_product($subscription->id);
         if ($is_synced_subscription) {
             $length_from_timestamp = $next_payment_timestamp;
         } elseif ($trial_end_timestamp > 0) {
             $length_from_timestamp = $trial_end_timestamp;
         } else {
             $length_from_timestamp = $start_timestamp;
         }
         $subscription_length = wcs_estimate_periods_between($length_from_timestamp, $subscription->get_time('end'), $subscription->billing_period);
         $subscription_installments = $subscription_length / $subscription_interval;
         $initial_payment = $is_payment_change ? 0 : $order->get_total();
         if ($order_contains_failed_renewal || $is_payment_change) {
             if ($is_payment_change) {
                 // Add a nonce to the order ID to avoid "This invoice has already been paid" error when changing payment method to PayPal when it was previously PayPal
                 $suffix = '-wcscpm-' . wp_create_nonce();
             } else {
                 // Failed renewal order, append a descriptor and renewal order's ID
                 $suffix = '-wcsfrp-' . $order->id;
             }
             // Change the 'invoice' and the 'custom' values to be for the original order (if there is one)
             if (false === $subscription->order) {
                 // No original order so we need to use the subscriptions values instead
                 $order_number = ltrim($subscription->get_order_number(), _x('#', 'hash before the order number. Used as a character to remove from the actual order number', 'woocommerce-subscriptions')) . '-subscription';
                 $order_id_key = array('order_id' => $subscription->id, 'order_key' => $subscription->order_key);
             } else {
                 $order_number = ltrim($subscription->order->get_order_number(), _x('#', 'hash before the order number. Used as a character to remove from the actual order number', 'woocommerce-subscriptions'));
                 $order_id_key = array('order_id' => $subscription->order->id, 'order_key' => $subscription->order->order_key);
             }
             $order_details = false !== $subscription->order ? $subscription->order : $subscription;
             // Set the invoice details to the original order's invoice but also append a special string and this renewal orders ID so that we can match it up as a failed renewal order payment later
             $paypal_args['invoice'] = WCS_PayPal::get_option('invoice_prefix') . $order_number . $suffix;
             $paypal_args['custom'] = wcs_json_encode(array_merge($order_id_key, array('subscription_id' => $subscription->id, 'subscription_key' => $subscription->order_key)));
         }
         if ($order_contains_failed_renewal) {
             $subscription_trial_length = 0;
             $subscription_installments = max($subscription_installments - $subscription->get_completed_payment_count(), 0);
             // If we're changing the payment date or switching subs, we need to set the trial period to the next payment date & installments to be the number of installments left
         } elseif ($is_payment_change || $is_synced_subscription) {
             $next_payment_timestamp = $subscription->get_time('next_payment');
             // When the subscription is on hold
             if (false != $next_payment_timestamp && !empty($next_payment_timestamp)) {
                 $trial_until = wcs_calculate_paypal_trial_periods_until($next_payment_timestamp);
                 $subscription_trial_length = $trial_until['first_trial_length'];
                 $converted_periods['trial_period'] = $trial_until['first_trial_period'];
                 $second_trial_length = $trial_until['second_trial_length'];
                 $second_trial_period = $trial_until['second_trial_period'];
             } else {
                 $subscription_trial_length = 0;
             }
             // If this is a payment change, we need to account for completed payments on the number of installments owing
             if ($is_payment_change && $subscription_length > 0) {
                 $subscription_installments = max($subscription_installments - $subscription->get_completed_payment_count(), 0);
             }
         } else {
             $subscription_trial_length = wcs_estimate_periods_between($start_timestamp, $trial_end_timestamp, $subscription->trial_period);
         }
         if ($subscription_trial_length > 0) {
             // Specify a free trial period
             $paypal_args['a1'] = $initial_payment > 0 ? $initial_payment : 0;
             // Trial period length
             $paypal_args['p1'] = $subscription_trial_length;
             // Trial period
             $paypal_args['t1'] = $converted_periods['trial_period'];
             // We need to use a second trial period before we have more than 90 days until the next payment
             if (isset($second_trial_length) && $second_trial_length > 0) {
                 $paypal_args['a2'] = 0.01;
                 // Alas, although it's undocumented, PayPal appears to require a non-zero value in order to allow a second trial period
                 $paypal_args['p2'] = $second_trial_length;
                 $paypal_args['t2'] = $second_trial_period;
             }
         } elseif ($initial_payment != $price_per_period) {
             // No trial period, but initial amount includes a sign-up fee and/or other items, so charge it as a separate period
             if (1 == $subscription_installments) {
                 $param_number = 3;
             } else {
                 $param_number = 1;
             }
             $paypal_args['a' . $param_number] = $initial_payment;
             // Sign Up interval
             $paypal_args['p' . $param_number] = $subscription_interval;
             // Sign Up unit of duration
             $paypal_args['t' . $param_number] = $converted_periods['billing_period'];
         }
         // We have a recurring payment
         if (!isset($param_number) || 1 == $param_number) {
             // Subscription price
             $paypal_args['a3'] = $price_per_period;
             // Subscription duration
             $paypal_args['p3'] = $subscription_interval;
             // Subscription period
             $paypal_args['t3'] = $converted_periods['billing_period'];
         }
         // Recurring payments
         if (1 == $subscription_installments || $initial_payment != $price_per_period && 0 == $subscription_trial_length && 2 == $subscription_installments) {
             // Non-recurring payments
             $paypal_args['src'] = 0;
         } else {
             $paypal_args['src'] = 1;
             if ($subscription_installments > 0) {
                 // An initial period is being used to charge a sign-up fee
                 if ($initial_payment != $price_per_period && 0 == $subscription_trial_length) {
                     $subscription_installments--;
                 }
                 $paypal_args['srt'] = $subscription_installments;
             }
         }
         // Don't reattempt failed payments, instead let Subscriptions handle the failed payment
         $paypal_args['sra'] = 0;
         // Force return URL so that order description & instructions display
         $paypal_args['rm'] = 2;
         // Reattach the filter we removed earlier
         if ($is_payment_change) {
             add_filter('woocommerce_order_amount_total', 'WC_Subscriptions_Change_Payment_Gateway::maybe_zero_total', 11, 2);
         }
     }
     return $paypal_args;
 }
 /**
  * Checks if the PayPal account has reference transactions setup
  *
  * Subscriptions keeps a record of all accounts where reference transactions were found to be enabled just in case the
  * store manager switches to and from accounts. This record is stored as a JSON encoded array in the options table.
  *
  * @since 2.0
  */
 public static function are_reference_transactions_enabled($bypass_cache = '')
 {
     $api_username = self::get_option('api_username');
     $transient_key = 'wcs_paypal_rt_enabled';
     $reference_transactions_enabled = false;
     if (self::are_credentials_set()) {
         $accounts_with_reference_transactions_enabled = json_decode(get_option('wcs_paypal_rt_enabled_accounts', wcs_json_encode(array())));
         if (in_array($api_username, $accounts_with_reference_transactions_enabled)) {
             $reference_transactions_enabled = true;
         } elseif ('bypass_cache' === $bypass_cache || get_transient($transient_key) !== $api_username) {
             if (self::get_api()->are_reference_transactions_enabled()) {
                 $accounts_with_reference_transactions_enabled[] = $api_username;
                 update_option('wcs_paypal_rt_enabled_accounts', wcs_json_encode($accounts_with_reference_transactions_enabled));
                 $reference_transactions_enabled = true;
             } else {
                 set_transient($transient_key, $api_username, DAY_IN_SECONDS);
             }
         }
     }
     return apply_filters('wooocommerce_subscriptions_paypal_reference_transactions_enabled', $reference_transactions_enabled);
 }
 /**
  * Processes an ajax request to change a subscription's next payment date.
  *
  * Deprecated because editing a subscription's next payment date is now done from the Edit Subscription screen.
  *
  * @since 1.2
  * @deprecated 2.0
  */
 public static function ajax_update_next_payment_date()
 {
     _deprecated_function(__METHOD__, '2.0', 'wp_delete_post()');
     $response = array('status' => 'error');
     if (!wp_verify_nonce($_POST['wcs_nonce'], 'woocommerce-subscriptions')) {
         $response['message'] = '<div class="error">' . __('Invalid security token, please reload the page and try again.', 'woocommerce-subscriptions') . '</div>';
     } elseif (!current_user_can('manage_woocommerce')) {
         $response['message'] = '<div class="error">' . __('Only store managers can edit payment dates.', 'woocommerce-subscriptions') . '</div>';
     } elseif (empty($_POST['wcs_day']) || empty($_POST['wcs_month']) || empty($_POST['wcs_year'])) {
         $response['message'] = '<div class="error">' . __('Please enter all date fields.', 'woocommerce-subscriptions') . '</div>';
     } else {
         $new_payment_date = sprintf('%s-%s-%s %s', (int) $_POST['wcs_year'], zeroise((int) $_POST['wcs_month'], 2), zeroise((int) $_POST['wcs_day'], 2), date('H:i:s', current_time('timestamp')));
         $new_payment_timestamp = self::update_next_payment_date($new_payment_date, $_POST['wcs_subscription_key'], self::get_user_id_from_subscription_key($_POST['wcs_subscription_key']), 'user');
         if (is_wp_error($new_payment_timestamp)) {
             $response['message'] = sprintf('<div class="error">%s</div>', $new_payment_timestamp->get_error_message());
         } else {
             $new_payment_timestamp_user_time = $new_payment_timestamp + get_option('gmt_offset') * 3600;
             // The timestamp is returned in server time
             $time_diff = $new_payment_timestamp - gmdate('U');
             if ($time_diff > 0 && $time_diff < 7 * 24 * 60 * 60) {
                 // translators: placeholder is human time diff (e.g. "3 weeks")
                 $date_to_display = sprintf(__('In %s', 'woocommerce-subscriptions'), human_time_diff(gmdate('U'), $new_payment_timestamp));
             } else {
                 $date_to_display = date_i18n(wc_date_format(), $new_payment_timestamp_user_time);
             }
             $response['status'] = 'success';
             $response['message'] = '<div class="updated">' . __('Date Changed', 'woocommerce-subscriptions') . '</div>';
             $response['dateToDisplay'] = $date_to_display;
             $response['timestamp'] = $new_payment_timestamp_user_time;
         }
     }
     echo wcs_json_encode($response);
     exit;
 }
 /**
  * Migrate the trial expiration, next payment and expiration/end dates to a new subscription.
  *
  * @since 2.0
  */
 private static function migrate_dates($new_subscription, $old_subscription)
 {
     global $wpdb;
     $dates_to_update = array();
     // old hook => new hook
     $date_keys = array('trial_end' => array('old_subscription_key' => 'trial_expiry_date', 'old_scheduled_hook' => 'scheduled_subscription_trial_end'), 'end' => array('old_subscription_key' => 'expiry_date', 'old_scheduled_hook' => 'scheduled_subscription_expiration'), 'end_date' => array('old_subscription_key' => '_subscription_end_date', 'old_scheduled_hook' => ''), 'next_payment' => array('old_subscription_key' => '', 'old_scheduled_hook' => 'scheduled_subscription_payment'), 'end_of_prepaid_term' => array('old_subscription_key' => '', 'old_scheduled_hook' => 'scheduled_subscription_end_of_prepaid_term'));
     $old_hook_args = array('user_id' => $old_subscription['user_id'], 'subscription_key' => $old_subscription['subscription_key']);
     foreach ($date_keys as $new_key => $old_keys) {
         // First check if there is a date stored on the subscription, and if so, use that
         if (!empty($old_keys['old_subscription_key']) && (isset($old_subscription[$old_keys['old_subscription_key']]) && 0 !== $old_subscription[$old_keys['old_subscription_key']])) {
             $dates_to_update[$new_key] = $old_subscription[$old_keys['old_subscription_key']];
         } elseif (!empty($old_keys['old_scheduled_hook'])) {
             // Now check if there is a scheduled date, this is for next payment and end of prepaid term dates
             $next_scheduled = wc_next_scheduled_action($old_keys['old_scheduled_hook'], $old_hook_args);
             if ($next_scheduled > 0) {
                 if ('end_of_prepaid_term' == $new_key) {
                     wc_schedule_single_action($next_scheduled, 'woocommerce_scheduled_subscription_end_of_prepaid_term', array('subscription_id' => $new_subscription->id));
                 } else {
                     $dates_to_update[$new_key] = date('Y-m-d H:i:s', $next_scheduled);
                 }
             }
         }
     }
     // Trash all the hooks in one go to save write requests
     $wpdb->update($wpdb->posts, array('post_status' => 'trash'), array('post_type' => ActionScheduler_wpPostStore::POST_TYPE, 'post_content' => wcs_json_encode($old_hook_args)), array('%s', '%s'));
     $dates_to_update['start'] = $new_subscription->post->post_date_gmt;
     // v2.0 enforces new rules for dates when they are being set, so we need to massage the old data to conform to these new rules
     foreach ($dates_to_update as $date_type => $date) {
         if (0 == $date) {
             continue;
         }
         switch ($date_type) {
             case 'end':
                 if (array_key_exists('next_payment', $dates_to_update) && $date <= $dates_to_update['next_payment']) {
                     $dates_to_update[$date_type] = $date;
                 }
             case 'next_payment':
                 if (array_key_exists('trial_end', $dates_to_update) && $date < $dates_to_update['trial_end']) {
                     $dates_to_update[$date_type] = $date;
                 }
             case 'trial_end':
                 if (array_key_exists('start', $dates_to_update) && $date <= $dates_to_update['start']) {
                     $dates_to_update[$date_type] = $date;
                 }
         }
     }
     try {
         if (!empty($dates_to_update)) {
             $new_subscription->update_dates($dates_to_update);
         }
         WCS_Upgrade_Logger::add(sprintf('For subscription %d: updated dates = %s', $new_subscription->id, str_replace(array('{', '}', '"'), '', wcs_json_encode($dates_to_update))));
     } catch (Exception $e) {
         WCS_Upgrade_Logger::add(sprintf('For subscription %d: unable to update dates, exception "%s"', $new_subscription->id, $e->getMessage()));
     }
 }
 /**
  * Because the upgrader may have attempted to set an invalid end date on the subscription, it could
  * lead to the entire date update process failing, which would mean that a next payment date would
  * not be set even when one existed.
  *
  * This method checks if a given subscription has no next payment date, and if it doesn't, it checks
  * if one was previously scheduled for the old subscription. If one was, and that date is in the future,
  * it will pass that date back for being set on the subscription. If a date was scheduled but that is now
  * in the past, it will recalculate it.
  *
  * @param  WC_Subscription $subscription the subscription to check
  * @return string|bool false if the date does not need to be repaired or the new date if it should be repaired
  */
 protected static function check_next_payment_date($subscription)
 {
     global $wpdb;
     // the subscription doesn't have a next payment date set, let's see if it should
     if (0 == $subscription->get_time('next_payment') && $subscription->has_status('active')) {
         $old_hook_args = array('user_id' => (int) $subscription->get_user_id(), 'subscription_key' => wcs_get_old_subscription_key($subscription));
         // get the latest scheduled subscription payment in v1.5
         $old_next_payment_date = $wpdb->get_var($wpdb->prepare("SELECT post_date_gmt FROM {$wpdb->posts}\n\t\t\t\t WHERE post_type = %s\n\t\t\t\t AND post_content = %s\n\t\t\t\t AND post_title = 'scheduled_subscription_payment'\n\t\t\t\t ORDER BY post_date_gmt DESC", ActionScheduler_wpPostStore::POST_TYPE, wcs_json_encode($old_hook_args)));
         WCS_Upgrade_Logger::add(sprintf('For subscription %d: new next payment date = %s.', $subscription->id, var_export($subscription->get_date('next_payment'), true)));
         WCS_Upgrade_Logger::add(sprintf('For subscription %d: old next payment date = %s.', $subscription->id, var_export($old_next_payment_date, true)));
         // if we have a date, make sure it's valid
         if (null !== $old_next_payment_date) {
             if (strtotime($old_next_payment_date) <= gmdate('U')) {
                 $repair_date = $subscription->calculate_date('next_payment');
                 if (0 == $repair_date) {
                     $repair_date = false;
                 }
                 WCS_Upgrade_Logger::add(sprintf('For subscription %d: old next payment date is in the past, setting it to %s.', $subscription->id, var_export($repair_date, true)));
             } else {
                 $repair_date = $old_next_payment_date;
             }
         } else {
             // let's just double check we shouldn't have a date set by recalculating it
             $calculated_next_payment_date = $subscription->calculate_date('next_payment');
             if (0 != $calculated_next_payment_date && strtotime($calculated_next_payment_date) > gmdate('U')) {
                 $repair_date = $calculated_next_payment_date;
             } else {
                 $repair_date = false;
             }
             WCS_Upgrade_Logger::add(sprintf('For subscription %d: no old next payment date, setting it to %s.', $subscription->id, var_export($repair_date, true)));
         }
     } else {
         $repair_date = false;
     }
     WCS_Upgrade_Logger::add(sprintf('For subscription %d: repair next payment date = %s.', $subscription->id, var_export($repair_date, true)));
     return $repair_date;
 }
}
?>
<div class="wc-metaboxes-wrapper">

	<div id="billing-schedule">
		<?php 
if ($the_subscription->can_date_be_updated('next_payment')) {
    ?>
		<div class="billing-schedule-edit wcs-date-input"><?php 
    // Subscription Period Interval
    echo woocommerce_wp_select(array('id' => '_billing_interval', 'class' => 'billing_interval', 'label' => __('Recurring:', 'woocommerce-subscriptions'), 'value' => empty($the_subscription->billing_interval) ? 1 : $the_subscription->billing_interval, 'options' => wcs_get_subscription_period_interval_strings()));
    // Billing Period
    echo woocommerce_wp_select(array('id' => '_billing_period', 'class' => 'billing_period', 'label' => __('Billing Period', 'woocommerce-subscriptions'), 'value' => empty($the_subscription->billing_period) ? 'month' : $the_subscription->billing_period, 'options' => wcs_get_subscription_period_strings()));
    ?>
			<input type="hidden" name="wcs-lengths" id="wcs-lengths" data-subscription_lengths="<?php 
    echo esc_attr(wcs_json_encode(wcs_get_subscription_ranges()));
    ?>
">
		</div>
		<?php 
} else {
    ?>
		<strong><?php 
    esc_html_e('Recurring:', 'woocommerce-subscriptions');
    ?>
</strong>
		<?php 
    printf('%s %s', esc_html(wcs_get_subscription_period_interval_strings($the_subscription->billing_interval)), esc_html(wcs_get_subscription_period_strings(1, $the_subscription->billing_period)));
    ?>
	<?php 
}
 /**
  * Move scheduled subscription hooks out of wp-cron and into the new Action Scheduler.
  *
  * Also set all existing subscriptions to "sold individually" to maintain previous behavior
  * for existing subscription products before the subscription quantities feature was enabled..
  *
  * @since 1.5
  */
 public static function ajax_upgrade()
 {
     global $wpdb;
     check_admin_referer('wcs_upgrade_process', 'nonce');
     self::set_upgrade_limits();
     WCS_Upgrade_Logger::add(sprintf('Starting upgrade step: %s', $_POST['upgrade_step']));
     if (ini_get('max_execution_time') < 600) {
         @set_time_limit(600);
     }
     @ini_set('memory_limit', apply_filters('admin_memory_limit', WP_MAX_MEMORY_LIMIT));
     update_option('wc_subscriptions_is_upgrading', gmdate('U') + 60 * 2);
     switch ($_POST['upgrade_step']) {
         case 'really_old_version':
             $upgraded_versions = self::upgrade_really_old_versions();
             $results = array('message' => sprintf(__('Database updated to version %s', 'woocommerce-subscriptions'), $upgraded_versions));
             break;
         case 'products':
             require_once 'class-wcs-upgrade-1-5.php';
             $upgraded_product_count = WCS_Upgrade_1_5::upgrade_products();
             $results = array('message' => sprintf(_x('Marked %s subscription products as "sold individually".', 'used in the subscriptions upgrader', 'woocommerce-subscriptions'), $upgraded_product_count));
             break;
         case 'hooks':
             require_once 'class-wcs-upgrade-1-5.php';
             $upgraded_hook_count = WCS_Upgrade_1_5::upgrade_hooks(self::$upgrade_limit_hooks);
             $results = array('upgraded_count' => $upgraded_hook_count, 'message' => sprintf(__('Migrated %1$s subscription related hooks to the new scheduler (in %2$s seconds).', 'woocommerce-subscriptions'), $upgraded_hook_count, '{execution_time}'));
             break;
         case 'subscriptions':
             require_once 'class-wcs-repair-2-0.php';
             require_once 'class-wcs-upgrade-2-0.php';
             try {
                 $upgraded_subscriptions = WCS_Upgrade_2_0::upgrade_subscriptions(self::$upgrade_limit_subscriptions);
                 $results = array('upgraded_count' => $upgraded_subscriptions, 'message' => sprintf(__('Migrated %1$s subscriptions to the new structure (in %2$s seconds).', 'woocommerce-subscriptions'), $upgraded_subscriptions, '{execution_time}'), 'status' => 'success', 'time_message' => sprintf(_x('Estimated time left (minutes:seconds): %s', 'Message that gets sent to front end.', 'woocommerce-subscriptions'), '{time_left}'));
             } catch (Exception $e) {
                 WCS_Upgrade_Logger::add(sprintf('Error on upgrade step: %s. Error: %s', $_POST['upgrade_step'], $e->getMessage()));
                 $results = array('upgraded_count' => 0, 'message' => sprintf(__('Unable to upgrade subscriptions.<br/>Error: %1$s<br/>Please refresh the page and try again. If problem persists, %2$scontact support%3$s.', 'woocommerce-subscriptions'), '<code>' . $e->getMessage() . '</code>', '<a href="' . esc_url('https://woothemes.com/my-account/create-a-ticket/') . '">', '</a>'), 'status' => 'error');
             }
             break;
         case 'subscription_dates_repair':
             require_once 'class-wcs-upgrade-2-0.php';
             require_once 'class-wcs-repair-2-0-2.php';
             $subscription_ids_to_repair = WCS_Repair_2_0_2::get_subscriptions_to_repair(self::$upgrade_limit_subscriptions);
             try {
                 $subscription_counts = WCS_Repair_2_0_2::maybe_repair_subscriptions($subscription_ids_to_repair);
                 // translators: placeholder is the number of subscriptions repaired
                 $repair_incorrect = sprintf(_x('Repaired %d subscriptions with incorrect dates, line tax data or missing customer notes.', 'Repair message that gets sent to front end.', 'woocommerce-subscriptions'), $subscription_counts['repaired_count']);
                 $repair_not_needed = '';
                 if ($subscription_counts['unrepaired_count'] > 0) {
                     // translators: placeholder is number of subscriptions that were checked and did not need repairs. There's a space at the beginning!
                     $repair_not_needed = sprintf(_nx(' %d other subscription was checked and did not need any repairs.', '%d other subscriptions were checked and did not need any repairs.', $subscription_counts['unrepaired_count'], 'Repair message that gets sent to front end.', 'woocommerce-subscriptions'), $subscription_counts['unrepaired_count']);
                 }
                 // translators: placeholder is "{execution_time}", which will be replaced on front end with actual time
                 $repair_time = sprintf(_x('(in %s seconds)', 'Repair message that gets sent to front end.', 'woocommerce-subscriptions'), '{execution_time}');
                 // translators: $1: "Repaired x subs with incorrect dates...", $2: "X others were checked and no repair needed", $3: "(in X seconds)". Ordering for RTL languages.
                 $repair_message = sprintf(_x('%1$s%2$s %3$s', 'The assembled repair message that gets sent to front end.', 'woocommerce-subscriptions'), $repair_incorrect, $repair_not_needed, $repair_time);
                 $results = array('repaired_count' => $subscription_counts['repaired_count'], 'unrepaired_count' => $subscription_counts['unrepaired_count'], 'message' => $repair_message, 'status' => 'success', 'time_message' => sprintf(_x('Estimated time left (minutes:seconds): %s', 'Message that gets sent to front end.', 'woocommerce-subscriptions'), '{time_left}'));
             } catch (Exception $e) {
                 WCS_Upgrade_Logger::add(sprintf('Error on upgrade step: %s. Error: %s', $_POST['upgrade_step'], $e->getMessage()));
                 $results = array('repaired_count' => 0, 'unrepaired_count' => 0, 'message' => sprintf(_x('Unable to repair subscriptions.<br/>Error: %1$s<br/>Please refresh the page and try again. If problem persists, %2$scontact support%3$s.', 'Error message that gets sent to front end when upgrading Subscriptions', 'woocommerce-subscriptions'), '<code>' . $e->getMessage() . '</code>', '<a href="' . esc_url('https://woothemes.com/my-account/create-a-ticket/') . '">', '</a>'), 'status' => 'error');
             }
             break;
     }
     if ('subscriptions' == $_POST['upgrade_step'] && 0 === self::get_total_subscription_count_query()) {
         self::upgrade_complete();
     } elseif ('subscription_dates_repair' == $_POST['upgrade_step']) {
         $subscriptions_to_repair = WCS_Repair_2_0_2::get_subscriptions_to_repair(self::$upgrade_limit_subscriptions);
         if (empty($subscriptions_to_repair)) {
             self::upgrade_complete();
         }
     }
     WCS_Upgrade_Logger::add(sprintf('Completed upgrade step: %s', $_POST['upgrade_step']));
     header('Content-Type: application/json; charset=utf-8');
     echo wcs_json_encode($results);
     exit;
 }
 /**
  * Charge a payment against a reference token
  *
  * @link https://developer.paypal.com/docs/classic/express-checkout/integration-guide/ECReferenceTxns/#id094UM0DA0HS
  * @link https://developer.paypal.com/docs/classic/api/merchant/DoReferenceTransaction_API_Operation_NVP/
  *
  * @param string $reference_id the ID of a refrence object, e.g. billing agreement ID.
  * @param WC_Order $order order object
  * @param array $args {
  *     @type string 'payment_type'         (Optional) Specifies type of PayPal payment you require for the billing agreement. It is one of the following values. 'Any' or 'InstantOnly'. Echeck is not supported for DoReferenceTransaction requests.
  *     @type string 'payment_action'       How you want to obtain payment. It is one of the following values: 'Authorization' - this payment is a basic authorization subject to settlement with PayPal Authorization and Capture; or 'Sale' - This is a final sale for which you are requesting payment.
  *     @type string 'return_fraud_filters' (Optional) Flag to indicate whether you want the results returned by Fraud Management Filters. By default, you do not receive this information.
  * }
  * @since 2.0
  */
 public function do_reference_transaction($reference_id, $order, $args = array())
 {
     $defaults = array('amount' => $order->get_total(), 'payment_type' => 'Any', 'payment_action' => 'Sale', 'return_fraud_filters' => 1, 'notify_url' => WC()->api_request_url('WC_Gateway_Paypal'), 'invoice_number' => WCS_PayPal::get_option('invoice_prefix') . wcs_str_to_ascii(ltrim($order->get_order_number(), _x('#', 'hash before the order number. Used as a character to remove from the actual order number', 'woocommerce-subscriptions'))), 'custom' => wcs_json_encode(array('order_id' => $order->id, 'order_key' => $order->order_key)));
     $args = wp_parse_args($args, $defaults);
     $this->set_method('DoReferenceTransaction');
     // set base params
     $this->add_parameters(array('REFERENCEID' => $reference_id, 'BUTTONSOURCE' => 'WooThemes_Cart', 'RETURNFMFDETAILS' => $args['return_fraud_filters'], 'NOTIFYURL' => $args['notify_url']));
     $this->add_payment_details_parameters($order, $args['payment_action'], true);
 }