/** * Setup the cart for paying for a delayed initial payment for a subscription. * * @since 2.0 */ public function maybe_setup_cart() { global $wp; if (isset($_GET['pay_for_order']) && isset($_GET['key']) && isset($wp->query_vars['order-pay'])) { // Pay for existing order $order_key = $_GET['key']; $order_id = isset($wp->query_vars['order-pay']) ? $wp->query_vars['order-pay'] : absint($_GET['order_id']); $order = wc_get_order($wp->query_vars['order-pay']); if ($order->order_key == $order_key && $order->has_status(array('pending', 'failed')) && !wcs_order_contains_subscription($order, array('renewal', 'resubscribe'))) { $subscriptions = wcs_get_subscriptions_for_order($order, array('order_type' => 'parent')); if (get_current_user_id() !== $order->get_user_id()) { wc_add_notice(__('That doesn\'t appear to be your order.', 'woocommerce-subscriptions'), 'error'); wp_safe_redirect(get_permalink(wc_get_page_id('myaccount'))); exit; } elseif (!empty($subscriptions)) { // Setup cart with all the original order's line items $this->setup_cart($order, array('order_id' => $order_id)); WC()->session->set('order_awaiting_payment', $order_id); // Set cart hash for orders paid in WC >= 2.6 $this->set_cart_hash($order_id); wp_safe_redirect(WC()->cart->get_checkout_url()); exit; } } } }
/** * Displays the renewal orders in the Related Orders meta box. * * @param object $post A WordPress post * @since 2.0 */ public static function output_rows($post) { $subscriptions = array(); $orders = array(); // On the subscription page, just show related orders if (wcs_is_subscription($post->ID)) { $subscriptions[] = wcs_get_subscription($post->ID); } elseif (wcs_order_contains_subscription($post->ID, array('parent', 'renewal'))) { $subscriptions = wcs_get_subscriptions_for_order($post->ID, array('order_type' => array('parent', 'renewal'))); } // First, display all the subscriptions foreach ($subscriptions as $subscription) { $subscription->relationship = __('Subscription', 'woocommerce-subscriptions'); $orders[] = $subscription; } //Resubscribed $initial_subscriptions = array(); if (wcs_is_subscription($post->ID)) { $initial_subscriptions = wcs_get_subscriptions_for_resubscribe_order($post->ID); $resubscribed_subscriptions = get_posts(array('meta_key' => '_subscription_resubscribe', 'meta_value' => $post->ID, 'post_type' => 'shop_subscription', 'post_status' => 'any', 'posts_per_page' => -1)); foreach ($resubscribed_subscriptions as $subscription) { $subscription = wcs_get_subscription($subscription); $subscription->relationship = _x('Resubscribed Subscription', 'relation to order', 'woocommerce-subscriptions'); $orders[] = $subscription; } } else { if (wcs_order_contains_subscription($post->ID, array('resubscribe'))) { $initial_subscriptions = wcs_get_subscriptions_for_order($post->ID, array('order_type' => array('resubscribe'))); } } foreach ($initial_subscriptions as $subscription) { $subscription->relationship = _x('Initial Subscription', 'relation to order', 'woocommerce-subscriptions'); $orders[] = $subscription; } // Now, if we're on a single subscription or renewal order's page, display the parent orders if (1 == count($subscriptions)) { foreach ($subscriptions as $subscription) { if (false !== $subscription->order) { $subscription->order->relationship = _x('Parent Order', 'relation to order', 'woocommerce-subscriptions'); $orders[] = $subscription->order; } } } // Finally, display the renewal orders foreach ($subscriptions as $subscription) { foreach ($subscription->get_related_orders('all', 'renewal') as $order) { $order->relationship = _x('Renewal Order', 'relation to order', 'woocommerce-subscriptions'); $orders[] = $order; } } $orders = apply_filters('woocommerce_subscriptions_admin_related_orders_to_display', $orders, $subscriptions, $post); foreach ($orders as $order) { if ($order->id == $post->ID) { continue; } include 'views/html-related-orders-row.php'; } }
/** * Add WC Meta boxes */ public function add_meta_boxes() { global $current_screen, $post_ID; add_meta_box('woocommerce-subscription-data', _x('Subscription Data', 'meta box title', 'woocommerce-subscriptions'), 'WCS_Meta_Box_Subscription_Data::output', 'shop_subscription', 'normal', 'high'); add_meta_box('woocommerce-subscription-schedule', _x('Billing Schedule', 'meta box title', 'woocommerce-subscriptions'), 'WCS_Meta_Box_Schedule::output', 'shop_subscription', 'side', 'default'); remove_meta_box('woocommerce-order-data', 'shop_subscription', 'normal'); add_meta_box('subscription_renewal_orders', __('Related Orders', 'woocommerce-subscriptions'), 'WCS_Meta_Box_Related_Orders::output', 'shop_subscription', 'normal', 'low'); // Only display the meta box if an order relates to a subscription if ('shop_order' === get_post_type($post_ID) && wcs_order_contains_subscription($post_ID, 'any')) { add_meta_box('subscription_renewal_orders', __('Related Orders', 'woocommerce-subscriptions'), 'WCS_Meta_Box_Related_Orders::output', 'shop_order', 'normal', 'low'); } }
/** * Updates other subscription sources. */ protected function save_source($order, $source) { parent::save_source($order, $source); // Also store it on the subscriptions being purchased or paid for in the order if (wcs_order_contains_subscription($order->id)) { $subscriptions = wcs_get_subscriptions_for_order($order->id); } elseif (wcs_order_contains_renewal($order->id)) { $subscriptions = wcs_get_subscriptions_for_renewal_order($order->id); } else { $subscriptions = array(); } foreach ($subscriptions as $subscription) { update_post_meta($subscription->id, '_stripe_customer_id', $source->customer); update_post_meta($subscription->id, '_stripe_card_id', $source->source); } }
/** * Only display the gateways which support subscriptions if manual payments are not allowed. * * @since 1.0 */ public static function get_available_payment_gateways($available_gateways) { if (WC_Subscriptions_Cart::cart_contains_subscription() || isset($_GET['order_id']) && wcs_order_contains_subscription($_GET['order_id'])) { $accept_manual_renewals = 'no' !== get_option(WC_Subscriptions_Admin::$option_prefix . '_accept_manual_renewals', 'no') ? true : false; $subscriptions_in_cart = count(WC()->cart->recurring_carts); foreach ($available_gateways as $gateway_id => $gateway) { $supports_subscriptions = $gateway->supports('subscriptions'); // Remove the payment gateway if there are multiple subscriptions in the cart and this gateway either doesn't support multiple subscriptions or isn't manual (all manual gateways support multiple subscriptions) if ($subscriptions_in_cart > 1 && $gateway->supports('multiple_subscriptions') !== true && ($supports_subscriptions || !$accept_manual_renewals)) { unset($available_gateways[$gateway_id]); // If there is just the one subscription the cart, remove the payment gateway if manual renewals are disabled and this gateway doesn't support automatic payments } elseif (!$supports_subscriptions && !$accept_manual_renewals) { unset($available_gateways[$gateway_id]); } } } return $available_gateways; }
/** * Process an initial subscription payment if the order contains a * subscription, otherwise use the parent::process_payment() method * * @since 1.4 * @param int $order_id the order identifier * @return array */ public function process_payment($order_id) { require_once 'class-wc-realex-api.php'; // processing subscription (which means we are ineligible for 3DSecure for now) if (SV_WC_Plugin_Compatibility::is_wc_subscriptions_version_gte_2_0() ? wcs_order_contains_subscription($order_id) : WC_Subscriptions_Order::order_contains_subscription($order_id)) { $order = wc_get_order($order_id); // redirect to payment page for payment if 3D secure is enabled if ($this->get_threedsecure()->is_3dsecure_available() && !SV_WC_Helper::get_post('woocommerce_pay_page')) { // empty cart before redirecting from Checkout page WC()->cart->empty_cart(); // redirect to payment page to continue payment return array('result' => 'success', 'redirect' => $order->get_checkout_payment_url(true)); } $order->payment_total = SV_WC_Helper::number_format(SV_WC_Plugin_Compatibility::is_wc_subscriptions_version_gte_2_0() ? $order->get_total() : WC_Subscriptions_Order::get_total_initial_payment($order)); // create the realex api client $realex_client = new Realex_API($this->get_endpoint_url(), $this->get_realvault_endpoint_url(), $this->get_shared_secret()); // create the customer/cc tokens, and authorize the initial payment amount, if any $result = $this->authorize($realex_client, $order); // subscription with initial payment, everything is now taken care of if (is_array($result)) { // for Subscriptions 2.0.x, save payment token to subscription object if (SV_WC_Plugin_Compatibility::is_wc_subscriptions_version_gte_2_0()) { // a single order can contain multiple subscriptions foreach (wcs_get_subscriptions_for_order($order->id) as $subscription) { // payment token update_post_meta($subscription->id, '_realex_cardref', get_post_meta($order->id, '_realex_cardref', true)); } } return $result; } // otherwise there was no initial payment, so we mark the order as complete, etc if ($order->payment_total == 0) { // mark order as having received payment $order->payment_complete(); WC()->cart->empty_cart(); return array('result' => 'success', 'redirect' => $this->get_return_url($order)); } } else { // processing regular product return parent::process_payment($order_id); } }
/** * Displays the renewal orders in the Related Orders meta box. * * @param object $post A WordPress post * @since 2.0 */ public static function output_rows($post) { $subscriptions = array(); $orders = array(); // On the subscription page, just show related orders if (wcs_is_subscription($post->ID)) { $subscriptions[] = wcs_get_subscription($post->ID); } elseif (wcs_order_contains_subscription($post->ID, array('parent', 'renewal'))) { $subscriptions = wcs_get_subscriptions_for_order($post->ID, array('order_type' => array('parent', 'renewal'))); } // First, display all the subscriptions foreach ($subscriptions as $subscription) { $subscription->relationship = _x('Subscription', 'relation to order', 'woocommerce-subscriptions'); $orders[] = $subscription; } // Now, if we're on a single subscription or renewal order's page, display the parent orders if (1 == count($subscriptions)) { foreach ($subscriptions as $subscription) { if (false !== $subscription->order) { $subscription->order->relationship = _x('Parent Order', 'relation to order', 'woocommerce-subscriptions'); $orders[] = $subscription->order; } } } // Finally, display the renewal orders foreach ($subscriptions as $subscription) { foreach ($subscription->get_related_orders('all', 'renewal') as $order) { $order->relationship = _x('Renewal Order', 'relation to order', 'woocommerce-subscriptions'); $orders[] = $order; } } foreach ($orders as $order) { if ($order->id == $post->ID) { continue; } include 'views/html-related-orders-row.php'; } }
/** * Created a new order for renewing a subscription product based on the details of a previous order. * * @param WC_Order|int $order The WC_Order object or ID of the order for which the a new order should be created. * @param string $product_id The ID of the subscription product in the order which needs to be added to the new order. * @param array $args (optional) An array of name => value flags: * 'new_order_role' string A flag to indicate whether the new order should become the master order for the subscription. Accepts either 'parent' or 'child'. Defaults to 'parent' - replace the existing order. * 'checkout_renewal' bool Indicates if invoked from an interactive cart/checkout session and certain order items are not set, like taxes, shipping as they need to be set in teh calling function, like @see WC_Subscriptions_Checkout::filter_woocommerce_create_order(). Default false. * 'failed_order_id' int For checkout_renewal true, indicates order id being replaced * @since 1.2 * @deprecated 2.0 */ public static function generate_renewal_order($original_order, $product_id, $args = array()) { _deprecated_function(__METHOD__, '2.0', 'wcs_create_renewal_order() or wcs_create_resubscribe_order()'); if (!wcs_order_contains_subscription($original_order, 'parent')) { return false; } $args = wp_parse_args($args, array('new_order_role' => 'parent', 'checkout_renewal' => false)); $subscriptions = wcs_get_subscriptions_for_order($original_order, array('order_type' => 'parent')); $subscription = array_shift($subscriptions); if ('parent' == $args['new_order_role']) { $new_order = wcs_create_resubscribe_order($subscription); } else { $new_order = wcs_create_renewal_order($subscription); } return $new_order->id; }
/** * Checks an order to see if it contains a subscription. * * @param mixed $order A WC_Order object or the ID of the order which the subscription was purchased in. * @return bool True if the order contains a subscription, otherwise false. * @version 1.2 * @since 1.0 */ public static function order_contains_subscription($order) { _deprecated_function(__METHOD__, '2.0', 'wcs_order_contains_subscription( $order )'); return wcs_order_contains_subscription($order); }
/** * Adjust renew membership URL for subscription-based memberships * * @since 1.0.0 * @param string $url Renew membership URL * @param WC_Memberships_User_Membership $user_membership * @return string Modified renew URL */ public function renew_membership_url($url, WC_Memberships_User_Membership $user_membership) { if ($this->has_membership_plan_subscription($user_membership->get_plan_id())) { // note that we must also check if order contains a subscription since users // can be manually-assigned to memberships and not have an associated subscription // 2.0 onwards if ($this->is_subscriptions_gte_2_0()) { if (wcs_order_contains_subscription($user_membership->get_order())) { $url = wcs_get_users_resubscribe_link($this->get_user_membership_subscription_id($user_membership->get_id())); } } else { if (WC_Subscriptions_Order::order_contains_subscription($user_membership->get_order())) { $subscription_key = $this->get_user_membership_subscription_key($user_membership->get_id()); $url = WC_Subscriptions_Renewal_Order::get_users_renewal_link($subscription_key); } } } return $url; }
/** * Automatically set the order's status to complete if all the subscriptions in an order * are synced and the order total is zero. * * @since 1.5.17 */ public static function order_autocomplete($new_order_status, $order_id) { $order = wc_get_order($order_id); if ('processing' == $new_order_status && $order->get_total() == 0 && wcs_order_contains_subscription($order)) { $subscriptions = wcs_get_subscriptions_for_order($order_id); $all_synced = true; foreach ($subscriptions as $subscription_id => $subscription) { if (!self::subscription_contains_synced_product($subscription_id)) { $all_synced = false; break; } } if ($all_synced) { $new_order_status = 'completed'; } } return $new_order_status; }
/** * WooCommerce's function receives the original order ID, the item and the list of files. This does not work for * download permissions stored on the subscription rather than the original order as the URL would have the wrong order * key. This function takes the same parameters, but queries the database again for download ids belonging to all the * subscriptions that were in the original order. Then for all subscriptions, it checks all items, and if the item * passed in here is in that subscription, it creates the correct download link to be passsed to the email. * * @param array $files List of files already included in the list * @param array $item An item (you get it by doing $order->get_items()) * @param WC_Order $order The original order * @return array List of files with correct download urls */ public static function get_item_downloads($files, $item, $order) { global $wpdb; if (wcs_order_contains_subscription($order, 'any')) { $subscriptions = wcs_get_subscriptions_for_order($order, array('order_type' => array('any'))); } else { return $files; } $product_id = wcs_get_canonical_product_id($item); foreach ($subscriptions as $subscription) { foreach ($subscription->get_items() as $subscription_item) { if (wcs_get_canonical_product_id($subscription_item) === $product_id) { $files = $subscription->get_item_downloads($subscription_item); } } } return $files; }
/** * Force tokenization for subscriptions, this can be forced either during checkout * or when the payment method for a subscription is being changed * * @since 4.1.0 * @see SV_WC_Payment_Gateway::tokenization_forced() * @param bool $force_tokenization whether tokenization should be forced * @return bool true if tokenization should be forced, false otherwise */ public function maybe_force_tokenization($force_tokenization) { // pay page with subscription? $pay_page_subscription = false; if ($this->get_gateway()->is_pay_page_gateway()) { $order_id = $this->get_gateway()->get_checkout_pay_page_order_id(); if ($order_id) { $pay_page_subscription = wcs_order_contains_subscription($order_id); } } if (WC_Subscriptions_Cart::cart_contains_subscription() || wcs_cart_contains_renewal() || WC_Subscriptions_Change_Payment_Gateway::$is_request_to_change_payment || $pay_page_subscription) { $force_tokenization = true; } return $force_tokenization; }
/** * Process the payment and return the result **/ function process_payment($order_id) { global $woocommerce; if ($this->direct_post_enabled()) { $this->params["card_token"] = $_POST['card_token']; } else { $this->params["card_number"] = str_replace(' ', '', $_POST['fatzebra-card-number']); if (!isset($_POST["fatzebra-card-number"])) { $this->params["card_number"] = $_POST['cardnumber']; } $this->params["cvv"] = $_POST["fatzebra-card-cvc"]; if (!isset($_POST['fatzebra-card-cvc'])) { $this->params["cvv"] = $_POST['card_cvv']; } if (isset($_POST['fatzebra-card-expiry']) && !empty($_POST['fatzebra-card-expiry'])) { list($exp_month, $exp_year) = explode('/', $_POST['fatzebra-card-expiry']); } else { $exp_month = $_POST['card_expiry_month']; $exp_year = $_POST['card_expiry_year']; } $this->params["card_expiry"] = trim($exp_month) . "/" . (2000 + intval($exp_year)); $this->params["card_holder"] = $_POST['billing_first_name'] . " " . $_POST['billing_last_name']; } $this->params["customer_ip"] = $this->get_customer_real_ip(); $defer_payment = $this->settings["deferred_payments"] == "yes"; $order = new WC_Order($order_id); $this->params["currency"] = $order->get_order_currency(); if (class_exists("WC_Subscriptions_Order") && wcs_order_contains_subscription($order)) { // No deferred payments for subscriptions. $defer_payment = false; // Charge sign up fee + first period here.. // Periodic charging should happen via scheduled_subscription_payment_fatzebra $this->params["amount"] = (int) ($order->get_total() * 100); } else { $this->params["amount"] = (int) ($order->order_total * 100); } $this->params["reference"] = (string) $order_id; $test_mode = $this->settings['test_mode'] == 'yes'; $this->params["test"] = $test_mode; $this->params["deferred"] = $defer_payment; if (trim($this->params["card_holder"]) == "") { // If the customer is updating their details the $_POST values for name will be missing, so fetch from the order $this->params["card_holder"] = $order->billing_first_name . " " . $order->billing_last_name; } if ($this->fraud_detection_enabled()) { // Add in the fraud data payload $fraud_data = $this->get_fraud_payload($order); $this->params['fraud'] = $fraud_data; } if ($this->params["amount"] === 0) { $result = $this->tokenize_card($this->params); } else { $result = $this->do_payment($this->params); } if (is_wp_error($result)) { switch ($result->get_error_code()) { case 1: // Non-200 response, so failed... (e.g. 401, 403, 500 etc). $order->add_order_note($result->get_error_message()); wc_add_notice($result->get_error_message(), 'error'); break; case 2: // Gateway error (data etc) $errors = $result->get_error_data(); foreach ($errors as $error) { $order->add_order_note("Gateway Error: " . $error); } error_log("WooCommerce Fat Zebra - Gateway Error: " . print_r($errors, true)); wc_add_notice("Payment Failed: " . implode(", ", $errors), 'error'); break; case 3: // Declined - error data is array with keys: message, id wc_add_notice("Payment declined: " . $this->response_data->response->message, 'error'); if (isset($this->response_data->response->fraud_result) && !empty($this->response_data->response->fraud_result)) { if ($this->response_data->response->fraud_result == 'Accept') { $order->add_order_note("Fraud Check Result: Accept"); } else { $order->add_order_note("Fraud Check Result: " . $this->response_data->response->fraud_result . " - " . implode(", ", $this->response_data->response->fraud_messages)); } } break; case 4: // Exception caught, something bad happened. Data is exception // Exception caught, something bad happened. Data is exception default: wc_add_notice("Unknown error.", 'error'); $order->add_order_note(__("Unknown Error (exception): " . print_r($result->get_error_data(), true))); break; } return; } else { // Success! Returned is an array with the transaction ID etc // For a deferred payment we set the status to on-hold and then add a detailed note for review. if ($defer_payment) { $date = new DateTime($result["card_expiry"], new DateTimeZone("Australia/Sydney")); $note = "Deferred Payment:<ul><li>Card Token: " . $result["card_token"] . "</li><li>Card Holder: " . $result["card_holder"] . "</li><li>Card Number: " . $result["card_number"] . "</li><li>Expiry: " . $date->format("m/Y") . "</li></ul>"; $order->update_status("on-hold", $note); update_post_meta($order_id, "_fatzebra_card_token", $result["card_token"]); update_post_meta($order_id, "fatzebra_card_token", $result["card_token"]); } else { if ($this->params["amount"] === 0) { $order->add_order_note(__("Fat Zebra payment complete - \$0 initial amount, card tokenized. Card token: " . $result["card_token"])); } else { $order->add_order_note(__("Fat Zebra payment complete. Reference: " . $result["transaction_id"])); } if (isset($this->response_data->response->fraud_result) && !empty($this->response_data->response->fraud_result)) { if ($this->response_data->response->fraud_result == 'Accept') { $order->add_order_note("Fraud Check Result: Accept"); } else { $order->add_order_note("Fraud Check Result: " . $this->response_data->response->fraud_result . " - " . implode(", ", $this->response_data->response->fraud_messages)); } } $order->payment_complete($result['transaction_id']); // Store the card token as post meta update_post_meta($order_id, "_fatzebra_card_token", $result["card_token"]); update_post_meta($order_id, "fatzebra_card_token", $result["card_token"]); update_post_meta($order_id, 'Fat Zebra Transaction ID', $result['transaction_id']); } $woocommerce->cart->empty_cart(); return array('result' => 'success', 'redirect' => $this->get_return_url($order)); } }
/** * Check if order contains subscriptions. * * @param int $order_id * @return bool */ protected function order_contains_subscription($order_id) { return function_exists('wcs_order_contains_subscription') && (wcs_order_contains_subscription($order_id) || wcs_order_contains_renewal($order_id)); }
/** * Check if the order has a subscription (either according to Subscriptions 1.5 or 2.0) * * @param string $order_id The ID of the order to check * @return mixed Either 1 (Subscriptions 1.5), 2 (Subscriptions 2) or false (no order) */ function order_has_subscription($order_id) { // Subscriptions not loaded if (!class_exists('WC_Subscriptions_Order')) return false; // Subscriptions v2.0 if (function_exists('wcs_order_contains_subscription')) { if (wcs_order_contains_subscription($order_id) || wcs_order_contains_renewal( $order_id ) || ( function_exists( 'wcs_is_subscription' ) && wcs_is_subscription( $order_id ))) { return 2; } else { return false; } } // Subscriptions v1.5 if (WC_Subscriptions_Order::order_contains_subscription($order_id)) { return 1; } return false; }
/** * Override quantities used to lower stock levels by when using synced subscriptions. If it's a synced product * that does not have proration enabled and the payment date is not today, do not lower stock levels. * * @param integer $qty the original quantity that would be taken out of the stock level * @param array $order order data * @param array $item item data for each item in the order * * @return int */ public static function maybe_do_not_reduce_stock($qty, $order, $order_item) { if (wcs_order_contains_subscription($order, array('parent', 'resubscribe')) && 0 == $order_item['line_total']) { $subscriptions = wcs_get_subscriptions_for_order($order); $product_id = wcs_get_canonical_product_id($order_item); foreach ($subscriptions as $subscription) { if (self::subscription_contains_synced_product($subscription) && $subscription->has_product($product_id)) { foreach ($subscription->get_items() as $subscription_item) { if (wcs_get_canonical_product_id($subscription_item) == $product_id && 0 < $subscription_item['line_total']) { $qty = 0; } } } } } return $qty; }
/** * Override the default PayPal standard args in WooCommerce for subscription purchases when * automatic payments are enabled and when the recurring order totals is over $0.00 (because * PayPal doesn't support subscriptions with a $0 recurring total, we need to circumvent it and * manage it entirely ourselves.) * * @since 2.0 */ public static function get_paypal_args($paypal_args, $order) { if (wcs_order_contains_subscription($order, array('parent', 'renewal', 'resubscribe', 'switch')) || wcs_is_subscription($order)) { if (self::are_reference_transactions_enabled()) { $paypal_args = self::get_api()->get_paypal_args($paypal_args, $order); } else { $paypal_args = WCS_PayPal_Standard_Request::get_paypal_args($paypal_args, $order); } } return $paypal_args; }
/** * Process the payment * * @param int $order_id * @return array */ public function process_payment($order_id, $retry = true) { // Processing subscription if (function_exists('wcs_order_contains_subscription') && (wcs_order_contains_subscription($order_id) || wcs_is_subscription($order_id) || wcs_order_contains_renewal($order_id))) { return $this->process_subscription($order_id, $retry); // Processing pre-order } elseif (class_exists('WC_Pre_Orders_Order') && WC_Pre_Orders_Order::order_contains_pre_order($order_id)) { return $this->process_pre_order($order_id, $retry); // Processing regular product } else { return parent::process_payment($order_id, $retry); } }