function paymill_webhooks() { global $wpdb; // is there a webhook from Paymill? if (class_exists('WC_Subscriptions_Manager')) { // grab data from webhook $body = @file_get_contents('php://input'); $event_json = json_decode($body, true); // retrieve sub ID if (isset($event_json['event']['event_resource']['id']) && strlen($event_json['event']['event_resource']['id']) > 0) { $paymill_sub_id = $event_json['event']['event_resource']['id']; } elseif (isset($event_json['event']['event_resource']['subscription']['id']) && strlen($event_json['event']['event_resource']['subscription']['id']) > 0) { $paymill_sub_id = $event_json['event']['event_resource']['subscription']['id']; } error_log("\n\n########################################################################################################################\n\n", 3, PAYMILL_DIR . 'lib/debug/PHP_errors.log'); error_log(date(DATE_RFC822) . ' - Webhook ' . $event_json['event']['event_type'] . ' (Resource-ID: ' . $paymill_sub_id . ') triggered - start processing' . "\n\n", 3, PAYMILL_DIR . 'lib/debug/PHP_errors.log'); /* output example: array(1) { ["event"]=> array(4) { ["event_type"]=> string(20) "subscription.deleted" ["event_resource"]=> array(13) { ["id"]=> string(24) "sub_b71adbf5....." ["offer"]=> array(10) { ["id"]=> string(26) "offer_8083a5b....." ["name"]=> string(39) "woo_91_73da6....." ["amount"]=> int(100) ["currency"]=> string(3) "EUR" ["interval"]=> string(5) "1 DAY" ["trial_period_days"]=> int(0) ["created_at"]=> int(1389547028) ["updated_at"]=> int(1389547028) ["subscription_count"]=> array(2) { ["active"]=> string(1) "1" ["inactive"]=> string(1) "1" } ["app_id"]=> NULL } ["livemode"]=> bool(false) ["cancel_at_period_end"]=> bool(false) ["trial_start"]=> NULL ["trial_end"]=> NULL ["next_capture_at"]=> int(1389836717) ["created_at"]=> int(1389663382) ["updated_at"]=> int(1389750317) ["canceled_at"]=> NULL ["app_id"]=> NULL ["payment"]=> array(12) { ["id"]=> string(28) "pay_4e3759f....." ["type"]=> string(10) "creditcard" ["client"]=> string(27) "client_dbe164....." ["card_type"]=> string(4) "visa" ["country"]=> NULL ["expire_month"]=> string(2) "12" ["expire_year"]=> string(4) "2020" ["card_holder"]=> string(13) "dfgdfgdfgdfgd" ["last4"]=> string(4) "1111" ["created_at"]=> int(1389663369) ["updated_at"]=> int(1389663380) ["app_id"]=> NULL } ["client"]=> array(8) { ["id"]=> string(27) "client_dbe164....." ["email"]=> string(22) "*****@*****.**" ["description"]=> string(15) "Matthias Reuter" ["created_at"]=> int(1389547027) ["updated_at"]=> int(1389547027) ["app_id"]=> NULL ["payment"]=> array(2) { [0]=> array(12) { ["id"]=> string(28) "pay_1a5ff8....." ["type"]=> string(10) "creditcard" ["client"]=> string(27) "client_dbe16....." ["card_type"]=> string(4) "visa" ["country"]=> NULL ["expire_month"]=> string(2) "12" ["expire_year"]=> string(4) "2020" ["card_holder"]=> string(10) "dfgdfgdfgd" ["last4"]=> string(4) "1111" ["created_at"]=> int(1389547027) ["updated_at"]=> int(1389547028) ["app_id"]=> NULL } [1]=> array(12) { ["id"]=> string(28) "pay_4e375....." ["type"]=> string(10) "creditcard" ["client"]=> string(27) "client_dbe164....." ["card_type"]=> string(4) "visa" ["country"]=> NULL ["expire_month"]=> string(2) "12" ["expire_year"]=> string(4) "2020" ["card_holder"]=> string(13) "dfgdfgdfgdfgd" ["last4"]=> string(4) "1111" ["created_at"]=> int(1389663369) ["updated_at"]=> int(1389663380) ["app_id"]=> NULL } } ["subscription"]=> array(2) { [0]=> string(24) "sub_fcc4....." [1]=> string(24) "sub_b71a....." } } } ["created_at"]=> int(1389816435) ["app_id"]=> NULL } } */ //error_log(var_export($event_json,true)."\n\n", 3, PAYMILL_DIR.'lib/debug/PHP_errors.log'); // get subscription info, if available if (isset($paymill_sub_id) && strlen($paymill_sub_id) > 0) { $sql = $wpdb->prepare('SELECT * FROM ' . $wpdb->prefix . 'paymill_subscriptions WHERE paymill_sub_id=%s', array($paymill_sub_id)); $sub_cache = $wpdb->get_results($sql, ARRAY_A); $sub_cache = $sub_cache[0]; /* output example: SELECT * FROM wp_paymill_subscriptions WHERE paymill_sub_id="sub_b71adbf5e097bbe5ba80" */ error_log("\n\n" . $sql . "\n\n", 3, PAYMILL_DIR . 'lib/debug/PHP_errors.log'); /* output example: 1 30 */ //error_log($sub_cache['woo_user_id']."\n\n".$sub_cache['woo_offer_id']."\n\n", 3, PAYMILL_DIR.'lib/debug/PHP_errors.log'); $subscription = WC_Subscriptions_Manager::get_subscription($sub_cache['woo_offer_id']); // update subscriptions when webhook is triggered if (isset($sub_cache['woo_offer_id']) && strlen($sub_cache['woo_offer_id']) > 0) { // subscription successfully created if ($event_json['event']['event_type'] == 'subscription.created') { } // tell WooCommerce when payment for subscription is successfully processed if ($event_json['event']['event_type'] == 'subscription.succeeded') { /* example data WC_Subscriptions_Manager::get_subscription: array(15) { ["order_id"]=> string(3) "201" ["product_id"]=> string(2) "91" ["variation_id"]=> string(0) "" ["status"]=> string(6) "active" ["period"]=> string(3) "day" ["interval"]=> string(1) "1" ["length"]=> string(2) "12" ["start_date"]=> string(19) "2014-01-12 17:17:10" ["expiry_date"]=> string(19) "2014-01-24 17:17:10" ["end_date"]=> string(1) "0" ["trial_expiry_date"]=> string(1) "0" ["failed_payments"]=> string(1) "0" ["completed_payments"]=> array(1) { [0]=> string(19) "2014-01-12 17:17:10" } ["suspension_count"]=> string(1) "0" ["last_payment_date"]=> string(19) "2014-01-12 17:17:10" } */ error_log(var_export($subscription, true) . "\n\n", 3, PAYMILL_DIR . 'lib/debug/PHP_errors.log'); // prevent multiple subscription renewals because of multiple webhook attempts. $whole_period = 0; switch ($subscription['period']) { case 'day': default: $whole_period = intval($subscription['interval']) * 86400; break; case 'week': $whole_period = intval($subscription['interval']) * 604800; break; case 'month': $whole_period = intval($subscription['interval']) * 2160000; // using 25 days to prevent problems with shorter months break; case 'year': $whole_period = intval($subscription['interval']) * 30240000; // using 350 days to prevent any timezone problems whatsoever break; } if (count($subscription['completed_payments']) >= 1) { if (strtotime(date(DATE_RFC822)) > strtotime($subscription['last_payment_date']) + $whole_period - 18000) { // minus 5 hours to prevent any problems with pending triggers $order = new WC_Order($subscription['order_id']); //WC_Subscriptions_Manager::process_subscription_payments_on_order($order, $subscription['product_id']); WC_Subscriptions_Manager::process_subscription_payments_on_order($order); } } else { $order = new WC_Order($subscription['order_id']); $order->payment_complete(); WC_Subscriptions_Manager::activate_subscriptions_for_order($subscription['order_id']); } WC_Subscriptions_Manager::set_next_payment_date($sub_cache['woo_offer_id'], $order->customer_user); } // cancel subscription, as it was deleted through Paymill dashboard if ($event_json['event']['event_type'] == 'subscription.deleted') { $sql = $wpdb->prepare('DELETE FROM ' . $wpdb->prefix . 'paymill_subscriptions WHERE woo_user_id=%s AND woo_offer_id=%s', array($sub_cache['woo_user_id'], $sub_cache['woo_offer_id'])); $wpdb->query($sql); error_log("\n\n" . $sql . "\n\n", 3, PAYMILL_DIR . 'lib/debug/PHP_errors.log'); //WC_Subscriptions_Manager::cancel_subscriptions_for_order( $order ); WC_Subscriptions_Manager::cancel_subscription($sub_cache['woo_user_id'], $sub_cache['woo_offer_id']); } // tell WC that payment failure occured if ($event_json['event']['event_type'] == 'subscription.failed') { WC_Subscriptions_Manager::process_subscription_payment_failure_on_order($subscription['order_id'], $subscription['product_id']); } } } error_log(date(DATE_RFC822) . ' - Webhook ' . $event_json['event']['event_type'] . ' finished - end processing' . "\n\n", 3, PAYMILL_DIR . 'lib/debug/PHP_errors.log'); error_log("\n\n########################################################################################################################\n\n", 3, PAYMILL_DIR . 'lib/debug/PHP_errors.log'); } }
/** * 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'))) { 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; } if ('paypal' != $order->recurring_payment_method) { 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: This IPN 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: This transaction 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']); } // 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']); $default_invoice_string = self::$paypal_settings['invoice_prefix'] . ltrim($order->get_order_number(), '#'); // 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 ($default_invoice_string != $transaction_details['invoice'] && false !== strpos($transaction_details['invoice'], '-wcscpm-')) { $is_payment_change = true; } else { $is_payment_change = false; } $switched_subscription_key = get_post_meta($order_id, '_switched_subscription_key', true); $no_initial_payment = 0 == WC_Subscriptions_Order::get_total_initial_payment($order) && WC_Subscriptions_Order::get_subscription_trial_length($order) > 0 ? 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 && (!empty($switched_subscription_key) || $no_initial_payment)) { $order->payment_complete(); } // Payment completed if ($is_payment_change) { $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) { $profile_id = get_post_meta($order->id, '_old_paypal_subscriber_id', true); self::cancel_subscription_with_paypal($order, $product_id, $profile_id); } $order->add_order_note(__('IPN subscription payment method changed.', '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, '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(); 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)) { 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); WC_Subscriptions_Manager::process_subscription_payments_on_order($order); // Make sure the next payment date is sync 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 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; }
/** * Work around a bug in WooCommerce which ignores order item meta values of 0. * * Code in this function is identical to a section of the @see woocommerce_process_shop_order_meta() function, except * that it doesn't include the bug which ignores item meta with a 0 value. * * @param int $post_id The ID of the post which is the WC_Order object. * @param Object $post The post object of the order. * @since 1.2.4 */ public static function process_shop_order_item_meta($post_id, $post) { global $woocommerce; $product_ids = array(); if (isset($_POST['order_item_id'])) { foreach ($_POST['order_item_id'] as $order_item_id) { // WC 2.0+ has unique order item IDs and the product ID is a piece of meta $product_ids[$order_item_id] = woocommerce_get_order_item_meta($order_item_id, '_product_id'); } } // Now that meta has been updated, we can update the schedules (if there were any changes to schedule related meta) if (!empty($product_ids)) { $user_id = (int) $_POST['customer_user']; foreach ($product_ids as $product_id) { $subscription_key = WC_Subscriptions_Manager::get_subscription_key($post_id, $product_id); // Order is important here, expiration date takes into account trial expriation date and next payment date takes into account expiration date if (in_array($product_id, self::$requires_update['trial_expiration'])) { WC_Subscriptions_Manager::set_trial_expiration_date($subscription_key, $user_id); } if (in_array($product_id, self::$requires_update['expiration_date'])) { WC_Subscriptions_Manager::set_expiration_date($subscription_key, $user_id); } if (in_array($product_id, self::$requires_update['next_billing_date'])) { WC_Subscriptions_Manager::set_next_payment_date($subscription_key, $user_id); } } } }
/** * Work around a bug in WooCommerce which ignores order item meta values of 0. * * Code in this function is identical to a section of the @see woocommerce_process_shop_order_meta() function, except * that it doesn't include the bug which ignores item meta with a 0 value. * * @param $post_id int The ID of the post which is the WC_Order object. * @param $post Object The post object of the order. * @since 1.2.4 */ public static function process_shop_order_item_meta($post_id, $post) { global $woocommerce; // Only needs to function on WC 1.x where the bug existed if (isset($_POST['item_id'])) { $order_items = array(); $item_id = $_POST['item_id']; $item_variation = $_POST['item_variation']; $item_name = $_POST['item_name']; $item_quantity = $_POST['item_quantity']; $line_subtotal = $_POST['line_subtotal']; $line_subtotal_tax = $_POST['line_subtotal_tax']; $line_total = $_POST['line_total']; $line_tax = $_POST['line_tax']; $item_meta_names = isset($_POST['meta_name']) ? $_POST['meta_name'] : ''; $item_meta_values = isset($_POST['meta_value']) ? $_POST['meta_value'] : ''; $item_tax_class = $_POST['item_tax_class']; $item_id_count = sizeof($item_id); for ($i = 0; $i < $item_id_count; $i++) { if (!isset($item_id[$i]) || !$item_id[$i]) { continue; } if (!isset($item_name[$i])) { continue; } if (!isset($item_quantity[$i]) || $item_quantity[$i] < 1) { continue; } if (!isset($line_total[$i])) { continue; } if (!isset($line_tax[$i])) { continue; } // Meta $item_meta = new WC_Order_Item_Meta(); if (isset($item_meta_names[$i]) && isset($item_meta_values[$i])) { $meta_names = $item_meta_names[$i]; $meta_values = $item_meta_values[$i]; $meta_names_count = sizeof($meta_names); for ($ii = 0; $ii < $meta_names_count; $ii++) { $meta_name = esc_attr($meta_names[$ii]); $meta_value = esc_attr($meta_values[$ii]); if (isset($meta_name) && isset($meta_value)) { $item_meta->add($meta_name, $meta_value); } } } // Add to array $order_items[] = apply_filters('update_order_item', array('id' => htmlspecialchars(stripslashes($item_id[$i])), 'variation_id' => (int) $item_variation[$i], 'name' => htmlspecialchars(stripslashes($item_name[$i])), 'qty' => (int) $item_quantity[$i], 'line_total' => rtrim(rtrim(number_format(woocommerce_clean($line_total[$i]), 4, '.', ''), '0'), '.'), 'line_tax' => rtrim(rtrim(number_format(woocommerce_clean($line_tax[$i]), 4, '.', ''), '0'), '.'), 'line_subtotal' => rtrim(rtrim(number_format(woocommerce_clean($line_subtotal[$i]), 4, '.', ''), '0'), '.'), 'line_subtotal_tax' => rtrim(rtrim(number_format(woocommerce_clean($line_subtotal_tax[$i]), 4, '.', ''), '0'), '.'), 'item_meta' => $item_meta->meta, 'tax_class' => woocommerce_clean($item_tax_class[$i]))); } update_post_meta($post_id, '_order_items', $order_items); } // WC <> 2.0 compatible posted product IDs $product_ids = array(); if (isset($_POST['order_item_id'])) { foreach ($_POST['order_item_id'] as $order_item_id) { // WC 2.0+ has unique order item IDs and the product ID is a piece of meta $product_ids[$order_item_id] = woocommerce_get_order_item_meta($order_item_id, '_product_id'); } } elseif (isset($_POST['item_id'])) { $product_ids = $_POST['item_id']; } // WC 1.x treated order item IDs as product IDs // Now that meta has been updated, we can update the schedules (if there were any changes to schedule related meta) if (!empty($product_ids)) { $user_id = (int) $_POST['customer_user']; foreach ($product_ids as $product_id) { $subscription_key = WC_Subscriptions_Manager::get_subscription_key($post_id, $product_id); // Order is important here, expiration date takes into account trial expriation date and next payment date takes into account expiration date if (in_array($product_id, self::$requires_update['trial_expiration'])) { WC_Subscriptions_Manager::set_trial_expiration_date($subscription_key, $user_id); } if (in_array($product_id, self::$requires_update['expiration_date'])) { WC_Subscriptions_Manager::set_expiration_date($subscription_key, $user_id); } if (in_array($product_id, self::$requires_update['next_billing_date'])) { WC_Subscriptions_Manager::set_next_payment_date($subscription_key, $user_id); } } } }