/** * Customise which actions are shown against a subscriptions order on the My Account page. * * @since 1.3 */ public static function filter_woocommerce_my_account_my_orders_actions($actions, $order) { if (WC_Subscriptions_Order::order_contains_subscription($order) || WC_Subscriptions_Renewal_Order::is_renewal($order)) { unset($actions['cancel']); if (is_numeric(get_post_meta($order->id, '_failed_order_replaced_by', true))) { unset($actions['pay']); } $original_order = WC_Subscriptions_Renewal_Order::get_parent_order($order); $order_items = WC_Subscriptions_Order::get_recurring_items($original_order); $first_order_item = reset($order_items); $product_id = WC_Subscriptions_Order::get_items_product_id($first_order_item); $subscription_key = WC_Subscriptions_Manager::get_subscription_key($original_order->id, $product_id); $subscription = WC_Subscriptions_Manager::get_users_subscription($original_order->customer_user, $subscription_key); if (empty($subscription) || !in_array($subscription['status'], array('on-hold', 'pending'))) { unset($actions['pay']); } } return $actions; }
/** * In typical PayPal style, there are a couple of important limitations we need to work around: * * 1. PayPal does not support subscriptions with a $0 recurring total. As a result, we treat it * as a normal purchase and then handle the subscription renewals here. * * 2. PayPal make no guarantee about when a recurring payment will be charged. This creates issues for * suspending a subscription until the payment is processed. Specifically, if PayPal processed a payment * *before* it was due, we can't suspend the subscription when it is due because it will remain suspended * until the next payment. As a result, subscriptions for PayPal are not suspended. However, if there was * an issue with the subscription sign-up or payment that was not correctly reported to the store, then the * subscription would remain active. No renewal order would be generated, because no payments are completed, * so physical subscriptions would not be affected, however, subscriptions to digital goods would be affected. * * @since 1.4.3 */ public static function scheduled_subscription_payment($amount_to_charge, $order, $product_id) { if (0 == $amount_to_charge) { WC_Subscriptions_Manager::process_subscription_payments_on_order($order); } else { $hook_args = array('subscription_key' => WC_Subscriptions_Manager::get_subscription_key($order->id, $product_id)); $one_day_from_now = gmdate('U') + 60 * 60 * 24; wp_schedule_single_event($one_day_from_now, 'paypal_check_subscription_payment', $hook_args); } }
<?php /** * Update Data to 7.5 * Look for queue items for subscription emails without subscription keys in the meta */ if (!defined('ABSPATH')) { exit; // Exit if accessed directly } global $wpdb; if (class_exists('WC_Subscriptions_Manager')) { $email_ids = fue_get_emails('subscription', '', array('fields' => 'ids')); if (count($email_ids)) { $item_ids = $wpdb->get_results("SELECT id FROM {$wpdb->prefix}followup_email_orders WHERE email_id IN (" . implode(',', $email_ids) . ")"); foreach ($item_ids as $item_id) { $item = new FUE_Sending_Queue_Item($item_id->id); if (empty($item->meta['subs_key']) && WC_Subscriptions_Order::order_contains_subscription($item->order_id)) { $subs_key = WC_Subscriptions_Manager::get_subscription_key($item->order_id); $item->meta['subs_key'] = $subs_key; $item->save(); } } } }
/** * A general purpose function for grabbing an array of subscriptions in form of 'subscription_key' => 'subscription_details'. * * The $args param is based on the parameter of the same name used by the core WordPress @see get_posts() function. * It can be used to choose which subscriptions should be returned by the function, how many subscriptions shoudl be returned * and in what order those subscriptions should be returned. * * @param array $args A set of name value pairs to determine the return value. * 'subscriptions_per_page' The number of subscriptions to return. Set to -1 for unlimited. Default 10. * 'offset' An optional number of subscription to displace or pass over. Default 0. * 'orderby' The field which the subscriptions should be orderd by. Can be 'start_date', 'expiry_date', 'end_date', 'status', 'name' or 'order_id'. Defaults to 'start_date'. * 'order' The order of the values returned. Can be 'ASC' or 'DESC'. Defaults to 'DESC' * 'customer_id' The user ID of a customer on the site. * 'product_id' The post ID of a WC_Product_Subscription, WC_Product_Variable_Subscription or WC_Product_Subscription_Variation object * 'subscription_status' Any valid subscription status. Can be 'any', 'active', 'cancelled', 'suspended', 'expired', 'pending' or 'trash'. Defaults to 'any'. * @return array Subscription details in 'subscription_key' => 'subscription_details' form. * @since 1.4 */ public static function get_subscriptions($args = array()) { global $wpdb; $args = wp_parse_args($args, array('subscriptions_per_page' => 10, 'paged' => 1, 'offset' => 0, 'orderby' => '_subscription_start_date', 'order' => 'DESC', 'customer_id' => '', 'product_id' => '', 'variation_id' => '', 'order_id' => array(), 'subscription_status' => 'any')); // Map human friendly order_by values to internal keys switch ($args['orderby']) { case 'status': $args['orderby'] = '_subscription_status'; break; case 'start_date': $args['orderby'] = '_subscription_start_date'; break; case 'expiry_date': $args['orderby'] = '_subscription_expiry_date'; break; case 'trial_expiry_date': $args['orderby'] = '_subscription_trial_expiry_date'; break; case 'end_date': $args['orderby'] = '_subscription_end_date'; break; } $subscriptions = array(); // First see if we're paging, so the limit can be applied to subqueries if (-1 !== $args['subscriptions_per_page']) { $per_page = absint($args['subscriptions_per_page']); $page_start = ''; if (0 == $args['paged']) { $args['paged'] = 1; } if ($args['paged']) { $page_start = absint($args['paged'] - 1) * $per_page . ', '; } elseif ($args['offset']) { $page_start = absint($args['offset']) . ', '; } $limit_query = ' LIMIT ' . $page_start . $per_page; } else { $limit_query = ''; } if ('DESC' === $args['order']) { $order_query = ' DESC'; } else { $order_query = ' ASC'; } // Now start building the actual query $query = "\n\t\t\tSELECT meta.*, items.*, r.renewal_order_count, (CASE WHEN (r.renewal_order_count > 0) THEN l.last_payment_date ELSE o.order_date END) AS last_payment_date FROM `{$wpdb->prefix}woocommerce_order_itemmeta` AS meta\n\t\t\tLEFT JOIN `{$wpdb->prefix}woocommerce_order_items` AS items USING (order_item_id)"; $query .= "\n\t\t\tLEFT JOIN (\n\t\t\t\tSELECT a.order_item_id FROM `{$wpdb->prefix}woocommerce_order_itemmeta` AS a"; // To filter order items by a specific product ID, we need to do another join if (!empty($args['product_id']) || !empty($args['variation_id'])) { // Can only specify a product ID or a variation ID, no need to specify a product ID if you want a variation of that product $meta_key = !empty($args['variation_id']) ? '_variation_id' : '_product_id'; $query .= sprintf("\n\t\t\t\tLEFT JOIN (\n\t\t\t\t\tSELECT `{$wpdb->prefix}woocommerce_order_itemmeta`.order_item_id FROM `{$wpdb->prefix}woocommerce_order_itemmeta`\n\t\t\t\t\tWHERE `{$wpdb->prefix}woocommerce_order_itemmeta`.meta_key = '%s'\n\t\t\t\t\tAND `{$wpdb->prefix}woocommerce_order_itemmeta`.meta_value = %s\n\t\t\t\t) AS p\n\t\t\t\tUSING (order_item_id)", $meta_key, $args['product_id']); } // To filter order items by a specific subscription status, we need to do another join (unless we're also ordering by subscription status) if (!empty($args['subscription_status']) && 'any' !== $args['subscription_status'] && '_subscription_status' !== $args['orderby']) { if ('all' == $args['subscription_status']) { // get all but trashed subscriptions $query .= "\n\t\t\t\tLEFT JOIN (\n\t\t\t\t\tSELECT `{$wpdb->prefix}woocommerce_order_itemmeta`.order_item_id FROM `{$wpdb->prefix}woocommerce_order_itemmeta`\n\t\t\t\t\tWHERE `{$wpdb->prefix}woocommerce_order_itemmeta`.meta_key = '_subscription_status'\n\t\t\t\t\tAND `{$wpdb->prefix}woocommerce_order_itemmeta`.meta_value != 'trash'\n\t\t\t\t) AS s\n\t\t\t\tUSING (order_item_id)"; } else { $query .= sprintf("\n\t\t\t\tLEFT JOIN (\n\t\t\t\t\tSELECT `{$wpdb->prefix}woocommerce_order_itemmeta`.order_item_id FROM `{$wpdb->prefix}woocommerce_order_itemmeta`\n\t\t\t\t\tWHERE `{$wpdb->prefix}woocommerce_order_itemmeta`.meta_key = '_subscription_status'\n\t\t\t\t\tAND `{$wpdb->prefix}woocommerce_order_itemmeta`.meta_value = '%s'\n\t\t\t\t) AS s\n\t\t\t\tUSING (order_item_id)", $args['subscription_status']); } } // We need an additional join when ordering by certain attributes switch ($args['orderby']) { case '_product_id': // Because all products have a product ID, but not all products are subscriptions if (empty($args['product_id'])) { $query .= "\n\t\t\t\tLEFT JOIN (\n\t\t\t\t\tSELECT `{$wpdb->prefix}woocommerce_order_itemmeta`.order_item_id FROM `{$wpdb->prefix}woocommerce_order_itemmeta`\n\t\t\t\t\tWHERE `{$wpdb->prefix}woocommerce_order_itemmeta`.meta_key LIKE '_subscription_%s'\n\t\t\t\t\tGROUP BY `{$wpdb->prefix}woocommerce_order_itemmeta`.order_item_id\n\t\t\t\t) AS a2 USING (order_item_id)"; } break; case '_order_item_name': // Because the order item name is found in the order_items tables // Because the order item name is found in the order_items tables case 'name': if (empty($args['product_id'])) { $query .= "\n\t\t\t\tLEFT JOIN (\n\t\t\t\t\tSELECT `{$wpdb->prefix}woocommerce_order_items`.order_item_id, `{$wpdb->prefix}woocommerce_order_items`.order_item_name FROM `{$wpdb->prefix}woocommerce_order_items`\n\t\t\t\t\tWHERE `{$wpdb->prefix}woocommerce_order_items`.order_item_type = 'line_item'\n\t\t\t\t) AS names USING (order_item_id)"; } break; case 'order_id': // Because the order ID is found in the order_items tables $query .= "\n\t\t\t\tLEFT JOIN (\n\t\t\t\t\tSELECT `{$wpdb->prefix}woocommerce_order_items`.order_item_id, `{$wpdb->prefix}woocommerce_order_items`.order_id FROM `{$wpdb->prefix}woocommerce_order_items`\n\t\t\t\t\tWHERE `{$wpdb->prefix}woocommerce_order_items`.order_item_type = 'line_item'\n\t\t\t\t) AS order_ids USING (order_item_id)"; break; case 'renewal_order_count': $query .= "\n\t\t\t\tLEFT JOIN (\n\t\t\t\t\tSELECT items2.order_item_id, items2.order_id, r2.renewal_order_count FROM `{$wpdb->prefix}woocommerce_order_items` AS items2\n\t\t\t\t\tLEFT JOIN (\n\t\t\t\t\t\tSELECT posts.post_parent, COUNT(posts.ID) as renewal_order_count FROM `{$wpdb->prefix}posts` AS posts\n\t\t\t\t\t\tWHERE posts.post_parent != 0\n\t\t\t\t\t\tAND posts.post_type = 'shop_order'\n\t\t\t\t\t\tGROUP BY posts.post_parent\n\t\t\t\t\t) AS r2 ON r2.post_parent = items2.order_id\n\t\t\t\t\tWHERE items2.order_item_type = 'line_item'\n\t\t\t\t) AS renewals USING (order_item_id)"; break; case 'user_display_name': case 'user': if (empty($args['customer_id'])) { $query .= "\n\t\t\t\tLEFT JOIN (\n\t\t\t\t\tSELECT items2.order_item_id, items2.order_id, users.display_name FROM `{$wpdb->prefix}woocommerce_order_items` AS items2\n\t\t\t\t\tLEFT JOIN (\n\t\t\t\t\t\tSELECT postmeta.post_id, postmeta.meta_value, u.display_name FROM `{$wpdb->prefix}postmeta` AS postmeta\n\t\t\t\t\t\tLEFT JOIN (\n\t\t\t\t\t\t\tSELECT `{$wpdb->prefix}users`.ID, `{$wpdb->prefix}users`.display_name FROM `{$wpdb->prefix}users`\n\t\t\t\t\t\t) AS u ON u.ID = postmeta.meta_value\n\t\t\t\t\t\tWHERE postmeta.meta_key = '_customer_user'\n\t\t\t\t\t) AS users ON users.post_id = items2.order_id\n\t\t\t\t\tWHERE items2.order_item_type = 'line_item'\n\t\t\t\t) AS users_items USING (order_item_id)"; } case 'last_payment_date': // Because we need the date of the last renewal order (or maybe the original order if there are no renewal orders) $query .= "\n\t\t\t\tLEFT JOIN (\n\t\t\t\t\tSELECT items2.order_item_id, (CASE WHEN (r2.renewal_order_count > 0) THEN l.last_payment_date ELSE o.order_date END) AS last_payment_date FROM `{$wpdb->prefix}woocommerce_order_items` AS items2\n\t\t\t\t\tLEFT JOIN (\n\t\t\t\t\t\tSELECT posts.post_parent, COUNT(posts.ID) as renewal_order_count FROM `{$wpdb->prefix}posts` AS posts\n\t\t\t\t\t\tWHERE posts.post_parent != 0\n\t\t\t\t\t\tAND posts.post_type = 'shop_order'\n\t\t\t\t\t\tGROUP BY posts.post_parent\n\t\t\t\t\t) AS r2 ON r2.post_parent = items2.order_id\n\t\t\t\t\tLEFT JOIN (\n\t\t\t\t\t\tSELECT o.ID, o.post_date_gmt AS order_date FROM `{$wpdb->prefix}posts` AS o\n\t\t\t\t\t\tWHERE o.post_type = 'shop_order'\n\t\t\t\t\t\tAND o.post_parent = 0\n\t\t\t\t\t) AS o ON o.ID = items2.order_id\n\t\t\t\t\tLEFT JOIN (\n\t\t\t\t\t\tSELECT p.ID, p.post_parent, MAX(p.post_date_gmt) AS last_payment_date FROM `{$wpdb->prefix}posts` AS p\n\t\t\t\t\t\tWHERE p.post_type = 'shop_order'\n\t\t\t\t\t\tAND p.post_parent != 0\n\t\t\t\t\t\tGROUP BY p.post_parent\n\t\t\t\t\t) AS l ON l.post_parent = items2.order_id\n\t\t\t\t\tWHERE items2.order_item_type = 'line_item'\n\t\t\t\t) AS payment_dates USING (order_item_id)"; break; } // Start where query $query .= "\n\t\t\t\tWHERE 1=1"; // We only want subscriptions from within the product filter subclause if (!empty($args['product_id']) || !empty($args['variation_id'])) { $query .= "\n\t\t\t\tAND a.order_item_id = p.order_item_id"; } // We only want subscriptions from within the status filter subclause if (!empty($args['subscription_status']) && 'any' !== $args['subscription_status'] && '_subscription_status' !== $args['orderby']) { $query .= "\n\t\t\t\tAND a.order_item_id = s.order_item_id"; } // We only want items from a certain order if (!empty($args['order_id'])) { $order_ids = is_array($args['order_id']) ? implode(',', $args['order_id']) : $args['order_id']; $query .= sprintf("\n\t\t\t\tAND a.order_item_id IN (\n\t\t\t\t\tSELECT o.order_item_id FROM `{$wpdb->prefix}woocommerce_order_items` AS o\n\t\t\t\t\tWHERE o.order_id IN (%s)\n\t\t\t\t)", $order_ids); } // If we only want subscriptions for a certain customer ID, we need to make sure items are from the customer's orders if (!empty($args['customer_id'])) { $query .= sprintf("\n\t\t\t\tAND a.order_item_id IN (\n\t\t\t\t\tSELECT `{$wpdb->prefix}woocommerce_order_items`.order_item_id FROM `{$wpdb->prefix}woocommerce_order_items`\n\t\t\t\t\tWHERE `{$wpdb->prefix}woocommerce_order_items`.order_id IN (\n\t\t\t\t\t\tSELECT `{$wpdb->prefix}postmeta`.post_id FROM `{$wpdb->prefix}postmeta`\n\t\t\t\t\t\tWHERE `{$wpdb->prefix}postmeta`.meta_key = '_customer_user'\n\t\t\t\t\t\tAND `{$wpdb->prefix}postmeta`.meta_value = %s\n\t\t\t\t\t)\n\t\t\t\t)", $args['customer_id']); } // Now we need to sort the subscriptions, which may mean selecting a specific bit of meta data switch ($args['orderby']) { case '_subscription_start_date': case '_subscription_expiry_date': case '_subscription_trial_expiry_date': case '_subscription_end_date': $query .= sprintf("\n\t\t\t\tAND a.meta_key = '%s'\n\t\t\t\tORDER BY CASE WHEN CAST(a.meta_value AS DATETIME) IS NULL THEN 1 ELSE 0 END, CAST(a.meta_value AS DATETIME) %s", $args['orderby'], $order_query); break; case '_subscription_status': $query .= "\n\t\t\t\tAND a.meta_key = '_subscription_status'\n\t\t\t\tORDER BY a.meta_value" . $order_query; break; case '_product_id': if (empty($args['product_id'])) { $query .= "\n\t\t\t\tAND a2.order_item_id = a.order_item_id\n\t\t\t\tAND a.meta_key = '_product_id'\n\t\t\t\tORDER BY a.meta_value" . $order_query; } break; case '_order_item_name': case 'name': if (empty($args['product_id'])) { $query .= "\n\t\t\t\tAND a.meta_key = '_subscription_start_date'\n\t\t\t\tAND names.order_item_id = a.order_item_id\n\t\t\t\tORDER BY names.order_item_name" . $order_query . ", CASE WHEN CAST(a.meta_value AS DATETIME) IS NULL THEN 1 ELSE 0 END, CAST(a.meta_value AS DATETIME) DESC"; } break; case 'order_id': $query .= "\n\t\t\t\tAND a.meta_key = '_subscription_start_date'\n\t\t\t\tAND order_ids.order_item_id = a.order_item_id\n\t\t\t\tORDER BY order_ids.order_id" . $order_query; break; case 'renewal_order_count': $query .= "\n\t\t\t\tAND a.meta_key = '_subscription_start_date'\n\t\t\t\tAND renewals.order_item_id = a.order_item_id\n\t\t\t\tORDER BY renewals.renewal_order_count" . $order_query; break; case 'user_display_name': case 'user': if (empty($args['customer_id'])) { $query .= "\n\t\t\t\tAND a.meta_key = '_subscription_start_date'\n\t\t\t\tAND users_items.order_item_id = a.order_item_id\n\t\t\t\tORDER BY users_items.display_name" . $order_query . ", CASE WHEN CAST(a.meta_value AS DATETIME) IS NULL THEN 1 ELSE 0 END, CAST(a.meta_value AS DATETIME) DESC"; } break; case 'last_payment_date': $query .= "\n\t\t\t\tAND a.meta_key = '_subscription_start_date'\n\t\t\t\tAND payment_dates.order_item_id = a.order_item_id\n\t\t\t\tORDER BY payment_dates.last_payment_date" . $order_query; break; } // Paging if (-1 !== $args['subscriptions_per_page']) { $query .= $limit_query; } $query .= "\n\t\t\t) AS a3 USING (order_item_id)"; // Add renewal order count & last payment date (there is duplication here when ordering by renewal order count or last payment date, but it's an arbitrary performance hit) $query .= "\n\t\t\tLEFT JOIN (\n\t\t\t\tSELECT `{$wpdb->prefix}posts`.post_parent, COUNT(`{$wpdb->prefix}posts`.ID) as renewal_order_count FROM `{$wpdb->prefix}posts`\n\t\t\t\tWHERE `{$wpdb->prefix}posts`.post_parent != 0\n\t\t\t\tAND `{$wpdb->prefix}posts`.post_type = 'shop_order'\n\t\t\t\tGROUP BY `{$wpdb->prefix}posts`.post_parent\n\t\t\t) AS r ON r.post_parent = items.order_id\n\t\t\tLEFT JOIN (\n\t\t\t\tSELECT o.ID, o.post_date_gmt AS order_date FROM `{$wpdb->prefix}posts` AS o\n\t\t\t\tWHERE o.post_type = 'shop_order'\n\t\t\t\tAND o.post_parent = 0\n\t\t\t) AS o ON o.ID = items.order_id\n\t\t\tLEFT JOIN (\n\t\t\t\tSELECT p.ID, p.post_parent, MAX(p.post_date_gmt) AS last_payment_date FROM `{$wpdb->prefix}posts` AS p\n\t\t\t\tWHERE p.post_type = 'shop_order'\n\t\t\t\tAND p.post_parent != 0\n\t\t\t\tGROUP BY p.post_parent\n\t\t\t) AS l ON l.post_parent = items.order_id"; $query .= "\n\t\t\tWHERE meta.meta_key REGEXP '_subscription_(.*)|_product_id|_variation_id'\n\t\t\tAND meta.order_item_id = a3.order_item_id"; $query = apply_filters('woocommerce_get_subscriptions_query', $query, $args); $raw_subscriptions = $wpdb->get_results($query); // Create a backward compatible structure foreach ($raw_subscriptions as $raw_subscription) { if (!isset($raw_subscription->order_item_id)) { continue; } if (!array_key_exists($raw_subscription->order_item_id, $subscriptions)) { $subscriptions[$raw_subscription->order_item_id] = array('order_id' => $raw_subscription->order_id, 'name' => $raw_subscription->order_item_name, 'renewal_order_count' => empty($raw_subscription->renewal_order_count) ? 0 : $raw_subscription->renewal_order_count, 'last_payment_date' => $raw_subscription->last_payment_date); $subscriptions[$raw_subscription->order_item_id]['user_id'] = get_post_meta($raw_subscription->order_id, '_customer_user', true); } $meta_key = str_replace('_subscription', '', $raw_subscription->meta_key); $meta_key = substr($meta_key, 0, 1) == '_' ? substr($meta_key, 1) : $meta_key; if ('product_id' === $meta_key) { $subscriptions[$raw_subscription->order_item_id]['subscription_key'] = WC_Subscriptions_Manager::get_subscription_key($subscriptions[$raw_subscription->order_item_id]['order_id'], $raw_subscription->meta_value); } $subscriptions[$raw_subscription->order_item_id][$meta_key] = maybe_unserialize($raw_subscription->meta_value); } return apply_filters('woocommerce_get_subscriptions', $subscriptions, $args); }
/** * Update the recurring payment method for a subscription after a customer has paid for a failed renewal order * (which usually failed because of an issue with the existing payment, like an expired card or token). * * Also trigger a hook for payment gateways to update any meta on the original order for a subscription. * * @param WC_Order $renewal_order The order which recorded the successful payment (to make up for the failed automatic payment). * @param WC_Order $original_order The original order in which the subscription was purchased. * @since 1.4 */ public static function change_failing_payment_method($renewal_order, $original_order) { $subscription_key = WC_Subscriptions_Manager::get_subscription_key($original_order->id); $new_payment_method = woocommerce_clean($_POST['payment_method']); self::update_recurring_payment_method($subscription_key, $original_order, $new_payment_method); do_action('woocommerce_subscriptions_changed_failing_payment_method', $original_order, $renewal_order, $subscription_key); do_action('woocommerce_subscriptions_changed_failing_payment_method_' . $new_payment_method, $original_order, $renewal_order, $subscription_key); }
/** * Make sure anything requesting the first payment date for a synced subscription on the front-end receives * a date which takes into account the day on which payments should be processed. * * This is necessary as the self::calculate_first_payment_date() is not called when the subscription is active * (which it isn't until the first payment is completed and the subscription is activated). * * @since 1.5 */ public static function get_first_payment_date($first_payment_date, $order, $product_id, $type) { $subscription_key = WC_Subscriptions_Manager::get_subscription_key($order->id, $product_id); if (self::order_contains_synced_subscription($order->id) && 1 >= WC_Subscriptions_Manager::get_subscriptions_completed_payment_count($subscription_key)) { $subscription = WC_Subscriptions_Manager::get_subscription($subscription_key); // Don't prematurely set the first payment date when manually adding a subscription from the admin if (defined('WOOCOMMERCE_CHECKOUT') && true === WOOCOMMERCE_CHECKOUT || !is_admin() || 'active' == $subscription['status']) { $id_for_calculation = !empty($subscription['variation_id']) ? $subscription['variation_id'] : $subscription['product_id']; $first_payment_timestamp = self::calculate_first_payment_date($id_for_calculation, 'timestamp', $order->order_date); if (0 != $first_payment_timestamp) { $first_payment_date = 'mysql' == $type ? date('Y-m-d H:i:s', $first_payment_timestamp) : $first_payment_timestamp; } } } return $first_payment_date; }
/** * Save related subscription data when a membership access is granted via a purchase * * Sets the end date to match subscription end date * * @since 1.0.0 * @param WC_Memberships_Membership_Plan $plan * @param array $args */ public function save_subscription_data(WC_Memberships_Membership_Plan $plan, $args) { $product = wc_get_product($args['product_id']); // Handle access from subscriptions if ($this->has_membership_plan_subscription($plan->get_id()) && $product->is_type(array('subscription', 'subscription_variation', 'variable-subscription'))) { // note: always use the product ID (not variation ID) when looking up a subscription // as Subs requires it $subscription = $this->get_order_product_subscription($args['order_id'], $product->id); if (!$subscription) { return; } if ($this->is_subscriptions_gte_2_0()) { // Save related subscription ID update_post_meta($args['user_membership_id'], '_subscription_id', $subscription->id); } else { // Save related subscription key $subscription_key = WC_Subscriptions_Manager::get_subscription_key($args['order_id'], $product->id); update_post_meta($args['user_membership_id'], '_subscription_key', $subscription_key); } // Set membership expiry date based on subscription expiry date if ($this->plan_grants_access_while_subscription_active($plan->get_id())) { $end_date = $this->is_subscriptions_gte_2_0() ? $subscription->get_date('end') ? $subscription->get_date('end') : '' : ($subscription['expiry_date'] ? $subscription['expiry_date'] : ''); update_post_meta($args['user_membership_id'], '_end_date', $end_date); } } }
/** * If the payment for a renewal order has previously failed and is then paid, we need to make sure the * subscription payment function is called. * * @param int $user_id The id of the user who purchased the subscription * @param string $subscription_key A subscription key of the form created by @see WC_Subscriptions_Manager::get_subscription_key() * @since 1.2 */ public static function process_subscription_payment_on_child_order($order_id, $payment_status = 'completed') { if (self::is_renewal($order_id, array('order_role' => 'child'))) { $child_order = new WC_Order($order_id); $parent_order = self::get_parent_order($child_order); $subscriptions_in_order = $child_order->get_items(); // Should only be one subscription in the renewal order, but just in case foreach ($subscriptions_in_order as $item) { $item_id = WC_Subscriptions_Order::get_items_product_id($item); if (WC_Subscriptions_Order::is_item_subscription($parent_order, $item_id)) { if ('failed' == $payment_status) { // Don't duplicate renewal order remove_action('processed_subscription_payment_failure', __CLASS__ . '::generate_failed_payment_renewal_order', 10, 2); WC_Subscriptions_Manager::process_subscription_payment_failure_on_order($parent_order->id, $item_id); // But make sure orders are still generated for other payments in the same request add_action('processed_subscription_payment_failure', __CLASS__ . '::generate_failed_payment_renewal_order', 10, 2); } else { // Don't duplicate renewal order remove_action('processed_subscription_payment', __CLASS__ . '::generate_paid_renewal_order', 10, 2); WC_Subscriptions_Manager::process_subscription_payments_on_order($parent_order->id, $item_id); // But make sure orders are still generated for other payments in the same request add_action('processed_subscription_payment', __CLASS__ . '::generate_paid_renewal_order', 10, 2); // Reactivate the subscription - activate_subscription doesn't operate on child orders $subscription_key = WC_Subscriptions_Manager::get_subscription_key($parent_order->id, $item_id); WC_Subscriptions_Manager::reactivate_subscription($parent_order->customer_user, $subscription_key); } } } } }
/** * Update the recurring payment method for a subscription after a customer has paid for a failed renewal order * (which usually failed because of an issue with the existing payment, like an expired card or token). * * Also trigger a hook for payment gateways to update any meta on the original order for a subscription. * * @param WC_Order $renewal_order The order which recorded the successful payment (to make up for the failed automatic payment). * @param WC_Order $original_order The original order in which the subscription was purchased. * @since 1.4 */ public static function change_failing_payment_method($renewal_order, $original_order) { if (!WC_Subscriptions_Order::requires_manual_renewal($original_order->id)) { $subscription_key = WC_Subscriptions_Manager::get_subscription_key($original_order->id); if (isset($_POST['payment_method'])) { $new_payment_method = woocommerce_clean($_POST['payment_method']); } else { $new_payment_method = $renewal_order->payment_method; } self::update_recurring_payment_method($subscription_key, $original_order, $new_payment_method); do_action('woocommerce_subscriptions_changed_failing_payment_method', $original_order, $renewal_order, $subscription_key); do_action('woocommerce_subscriptions_changed_failing_payment_method_' . $new_payment_method, $original_order, $renewal_order, $subscription_key); } }
function blendercloud_api($atts) { $user_data = array('shop_id' => '0', 'cloud_access' => 0, 'expiration_date' => '1970-01-01 00:00:00'); $last_expiration_date = new DateTime('1970-01-01 00:00:00'); // map blenderid to userid $args = array('search' => $_GET['blenderid'], 'search_columns' => array('user_login')); $user_query = new WP_User_Query($args); // Get the results from the query, returning the first user $users = $user_query->get_results(); if (!empty($users)) { $user_id = $users[0]->ID; $user_data['shop_id'] = $user_id; // process simple products (prepaid subscriptions) $order_ids = bo_get_all_user_orders($user_id, 'completed'); foreach ($order_ids as $order_id) { $order = new WC_Order($order_id); $order_date = $order->order_date; $items = $order->get_items(); foreach ($items as $item) { $tmp = bo_empty_subscription_line(); $product_id = $item['product_id']; $product = get_product($product_id); $sku = $product->get_sku(); $expiry_date = new DateTime($order_date); $tmp['sku'] = $sku; switch ($sku) { case 'cloud-prepaid-3': case 'cloud-prepaid-3-renewal': $expiry_date->modify('+3 month'); break; case 'cloud-prepaid-18': $expiry_date->modify('+18 month'); break; default: continue 2; // skip to next product } $tmp['expiration_date'] = $expiry_date->format('Y-m-d H:i:s'); $tmp['subscription_status'] = 'prepaid'; $now = new DateTime("now"); if ($expiry_date > $now) { $tmp['cloud_access'] = 1; } if ($expiry_date > $last_expiration_date) { $last_expiration_date = $expiry_date; } $user_data['subscriptions'][] = $tmp; } } // process recurring subscriptions $subscriptions = WC_Subscriptions_Manager::get_users_subscriptions($user_id); if (!empty($subscriptions)) { // iterate over all subscriptions. foreach ($subscriptions as $subscription_details) { if ($subscription_details['status'] != 'trash') { $order_id = $subscription_details['order_id']; $product_id = $subscription_details['product_id']; $order = new WC_Order($order_id); // print_r($order); // $next_payment_date = WC_Subscriptions_Manager::get_next_payment_date( $subscription_key, $user_id, 'mysql' ); $subscription_key = WC_Subscriptions_Manager::get_subscription_key($order_id, $product_id); if ($subscription_details['expiry_date'] == 0 && !in_array($subscription_details['status'], array('cancelled', 'switched'))) { $end_time = WC_Subscriptions_Manager::get_next_payment_date($subscription_key, $user_id, 'mysql'); $end_timestamp = strtotime($end_time); } else { if (in_array($subscription_details['status'], array('cancelled', 'switched'))) { $end_of_prepaid_term = wc_next_scheduled_action('scheduled_subscription_end_of_prepaid_term', array('user_id' => (int) $user_id, 'subscription_key' => $subscription_key)); if (false === $end_of_prepaid_term) { $end_timestamp = strtotime($subscription_details['end_date']); } else { $end_timestamp = $end_of_prepaid_term; } } else { $end_timestamp = strtotime($subscription_details['expiry_date']); } } // if( $users[0]->data->user_email == '*****@*****.**' ) { // print_r($subscription_details); // } if ($subscription_details['status'] == 'cancelled') { $end_timestamp = strtotime($subscription_details['trial_expiry_date']); } if ($subscription_details['status'] == 'on-hold') { $end_timestamp = strtotime($subscription_details['last_payment_date']); } $end_time = date("Y-m-d H:i:s", $end_timestamp); $product = get_product($product_id); $sku = $product->get_sku(); $tmp = bo_empty_subscription_line(); $tmp['expiration_date'] = $end_time; $tmp['subscription_status'] = $subscription_details['status']; $expiry_date = new DateTime($end_time); if ($expiry_date > $last_expiration_date) { $last_expiration_date = $expiry_date; } $now = new DateTime("now"); $tmp['cloud_access'] = $expiry_date > $now ? 1 : 0; $tmp['sku'] = $sku; // if order is refunded, stop access if ($order->status == 'refunded') { $tmp['expiration_date'] = $end_time; $tmp['subscription_status'] = 'refunded'; $tmp['cloud_access'] = 0; } switch ($sku) { case 'cloud-subscription-1-renewal': case 'cloud-subscription-3': //$tmp['failed_payments'] = $subscription['failed_payments']; break; case 'cloud-subscription-team': // purchased team size $variation_id = $subscription_details['variation_id']; // find variation info from order to pass on # of seats $order = new WC_Order($order_id); $items = $order->get_items(); $team_members = 0; foreach ($items as $item) { // does product variation id match the current subscription? if ($item['item_meta']['_variation_id'][0] == $variation_id) { $team_members = $item['item_meta']['pa_team-size'][0]; } } $tmp['team_members'] = $team_members; break; } $user_data['subscriptions'][] = $tmp; } } } } // add one grace day to expiration $last_expiration_date->add(DateInterval::createfromdatestring('+1 day')); $user_data['expiration_date'] = $last_expiration_date->format('Y-m-d H:i:s'); $now = new DateTime("now"); if ($last_expiration_date > $now) { $user_data['cloud_access'] = 1; } //echo "<pre>";print_r($user_data); echo json_encode($user_data, JSON_PRETTY_PRINT); die; }
function woo_ce_get_subscription_key( $order_id = 0, $product_id = 0 ) { if( method_exists( 'WC_Subscriptions_Manager', 'get_subscription_key' ) ) { $key = WC_Subscriptions_Manager::get_subscription_key( $order_id, $product_id ); return $key; } }
/** * 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; } if (empty($transaction_details['custom']) || empty($transaction_details['invoice'])) { 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.'); } return; } 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 $transaction_id = $transaction_details['txn_type'] . '_' . $transaction_details['ipn_track_id']; if (in_array($transaction_id, $handled_ipn_requests)) { if (self::$debug) { self::$log->add('paypal', 'Subscription IPN Error: This IPN message has already been correctly handled.'); } return; } } if (isset($transaction_details['subscr_id'])) { 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); $subscription_key = WC_Subscriptions_Manager::get_subscription_key($order->id, WC_Subscriptions_Order::get_items_product_id($subscription_item)); $subscription = WC_Subscriptions_Manager::get_subscription($subscription_key, $order->customer_user); $is_first_payment = empty($subscription['completed_payments']) ? true : false; 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']); // Payment completed $order->add_order_note(__('IPN subscription sign up completed.', WC_Subscriptions::$text_domain)); if (self::$debug) { self::$log->add('paypal', 'IPN subscription sign up completed for order ' . $order_id); } // When there is a free trial & no initial payment amount, we need to mark the order as paid and activate the subscription if (0 == WC_Subscriptions_Order::get_total_initial_payment($order) && WC_Subscriptions_Order::get_subscription_trial_length($order) > 0) { $order->payment_complete(); WC_Subscriptions_Manager::activate_subscriptions_for_order($order); } 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.', WC_Subscriptions::$text_domain)); 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); } else { // 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); 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.', WC_Subscriptions::$text_domain)); 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': if (self::$debug) { self::$log->add('paypal', 'IPN subscription cancelled for order ' . $order_id); } // Subscription Payment completed $order->add_order_note(__('IPN subscription cancelled for order.', WC_Subscriptions::$text_domain)); WC_Subscriptions_Manager::cancel_subscriptions_for_order($order); break; case 'subscr_eot': // Subscription ended, either due to failed payments or expiration $subscription_length = WC_Subscriptions_Order::get_subscription_length($order); // PayPal fires the 'subscr_eot' notice immediately if a subscription is only for one billing period, so ignore the request when we only have one billing period if (1 != $subscription_length && $subscription_length != WC_Subscriptions_Order::get_subscription_interval($order)) { if (self::$debug) { self::$log->add('paypal', 'IPN subscription end-of-term for order ' . $order_id); } // Record subscription ended $order->add_order_note(__('IPN subscription end-of-term for order.', WC_Subscriptions::$text_domain)); // Ended due to failed payments so cancel the subscription if (gmdate('U') + 24 * 60 * 60 < strtotime(WC_Subscriptions_Manager::get_subscription_expiration_date(WC_Subscriptions_Manager::get_subscription_key($order->id), $order->customer_user))) { WC_Subscriptions_Manager::cancel_subscriptions_for_order($order); } else { WC_Subscriptions_Manager::expire_subscriptions_for_order($order); } } 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.', WC_Subscriptions::$text_domain)); // 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_id); break; } // Store the transaction ID to avoid handling requests duplicated by PayPal if (isset($transaction_details['ipn_track_id'])) { $handled_ipn_requests[] = $transaction_id; update_post_meta($order_id, '_paypal_ipn_tracking_ids', $handled_ipn_requests); } // Prevent default IPN handling for subscription txn_types exit; }
/** * Delete a stored billing method */ function delete_payment_method($payment_method) { global $woocommerce; $user = wp_get_current_user(); $customer_vault_ids = get_user_meta($user->ID, 'customer_vault_ids', true); $id = $customer_vault_ids[$payment_method]; // If method is Single Billing, actually delete the record if (substr($id, 0, 1) !== '_') { $inspire_request = array('username' => $this->username, 'password' => $this->password, 'customer_vault' => 'delete_customer', 'customer_vault_id' => $id); $response = $this->post_and_get_response($inspire_request); if ($response['response'] != 1) { $woocommerce->add_error(__('Sorry, there was an error: ', 'woocommerce') . $response['responsetext']); $woocommerce->show_messages(); return; } } $last_method = count($customer_vault_ids) - 1; // Update subscription references if (class_exists('WC_Subscriptions_Manager')) { foreach ((array) WC_Subscriptions_Manager::get_users_subscriptions($user->ID) as $subscription) { $subscription_payment_method = get_post_meta($subscription['order_id'], 'payment_method_number', true); // Cancel subscriptions that were purchased with the deleted method if ($subscription_payment_method == $payment_method) { delete_post_meta($subscription['order_id'], 'payment_method_number'); WC_Subscriptions_Manager::cancel_subscription($user->ID, WC_Subscriptions_Manager::get_subscription_key($subscription['order_id'])); } else { if ($subscription_payment_method == $last_method && $subscription['status'] != 'cancelled') { update_post_meta($subscription['order_id'], 'payment_method_number', $payment_method); } } } } // Delete the reference by replacing it with the last method in the array if ($payment_method < $last_method) { $customer_vault_ids[$payment_method] = $customer_vault_ids[$last_method]; } unset($customer_vault_ids[$last_method]); update_user_meta($user->ID, 'customer_vault_ids', $customer_vault_ids); $woocommerce->add_message(__('Successfully deleted your information!', 'woocommerce')); $woocommerce->show_messages(); }
/** * Returns an array of order IDs for valid orders that grant group * membership for the given group to the user related to the order. * * @param int $user_id * @param int $group_id * @return array of int, order IDs */ public static function get_valid_order_ids_granting_group_membership_from_order_items($user_id, $group_id) { $order_ids = array(); if (!empty($user_id)) { $base_statuses = array('processing', 'completed'); $statuses = array('completed'); $options = get_option('groups-woocommerce', array()); $order_status = isset($options[GROUPS_WS_MEMBERSHIP_ORDER_STATUS]) ? $options[GROUPS_WS_MEMBERSHIP_ORDER_STATUS] : GROUPS_WS_DEFAULT_MEMBERSHIP_ORDER_STATUS; if ($order_status == 'processing') { $statuses[] = 'processing'; } // DO NOT use groups_ws_order_status( $statuses ) for $statuses or $base_statuses here, // $order->status doesn't provide the wc- prefix. $groups_product_groups = get_user_meta($user_id, '_groups_product_groups', true); if (empty($groups_product_groups)) { $groups_product_groups = array(); } foreach ($groups_product_groups as $order_id => $product_ids) { if ($order = Groups_WS_Helper::get_order($order_id)) { // If this is a completed/processing order, consider group assignments. // We check the order status for non-subscription products below, // for subscriptions the subscription status is checked. if (in_array($order->status, $base_statuses)) { // Note that for orders placed with versions up to 1.4.1, the following won't give the results we might expect if the product group-related information has changed since the order was placed. // As we don't store that information (WC doesn't store the whole lot of the product when purchased, nor does GW) checking the duration based on the product is the best effort at // finding out about the group membership duration we can make. // Use the order items (only existing order items are taken into account). if ($items = $order->get_items()) { foreach ($items as $item) { if ($product = $order->get_product_from_item($item)) { // Use the groups that were stored for the product when it was ordered, // this avoids hickups when the product's groups were changed since. if (isset($product_ids[$product->id]) && isset($product_ids[$product->id]['groups'])) { $product_groups = $product_ids[$product->id]['groups']; if (in_array($group_id, $product_groups)) { // non-subscriptions if (!class_exists('WC_Subscriptions_Product') || !WC_Subscriptions_Product::is_subscription($product->id)) { if (in_array($order->status, $statuses)) { if (isset($product_ids[$product->id]) && isset($product_ids[$product->id]['version'])) { $has_duration = isset($product_ids[$product->id]['duration']) && $product_ids[$product->id]['duration'] && isset($product_ids[$product->id]['duration_uom']); } else { $has_duration = Groups_WS_Product::has_duration($product); } // unlimited membership if (!$has_duration) { if (!in_array($order_id, $order_ids)) { $order_ids[] = $order_id; } } else { if (isset($product_ids[$product->id]) && isset($product_ids[$product->id]['version'])) { $duration = Groups_WS_Product::calculate_duration($product_ids[$product->id]['duration'], $product_ids[$product->id]['duration_uom']); } else { // <= 1.4.1 $duration = Groups_WS_Product::get_duration($product); } // time-limited membership if ($duration) { $start_date = $order->order_date; if ($paid_date = get_post_meta($order_id, '_paid_date', true)) { $start_date = $paid_date; } $end = strtotime($start_date) + $duration; if (time() < $end) { if (!in_array($order_id, $order_ids)) { $order_ids[] = $order_id; } } } } } } else { // include active subscriptions ( subscriptions >= 2.x ) if (function_exists('wcs_get_subscriptions_for_order')) { if ($subscriptions = wcs_get_subscriptions_for_order($order_id)) { if (is_array($subscriptions)) { foreach ($subscriptions as $subscription) { if ($subscription->has_product($product->id)) { $valid = false; if ($subscription->get_status() == 'active') { $valid = true; } else { if ($subscription->get_status() == 'cancelled') { $hook_args = array('subscription_id' => $subscription->id); $end_timestamp = wp_next_scheduled('scheduled_subscription_end_of_prepaid_term', $hook_args); if ($end_timestamp !== false && $end_timestamp > time()) { $valid = true; } } } if ($valid) { if (!in_array($order_id, $order_ids)) { $order_ids[] = $order_id; break; } } } } } } } else { $subscription_key = WC_Subscriptions_Manager::get_subscription_key($order_id, $product->id); $subscription = WC_Subscriptions_Manager::get_subscription($subscription_key); if (isset($subscription['status'])) { $valid = false; if ($subscription['status'] == 'active') { $valid = true; } else { if ($subscription['status'] == 'cancelled') { $hook_args = array('user_id' => (int) $user_id, 'subscription_key' => $subscription_key); $end_timestamp = wp_next_scheduled('scheduled_subscription_end_of_prepaid_term', $hook_args); if ($end_timestamp !== false && $end_timestamp > time()) { $valid = true; } } } if ($valid) { if (!in_array($order_id, $order_ids)) { $order_ids[] = $order_id; } } } } } } } } } } } } } } return $order_ids; }
/** * Calculate the timestamp for the next payment * * @param WC_Order $order * @param int $product_id * * @return mixed|void */ private static function calculate_next_payment_timestamp($order, $product_id) { $type = 'timestamp'; $from_date = ''; $from_date_arg = $from_date; $subscription = WC_Subscriptions_Manager::get_subscription(WC_Subscriptions_Manager::get_subscription_key($order->id, $product_id)); $subscription_period = WC_Subscriptions_Order::get_subscription_period($order, $product_id); $subscription_interval = WC_Subscriptions_Order::get_subscription_interval($order, $product_id); $subscription_trial_length = WC_Subscriptions_Order::get_subscription_trial_length($order, $product_id); $subscription_trial_period = WC_Subscriptions_Order::get_subscription_trial_period($order, $product_id); $trial_end_time = !empty($subscription['trial_expiry_date']) ? $subscription['trial_expiry_date'] : WC_Subscriptions_Product::get_trial_expiration_date($product_id, get_gmt_from_date($order->order_date)); $trial_end_time = strtotime($trial_end_time); // If the subscription has a free trial period, and we're still in the free trial period, the next payment is due at the end of the free trial if ($subscription_trial_length > 0 && $trial_end_time > gmdate('U') + 60 * 60 * 23 + 120) { // Make sure trial expiry is more than 23+ hours in the future to account for trial expiration dates incorrectly stored in non-UTC/GMT timezone (and also for any potential changes to the site's timezone) $next_payment_timestamp = $trial_end_time; // The next payment date is {interval} billing periods from the from date } else { // We have a timestamp if (!empty($from_date) && is_numeric($from_date)) { $from_date = date('Y-m-d H:i:s', $from_date); } if (empty($from_date)) { if (!empty($subscription['completed_payments'])) { $from_date = array_pop($subscription['completed_payments']); $add_failed_payments = true; } else { if (!empty($subscription['start_date'])) { $from_date = $subscription['start_date']; $add_failed_payments = true; } else { $from_date = gmdate('Y-m-d H:i:s'); $add_failed_payments = false; } } $failed_payment_count = WC_Subscriptions_Order::get_failed_payment_count($order, $product_id); // Maybe take into account any failed payments if (true === $add_failed_payments && $failed_payment_count > 0) { $failed_payment_periods = $failed_payment_count * $subscription_interval; $from_timestamp = strtotime($from_date); if ('month' == $subscription_period) { $from_date = date('Y-m-d H:i:s', WC_Subscriptions::add_months($from_timestamp, $failed_payment_periods)); } else { // Safe to just add the billing periods $from_date = date('Y-m-d H:i:s', strtotime("+ {$failed_payment_periods} {$subscription_period}", $from_timestamp)); } } } $from_timestamp = strtotime($from_date); if ('month' == $subscription_period) { // Workaround potential PHP issue $next_payment_timestamp = WC_Subscriptions::add_months($from_timestamp, $subscription_interval); } else { $next_payment_timestamp = strtotime("+ {$subscription_interval} {$subscription_period}", $from_timestamp); } // Make sure the next payment is in the future $i = 1; while ($next_payment_timestamp < gmdate('U') && $i < 30) { if ('month' == $subscription_period) { $next_payment_timestamp = WC_Subscriptions::add_months($next_payment_timestamp, $subscription_interval); } else { // Safe to just add the billing periods $next_payment_timestamp = strtotime("+ {$subscription_interval} {$subscription_period}", $next_payment_timestamp); } $i = $i + 1; } } // If the subscription has an expiry date and the next billing period comes after the expiration, return 0 if (isset($subscription['expiry_date']) && 0 != $subscription['expiry_date'] && $next_payment_timestamp + 120 > strtotime($subscription['expiry_date'])) { $next_payment_timestamp = 0; } $next_payment = 'mysql' == $type && 0 != $next_payment_timestamp ? date('Y-m-d H:i:s', $next_payment_timestamp) : $next_payment_timestamp; return apply_filters('woocommerce_subscriptions_calculated_next_payment_date', $next_payment, $order, $product_id, $type, $from_date, $from_date_arg); }
/** * Records the initial payment against a subscription. * * This function is called when a gateway calls @see WC_Order::payment_complete() and payment * is completed on an order. It is also called when an orders status is changed to completed or * processing for those gateways which never call @see WC_Order::payment_complete(), like the * core WooCommerce Cheque and Bank Transfer gateways. * * @param $order WC_Order | int A WC_Order object or ID of a WC_Order order. * @since 1.1.2 */ public static function maybe_record_order_payment($order) { if (!is_object($order)) { $order = new WC_Order($order); } $subscriptions_in_order = self::get_recurring_items($order); foreach ($subscriptions_in_order as $subscription_item) { $subscription_key = WC_Subscriptions_Manager::get_subscription_key($order->id, $subscription_item['id']); $subscription = WC_Subscriptions_Manager::get_subscription($subscription_key, $order->customer_user); // No payments have been recorded yet if (empty($subscription['completed_payments'])) { // Don't duplicate orders remove_action('processed_subscription_payment', 'WC_Subscriptions_Renewal_Order::generate_paid_renewal_order', 10, 2); WC_Subscriptions_Manager::process_subscription_payments_on_order($order->id); WC_Subscriptions_Manager::safeguard_scheduled_payments($order->customer_user, $subscription_key); // Make sure orders are still generated for other payments in the same request add_action('processed_subscription_payment', 'WC_Subscriptions_Renewal_Order::generate_paid_renewal_order', 10, 2); } } }
/** * Override the default PayPal standard args in WooCommerce for subscription purchases. * * Based on the HTML Variables documented here: https://developer.paypal.com/webapps/developer/docs/classic/paypal-payments-standard/integration-guide/Appx_websitestandard_htmlvariables/#id08A6HI00JQU * * @since 1.0 */ public static function paypal_standard_subscription_args($paypal_args) { extract(self::get_order_id_and_key($paypal_args)); if (WC_Subscriptions_Order::order_contains_subscription($order_id) && 'yes' !== get_option(WC_Subscriptions_Admin::$option_prefix . '_turn_off_automatic_payments', 'no')) { $order = new WC_Order($order_id); $order_items = $order->get_items(); // Only one subscription allowed in the cart when PayPal Standard is active $product = $order->get_product_from_item(array_pop($order_items)); // It's a subscription $paypal_args['cmd'] = '_xclick-subscriptions'; if (count($order->get_items()) > 1) { foreach ($order->get_items() as $item) { if ($item['qty'] > 1) { $item_names[] = $item['qty'] . ' x ' . $item['name']; } else { if ($item['qty'] > 0) { $item_names[] = $item['name']; } } } $paypal_args['item_name'] = sprintf(__('Order %s', WC_Subscriptions::$text_domain), $order->get_order_number()); } else { $paypal_args['item_name'] = $product->get_title(); } $unconverted_periods = array('billing_period' => WC_Subscriptions_Order::get_subscription_period($order), 'trial_period' => WC_Subscriptions_Order::get_subscription_trial_period($order)); $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 = WC_Subscriptions_Order::get_recurring_total($order); $subscription_interval = WC_Subscriptions_Order::get_subscription_interval($order); $subscription_length = WC_Subscriptions_Order::get_subscription_length($order); $subscription_installments = $subscription_length / $subscription_interval; $is_payment_change = WC_Subscriptions_Change_Payment_Gateway::$is_request_to_change_payment; $is_switch_order = WC_Subscriptions_Switcher::order_contains_subscription_switch($order->id); $sign_up_fee = $is_payment_change ? 0 : WC_Subscriptions_Order::get_sign_up_fee($order); $initial_payment = $is_payment_change ? 0 : WC_Subscriptions_Order::get_total_initial_payment($order); 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 $paypal_args['invoice'] = $paypal_args['invoice'] . '-wcscpm-' . wp_create_nonce(); // Set a flag on the order if changing from PayPal *to* PayPal to prevent incorrectly cancelling the subscription if ('paypal' == $order->recurring_payment_method) { add_post_meta($order_id, '_wcs_changing_payment_from_paypal_to_paypal', 'true', true); } } // 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 if ($is_payment_change || $is_switch_order) { $subscription_key = WC_Subscriptions_Manager::get_subscription_key($order_id, $product->id); // Give a free trial until the next payment date $next_payment_timestamp = WC_Subscriptions_Manager::get_next_payment_date($subscription_key, $order->user_id, 'timestamp'); // When the subscription is on hold if ($next_payment_timestamp != false) { $trial_until = self::calculate_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']; } // If 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 -= WC_Subscriptions_Manager::get_subscriptions_completed_payment_count($subscription_key); } } else { $subscription_trial_length = WC_Subscriptions_Order::get_subscription_trial_length($order); } if ($subscription_trial_length > 0) { // Specify a free trial period if ($is_switch_order) { $paypal_args['a1'] = $initial_payment > 0 ? $initial_payment : 0; } else { $paypal_args['a1'] = $sign_up_fee > 0 ? $sign_up_fee : 0; } // Maybe add the sign up fee to the free trial period // 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 (WC_Subscriptions_Change_Payment_Gateway::$is_request_to_change_payment && $second_trial_length > 0) { $paypal_args['a2'] = 0; $paypal_args['p2'] = $second_trial_length; $paypal_args['t2'] = $second_trial_period; } } elseif ($sign_up_fee > 0 || $initial_payment !== $price_per_period) { // No trial period, so charge sign up fee and per period price for the first period if ($subscription_installments == 1) { $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) || $param_number == 1) { // 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 ($subscription_installments == 1 || $sign_up_fee > 0 && $subscription_trial_length == 0 && $subscription_installments == 2) { // Non-recurring payments $paypal_args['src'] = 0; } else { $paypal_args['src'] = 1; if ($subscription_installments > 0) { if ($sign_up_fee > 0 && $subscription_trial_length == 0) { // An initial period is being used to charge a sign-up fee $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; } return $paypal_args; }
/** * Checks if a user (by email) has bought an item. * @access public * @since 1.0.0 * @param string $customer_email * @param int $user_id * @param int $product_id * @return bool */ public static function sensei_customer_bought_product($customer_email, $user_id, $product_id) { global $wpdb; $emails = array(); if ($user_id) { $user = get_user_by('id', intval($user_id)); $emails[] = $user->user_email; } if (is_email($customer_email)) { $emails[] = $customer_email; } if (sizeof($emails) == 0) { return false; } $orders = get_posts(array('posts_per_page' => -1, 'meta_key' => '_customer_user', 'meta_value' => intval($user_id), 'post_type' => 'shop_order', 'post_status' => array('wc-processing', 'wc-completed'))); foreach ($orders as $order_id) { $order = new WC_Order($order_id->ID); if ($order->post_status == 'wc-completed') { if (0 < sizeof($order->get_items())) { foreach ($order->get_items() as $item) { // Allow product ID to be filtered $product_id = apply_filters('sensei_bought_product_id', $product_id, $order); // Check if user has bought product if ($item['product_id'] == $product_id || $item['variation_id'] == $product_id) { // Check if user has an active subscription for product if (class_exists('WC_Subscriptions_Manager')) { $sub_key = WC_Subscriptions_Manager::get_subscription_key($order_id->ID, $product_id); if ($sub_key) { $sub = WC_Subscriptions_Manager::get_subscription($sub_key); if ($sub && isset($sub['status'])) { if ('active' == $sub['status']) { return true; } else { return false; } } } } // Customer has bought product return true; } // End If Statement } // End For Loop } // End If Statement } // End If Statement } // End For Loop }
/** * Make sure when calculating the first payment date for a switched subscription, the date takes into * account the switch (i.e. prepaid days and possibly a downgrade). * * @since 1.4 * @deprecated 2.0 */ public static function calculate_first_payment_date($next_payment_date, $order, $product_id, $type) { _deprecated_function(__METHOD__, '2.0'); return self::get_first_payment_date($next_payment_date, WC_Subscriptions_Manager::get_subscription_key($order->id, $product_id), $order->user_id, $type); }
/** * Operaciones sucesivas * */ function scheduled_subscription_payment($amount_to_charge, $order, $product_id) { $this->write_log('scheduled_subscription_payment: ' . $amount_to_charge . '€ ' . $order->id); $client = $this->get_client(); // Obtenemos el numero de pago de la suscripcion $subscription_key = WC_Subscriptions_Manager::get_subscription_key($order->id, $product_id); $num_pago = WC_Subscriptions_Manager::get_subscriptions_completed_payment_count($subscription_key); $paytpv_order_ref = $order->id . "_" . $num_pago; $importe = number_format($amount_to_charge * 100, 0, '.', ''); // Obtenemos el terminal para el pedido $arrTerminalData = $this->TerminalCurrency($order); $currency_iso_code = $arrTerminalData["currency_iso_code"]; $term = $arrTerminalData["term"]; $pass = $arrTerminalData["pass"]; $payptv_iduser = get_post_meta((int) $order->id, 'PayTPV_IdUser', true); $payptv_tokenuser = get_post_meta((int) $order->id, 'PayTPV_TokenUser', true); $result = $client->execute_purchase($order, $payptv_iduser, $payptv_tokenuser, $term, $pass, $currency_iso_code, $importe, $paytpv_order_ref); if ((int) $result['DS_RESPONSE'] == 1) { update_post_meta($order->id, 'PayTPV_Referencia', $result['DS_MERCHANT_ORDER']); update_post_meta($order->id, '_transaction_id', $result['DS_MERCHANT_AUTHCODE']); WC_Subscriptions_Manager::process_subscription_payments_on_order($order); } }
/** * Checks if an order contains an in active subscription and if it does, denies download acces * to files purchased on the order. * * @return bool False if the order contains a subscription that has expired or is cancelled/on-hold, otherwise, the original value of $download_permitted * @since 1.3 */ public static function is_download_permitted($download_permitted, $order) { if (self::order_contains_subscription($order)) { foreach (self::get_recurring_items($order) as $order_item) { $subscription_key = WC_Subscriptions_Manager::get_subscription_key($order->id, self::get_items_product_id($order_item)); $subscription = WC_Subscriptions_Manager::get_subscription($subscription_key); if (!isset($subscription['status']) || 'active' !== $subscription['status']) { $download_permitted = false; break; } } } return $download_permitted; }
/** * Checks if a user has bought a product item. * * @since 1.9.0 * * @param int $user_id * @param int $product_id * * @return bool */ public static function has_customer_bought_product($user_id, $product_id) { $orders = get_posts(array('posts_per_page' => -1, 'meta_key' => '_customer_user', 'meta_value' => intval($user_id), 'post_type' => 'shop_order', 'post_status' => array('wc-processing', 'wc-completed'))); foreach ($orders as $order_id) { $order = new WC_Order($order_id->ID); if ($order->post_status != 'wc-completed' && $order->post_status != 'wc-processing') { continue; } if (!(0 < sizeof($order->get_items()))) { continue; } foreach ($order->get_items() as $item) { // Check if user has bought product if ($item['product_id'] == $product_id || $item['variation_id'] == $product_id) { // Check if user has an active subscription for product if (class_exists('WC_Subscriptions_Manager')) { $sub_key = WC_Subscriptions_Manager::get_subscription_key($order_id->ID, $product_id); if ($sub_key) { $sub = WC_Subscriptions_Manager::get_subscription($sub_key); if ($sub && isset($sub['status'])) { if ('active' == $sub['status']) { return true; } else { return false; } } } } // Customer has bought product return true; } // End If Statement } // End For each item } // End For each order }
/** * Make sure when calculating the first payment date for a switched subscription, the date takes into * account the switch (i.e. prepaid days and possibly a downgrade). * * @since 1.4 */ public static function calculate_first_payment_date($next_payment_date, $order, $product_id, $type) { return self::get_first_payment_date($next_payment_date, WC_Subscriptions_Manager::get_subscription_key($order->id, $product_id), $order->user_id, $type); }
/** * In typical PayPal style, there are a couple of important limitations we need to work around: * * 1. PayPal does not support subscriptions with a $0 recurring total. As a result, we treat it * as a normal purchase and then handle the subscription renewals here. * * 2. PayPal make no guarantee about when a recurring payment will be charged. This creates issues for * suspending a subscription until the payment is processed. Specifically, if PayPal processed a payment * *before* it was due, we can't suspend the subscription when it is due because it will remain suspended * until the next payment. As a result, subscriptions for PayPal are not suspended. However, if there was * an issue with the subscription sign-up or payment that was not correctly reported to the store, then the * subscription would remain active. No renewal order would be generated, because no payments are completed, * so physical subscriptions would not be affected, however, subscriptions to digital goods would be affected. * * @since 1.4.3 */ public static function scheduled_subscription_payment($amount_to_charge, $order, $product_id) { $hook_args = array('subscription_key' => WC_Subscriptions_Manager::get_subscription_key($order->id, $product_id)); $one_day_from_now = gmdate('U') + 60 * 60 * 24; wc_schedule_single_action($one_day_from_now, 'paypal_check_subscription_payment', $hook_args); }
private function processSubscriptions() { global $wpdb; // check wether subscriptions addon is activated if (class_exists('WC_Subscriptions_Order') && WC_Subscriptions_Order::order_contains_subscription($this->order)) { $products = $this->order->get_items(); foreach ($products as $product) { if (is_array($product) && isset($product['product_id']) && intval($product['product_id']) > 0 && isset($product['subscription_period']) && $product['subscription_period'] != '') { // product is a subscription? $woo_sub_key = WC_Subscriptions_Manager::get_subscription_key($this->order_id, $product['product_id']); // required vars $amount = floatval(WC_Subscriptions_Order::get_recurring_total($this->order)) * 100; $currency = get_woocommerce_currency(); $interval = intval($product['subscription_interval']); $period = strtoupper($product['subscription_period']); $length = strtoupper($product['subscription_length']); if ($length > 0) { $periodOfValidity = $length . ' ' . $period; } else { $periodOfValidity = false; } $trial_end = strtotime(WC_Subscriptions_Product::get_trial_expiration_date($product['product_id'], get_gmt_from_date($this->order->order_date))); if ($trial_end === false) { $trial_time = 0; } else { $datediff = $trial_end - time(); $trial_time = ceil($datediff / (60 * 60 * 24)); } // md5 name $woo_sub_md5 = md5($amount . $currency . $interval . $trial_time); // get offer $name = 'woo_' . $product['product_id'] . '_' . $woo_sub_md5; $offer = $this->subscriptions->offerGetDetailByName($name); // check wether offer exists in paymill if ($offer === false) { // offer does not exist in paymill yet, create it $params = array('amount' => $amount, 'currency' => $currency, 'interval' => $interval . ' ' . $period, 'name' => $name, 'trial_period_days' => intval($trial_time)); $offer = $this->subscriptions->offerCreate($params); if ($GLOBALS['paymill_loader']->paymill_errors->status()) { $GLOBALS['paymill_loader']->paymill_errors->getErrors(); return false; } } // create user subscription $user_sub = $this->subscriptions->create($this->clientClass->getCurrentClientID(), $offer, $this->paymentClass->getPaymentID(), isset($_POST['paymill_delivery_date']) ? $_POST['paymill_delivery_date'] : false, $periodOfValidity); if ($GLOBALS['paymill_loader']->paymill_errors->status()) { //maybe offer cache is outdated, recache and try again $GLOBALS['paymill_loader']->paymill_errors->reset(); // reset error status $this->subscriptions->offerGetList(true); $params = array('amount' => $amount, 'currency' => $currency, 'interval' => $interval . ' ' . $period, 'name' => $name, 'trial_period_days' => intval($trial_time)); $offer = $this->subscriptions->offerCreate($params); if ($GLOBALS['paymill_loader']->paymill_errors->status()) { $GLOBALS['paymill_loader']->paymill_errors->getErrors(); return false; } $user_sub = $this->subscriptions->create($this->clientClass->getCurrentClientID(), $offer, $this->paymentClass->getPaymentID(), isset($_POST['paymill_delivery_date']) ? $_POST['paymill_delivery_date'] : false, $periodOfValidity); if ($GLOBALS['paymill_loader']->paymill_errors->status()) { $GLOBALS['paymill_loader']->paymill_errors->getErrors(); return false; } } $wpdb->query($wpdb->prepare('INSERT INTO ' . $wpdb->prefix . 'paymill_subscriptions (paymill_sub_id, woo_user_id, woo_offer_id) VALUES (%s, %s, %s)', array($user_sub, get_current_user_id(), $woo_sub_key))); // subscription successful do_action('paymill_woocommerce_subscription_created', array('product_id' => $product['product_id'], 'offer_id' => $offer)); return true; } } } else { return true; } }
/** * @param WC_Order $order */ public function payment_failed_for_order($order) { if (1 == get_option('fue_subscription_failure_notification', 0)) { // notification enabled $emails_string = get_option('fue_subscription_failure_notification_emails', ''); if (empty($emails_string)) { return; } // get the product id to get the subscription string $order_items = WC_Subscriptions_Order::get_recurring_items($order); $first_order_item = reset($order_items); $product_id = WC_Subscriptions_Order::get_items_product_id($first_order_item); $subs_key = WC_Subscriptions_Manager::get_subscription_key($order->id, $product_id); $subject = sprintf(__('Subscription payment failed for Order %s'), $order->get_order_number()); $message = sprintf(__('A subscription payment for the order %s has failed. The subscription has now been automatically put on hold.'), $order->get_order_number()); $recipients = array(); if (strpos($emails_string, ',') !== false) { $recipients = array_map('trim', explode(',', $emails_string)); } else { $recipients = array($emails_string); } foreach ($recipients as $email) { FUE::mail($email, $subject, $message); } } }
/** * Once payment is completed on an order, set a lock on payments until the next subscription payment period. * * @param $user_id int The id of the user who purchased the subscription * @param $subscription_key string A subscription key of the form created by @see self::get_subscription_key() * @since 1.1.2 */ public static function safeguard_scheduled_payments($order_id) { $order = new WC_Order($order_id); $subscription_key = WC_Subscriptions_Manager::get_subscription_key($order_id); WC_Subscriptions_Manager::safeguard_scheduled_payments($order->customer_user, $subscription_key); }
/** * 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) { if (!in_array($transaction_details['txn_type'], array('subscr_signup', 'subscr_payment', 'subscr_cancel', 'subscr_eot', 'subscr_failed', 'subscr_modify'))) { return; } if (empty($transaction_details['custom']) || empty($transaction_details['invoice'])) { 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 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.'); } return; } 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']); update_post_meta($order_id, 'PayPal Subscriber ID', $transaction_details['subscr_id']); // Payment completed $order->add_order_note(__('IPN subscription sign up completed.', WC_Subscriptions::$text_domain)); if (self::$debug) { self::$log->add('paypal', 'IPN subscription sign up completed for order ' . $order_id); } // When there is a free trial & no initial payment amount, we need to mark the order as paid and activate the subscription if (0 == WC_Subscriptions_Order::get_total_initial_payment($order) && WC_Subscriptions_Order::get_subscription_trial_length($order) > 0) { $order->payment_complete(); WC_Subscriptions_Manager::activate_subscriptions_for_order($order); } 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.', WC_Subscriptions::$text_domain)); if (self::$debug) { self::$log->add('paypal', 'IPN subscription payment completed for order ' . $order_id); } $subscriptions_in_order = WC_Subscriptions_Order::get_recurring_items($order); $subscription_item = array_pop($subscriptions_in_order); $subscription_key = WC_Subscriptions_Manager::get_subscription_key($order->id, $subscription_item['id']); $subscription = WC_Subscriptions_Manager::get_subscription($subscription_key, $order->customer_user); // First payment on order, process payment & activate subscription if (empty($subscription['completed_payments'])) { $order->payment_complete(); WC_Subscriptions_Manager::activate_subscriptions_for_order($order); } else { WC_Subscriptions_Manager::process_subscription_payments_on_order($order); } } elseif ('failed' == strtolower($transaction_details['payment_status'])) { // Subscription Payment completed $order->add_order_note(__('IPN subscription payment failed.', WC_Subscriptions::$text_domain)); if (self::$debug) { self::$log->add('paypal', 'IPN subscription payment failed for order ' . $order_id); } 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': if (self::$debug) { self::$log->add('paypal', 'IPN subscription cancelled for order ' . $order_id); } // Subscription Payment completed $order->add_order_note(__('IPN subscription cancelled for order.', WC_Subscriptions::$text_domain)); WC_Subscriptions_Manager::cancel_subscriptions_for_order($order); break; case 'subscr_eot': // Subscription ended, either due to failed payments or expiration // PayPal fires the 'subscr_eot' notice immediately if a subscription is only for one billing period, so ignore the request when we only have one billing period if (1 != WC_Subscriptions_Order::get_subscription_length($order)) { if (self::$debug) { self::$log->add('paypal', 'IPN subscription end-of-term for order ' . $order_id); } // Record subscription ended $order->add_order_note(__('IPN subscription end-of-term for order.', WC_Subscriptions::$text_domain)); // Ended due to failed payments so cancel the subscription if (time() < strtotime(WC_Subscriptions_Manager::get_subscription_expiration_date(WC_Subscriptions_Manager::get_subscription_key($order->id), $order->customer_user))) { WC_Subscriptions_Manager::cancel_subscriptions_for_order($order); } else { WC_Subscriptions_Manager::expire_subscriptions_for_order($order); } } break; case 'subscr_failed': // Subscription sign up failed if (self::$debug) { self::$log->add('paypal', 'IPN subscription sign up failure for order ' . $order_id); } // Subscription Payment completed $order->add_order_note(__('IPN subscription sign up failure.', WC_Subscriptions::$text_domain)); WC_Subscriptions_Manager::failed_subscription_sign_ups_for_order($order); break; } // Prevent default IPN handling for subscription txn_types exit; }