From 32e77897cc223dcedc524f72fd164ab8c7de44e9 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Fri, 6 Jun 2014 15:51:09 +0100 Subject: [PATCH 01/13] Placeholder functions --- includes/class-wc-order.php | 48 +++++++++++++++++++++++++++++---- includes/class-wc-post-data.php | 2 +- includes/wc-core-functions.php | 46 +++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 6 deletions(-) diff --git a/includes/class-wc-order.php b/includes/class-wc-order.php index 2344dc84339..06e1e775936 100644 --- a/includes/class-wc-order.php +++ b/includes/class-wc-order.php @@ -5,7 +5,7 @@ * The WooCommerce order class handles order data. * * @class WC_Order - * @version 2.1.0 + * @version 2.2.0 * @package WooCommerce/Classes * @category Class * @author WooThemes @@ -35,6 +35,48 @@ class WC_Order { } + + public function set_address( $address_data, $type = 'billing' ) { + update_post_meta( $order_id, '_' . $type . '_address', $postvalue ); + } + + public function set_payment_method( $payment_method ) { + + } + + public function add_item( $item_name, $meta = array() ) { + $item_id = wc_add_order_item( $this->id, array( + 'order_item_name' => $item_name, + 'order_item_type' => 'line_item' + ) ); + + if ( $item_id ) { + wc_add_order_item_meta( $item_id, '_qty', apply_filters( 'woocommerce_stock_amount', $values['quantity'] ) ); + + foreach ( $meta as $key => $value ) { + wc_add_order_item_meta( $item_id, $key, $value ); + } + } + } + + public function add_fee() { + + } + + public function add_tax() { + + } + + public function add_shipping() { + + } + + + + + + + /** * Gets an order from the database. * @@ -160,7 +202,6 @@ class WC_Order { if ( $key == $this->order_key ) { return true; } - return false; } @@ -260,7 +301,6 @@ class WC_Order { return $this->formatted_shipping_address; } - /** * Get the shipping address in an array. * @@ -1607,10 +1647,8 @@ class WC_Order { $this->add_order_note( __( 'Order item stock reduced successfully.', 'woocommerce' ) ); } - } - /** * send_stock_notifications function. * diff --git a/includes/class-wc-post-data.php b/includes/class-wc-post-data.php index 999f487b018..b060259d1fd 100644 --- a/includes/class-wc-post-data.php +++ b/includes/class-wc-post-data.php @@ -151,7 +151,7 @@ class WC_Post_Data { * * @param int $post_id */ - public function pre_post_update( $post_id ) { + public static function pre_post_update( $post_id ) { if ( isset( $_POST['_visibility'] ) ) { update_post_meta( $post_id, '_visibility', stripslashes( $_POST['_visibility'] ) ); } diff --git a/includes/wc-core-functions.php b/includes/wc-core-functions.php index d071c416c40..9b43f5a1025 100644 --- a/includes/wc-core-functions.php +++ b/includes/wc-core-functions.php @@ -42,6 +42,52 @@ add_filter( 'woocommerce_short_description', 'shortcode_unautop' ); add_filter( 'woocommerce_short_description', 'prepend_attachment' ); add_filter( 'woocommerce_short_description', 'do_shortcode', 11 ); // AFTER wpautop() +/** + * Create a new order programmatically + * + * Returns a new order object on success which can then be used to add additonal data. + * + * @return WC_Order on success, WP_Error on failure + */ +function wc_create_order( $args ) { + $default_args = array( + 'status' => apply_filters( 'woocommerce_default_order_status', 'pending' ), + 'customer_id' => 0, + 'customer_note' => '' + ); + + $args = wp_parse_args( $args, $default_args ); + + // Validate status + if ( ! in_array( 'wc-' . $args['status'], array_keys( wc_get_order_statuses() ) ) ) { + return new WP_Error( __( 'Invalid order status', 'woocommerce' ) ); + } + + $order_data = apply_filters( 'woocommerce_new_order_data', array( + 'post_type' => 'shop_order', + 'post_title' => sprintf( __( 'Order – %s', 'woocommerce' ), strftime( _x( '%b %d, %Y @ %I:%M %p', 'Order date parsed by strftime', 'woocommerce' ) ) ), + 'post_status' => 'wc-' . $args['status'], + 'ping_status' => 'closed', + 'post_excerpt' => $args['customer_note'], + 'post_author' => 1, + 'post_password' => uniqid( 'order_' ) + ) ); + + $order_id = wp_insert_post( $order_data, true ); + + if ( is_wp_error( $order_id ) ) { + return $order_id; + } else { + // Set meta data automatically + update_post_meta( $order_id, '_order_key', 'wc_' . apply_filters( 'woocommerce_generate_order_key', uniqid( 'order_' ) ) ); + update_post_meta( $order_id, '_customer_user', absint( $args['customer_id'] ) ); + update_post_meta( $order_id, '_order_currency', get_woocommerce_currency() ); + update_post_meta( $order_id, '_prices_include_tax', get_option( 'woocommerce_prices_include_tax' ) ); + + return new WC_Order( $order_id ); + } +} + /** * Get template part (for templates like the shop-loop). * From ef3e728855ad3fe8da054f1dc9c34c02032ab276 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Wed, 11 Jun 2014 15:10:03 +0100 Subject: [PATCH 02/13] Setters + wc_create_order + wc_update_order for #4169 --- includes/class-wc-cart.php | 29 +++- includes/class-wc-checkout.php | 300 +++++++++++++-------------------- includes/class-wc-order.php | 226 ++++++++++++++++++++++--- includes/wc-core-functions.php | 79 ++++++--- 4 files changed, 401 insertions(+), 233 deletions(-) diff --git a/includes/class-wc-cart.php b/includes/class-wc-cart.php index 8bc5dcd5f68..df2a8f964ea 100644 --- a/includes/class-wc-cart.php +++ b/includes/class-wc-cart.php @@ -1371,7 +1371,7 @@ class WC_Cart { $needs_shipping_address = false; - if ( WC()->cart->needs_shipping() === true && ! WC()->cart->ship_to_billing_address_only() ) { + if ( $this->needs_shipping() === true && ! $this->ship_to_billing_address_only() ) { $needs_shipping_address = true; } @@ -1661,6 +1661,15 @@ class WC_Cart { return $this->applied_coupons; } + /** + * Get the discount amount for a used coupon + * @param string $code coupon code + * @return float discount amount + */ + public function get_coupon_discount_amount( $code ) { + return isset( $this->coupon_discount_amounts[ $code ] ) ? $this->coupon_discount_amounts[ $code ] : 0; + } + /** * Remove coupons from the cart of a defined type. Type 1 is before tax, type 2 is after tax. * @@ -2075,6 +2084,24 @@ class WC_Cart { return apply_filters( 'woocommerce_get_cart_tax', $cart_total_tax ? wc_price( $cart_total_tax ) : '' ); } + /** + * Get a tax amount + * @param string $tax_rate_id + * @return float amount + */ + public function get_tax_amount( $tax_rate_id ) { + return isset( $this->taxes[ $tax_rate_id ] ) ? $this->taxes[ $tax_rate_id ] : 0; + } + + /** + * Get a tax amount + * @param string $tax_rate_id + * @return float amount + */ + public function get_shipping_tax_amount( $tax_rate_id ) { + return isset( $this->shipping_taxes[ $tax_rate_id ] ) ? $this->shipping_taxes[ $tax_rate_id ] : 0; + } + /** * Get tax row amounts with or without compound taxes includes. * diff --git a/includes/class-wc-checkout.php b/includes/class-wc-checkout.php index 719948ce4f5..37ae424186a 100644 --- a/includes/class-wc-checkout.php +++ b/includes/class-wc-checkout.php @@ -123,7 +123,6 @@ class WC_Checkout { do_action( 'woocommerce_checkout_init', $this ); } - /** * Checkout process */ @@ -132,7 +131,6 @@ class WC_Checkout { do_action('woocommerce_check_cart_items'); } - /** * Output the billing information form * @@ -143,7 +141,6 @@ class WC_Checkout { wc_get_template( 'checkout/form-billing.php', array( 'checkout' => $this ) ); } - /** * Output the shipping information form * @@ -154,7 +151,6 @@ class WC_Checkout { wc_get_template( 'checkout/form-shipping.php', array( 'checkout' => $this ) ); } - /** * create_order function. * @access public @@ -162,231 +158,143 @@ class WC_Checkout { * @return int */ public function create_order() { - global $wpdb; - // Give plugins the opportunity to create an order themselves - $order_id = apply_filters( 'woocommerce_create_order', null, $this ); - - if ( is_numeric( $order_id ) ) + if ( $order_id = apply_filters( 'woocommerce_create_order', null, $this ) ) { return $order_id; + } - // Create Order (send cart variable so we can record items and reduce inventory). Only create if this is a new order, not if the payment was rejected. - $order_data = apply_filters( 'woocommerce_new_order_data', array( - 'post_type' => 'shop_order', - 'post_title' => sprintf( __( 'Order – %s', 'woocommerce' ), strftime( _x( '%b %d, %Y @ %I:%M %p', 'Order date parsed by strftime', 'woocommerce' ) ) ), - 'post_status' => 'wc-' . apply_filters( 'woocommerce_default_order_status', 'pending' ), - 'ping_status' => 'closed', - 'post_excerpt' => isset( $this->posted['order_comments'] ) ? $this->posted['order_comments'] : '', - 'post_author' => 1, - 'post_password' => uniqid( 'order_' ) // Protects the post just in case - ) ); + $order_data = array( + 'status' => apply_filters( 'woocommerce_default_order_status', 'pending' ), + 'customer_id' => $this->customer_id, + 'customer_note' => isset( $this->posted['order_comments'] ) ? $this->posted['order_comments'] : '' + ); // Insert or update the post data - $create_new_order = true; + $order_id = absint( WC()->session->order_awaiting_payment ); - if ( WC()->session->order_awaiting_payment > 0 ) { + // Resume the unpaid order if its pending + if ( $order_id > 0 && ( $order = new WC_Order( $order_id ) ) && $order->has_status( array( 'pending', 'failed' ) ) ) { - $order_id = absint( WC()->session->order_awaiting_payment ); + $order_data['order_id'] = $order_id; + $order = wc_update_order( $order_data ); - // Resume the unpaid order if its pending - if ( ( $existing_order = get_post( $order_id ) ) && $existing_order->has_status( array( 'pending', 'failed' ) ) ) { - - // Update the existing order as we are resuming it - $create_new_order = false; - $order_data['ID'] = $order_id; - wp_update_post( $order_data ); - - // Clear the old line items - we'll add these again in case they changed - $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id IN ( SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d )", $order_id ) ); - - $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $order_id ) ); - - // Trigger an action for the resumed order + if ( is_wp_error( $order ) ) { + throw new Exception( 'Error: Unable to update order. Please try again.' ); + } else { + $order->remove_order_items(); do_action( 'woocommerce_resume_order', $order_id ); } - } - if ( $create_new_order ) { - $order_id = wp_insert_post( $order_data, true ); + } else { - if ( is_wp_error( $order_id ) ) + $order = wc_create_order( $order_data ); + + if ( is_wp_error( $order ) ) { throw new Exception( 'Error: Unable to create order. Please try again.' ); - else + } else { + $order_id = $order->id; do_action( 'woocommerce_new_order', $order_id ); - } - - // Store user data - if ( $this->checkout_fields['billing'] ) - foreach ( $this->checkout_fields['billing'] as $key => $field ) { - update_post_meta( $order_id, '_' . $key, $this->posted[ $key ] ); - - if ( $this->customer_id && apply_filters( 'woocommerce_checkout_update_customer_data', true, $this ) ) - update_user_meta( $this->customer_id, $key, $this->posted[ $key ] ); - } - - if ( $this->checkout_fields['shipping'] && WC()->cart->needs_shipping() ) { - foreach ( $this->checkout_fields['shipping'] as $key => $field ) { - $postvalue = false; - - if ( $this->posted['ship_to_different_address'] == false ) { - if ( isset( $this->posted[ str_replace( 'shipping_', 'billing_', $key ) ] ) ) { - $postvalue = $this->posted[ str_replace( 'shipping_', 'billing_', $key ) ]; - update_post_meta( $order_id, '_' . $key, $postvalue ); - } - } else { - $postvalue = $this->posted[ $key ]; - update_post_meta( $order_id, '_' . $key, $postvalue ); - } - - // User - if ( $postvalue && $this->customer_id && apply_filters( 'woocommerce_checkout_update_customer_data', true, $this ) ) - update_user_meta( $this->customer_id, $key, $postvalue ); } } - // Save any other user meta - if ( $this->customer_id ) - do_action( 'woocommerce_checkout_update_user_meta', $this->customer_id, $this->posted ); - // Store the line items to the new/resumed order foreach ( WC()->cart->get_cart() as $cart_item_key => $values ) { + $item_id = $order->add_product( + $values['data'], + $values['quantity'], + array( + 'variation' => $values['variation'], + 'totals' => array( + 'subtotal' => $values['line_subtotal'], + 'subtotal_tax' => $values['line_subtotal_tax'], + 'total' => $values['line_total'], + 'tax' => $values['line_tax'] + ) + ) + ); - $_product = $values['data']; - - // Add line item - $item_id = wc_add_order_item( $order_id, array( - 'order_item_name' => $_product->get_title(), - 'order_item_type' => 'line_item' - ) ); - - // Add line item meta - if ( $item_id ) { - wc_add_order_item_meta( $item_id, '_qty', apply_filters( 'woocommerce_stock_amount', $values['quantity'] ) ); - wc_add_order_item_meta( $item_id, '_tax_class', $_product->get_tax_class() ); - wc_add_order_item_meta( $item_id, '_product_id', $values['product_id'] ); - wc_add_order_item_meta( $item_id, '_variation_id', $values['variation_id'] ); - wc_add_order_item_meta( $item_id, '_line_subtotal', wc_format_decimal( $values['line_subtotal'] ) ); - wc_add_order_item_meta( $item_id, '_line_total', wc_format_decimal( $values['line_total'] ) ); - wc_add_order_item_meta( $item_id, '_line_tax', wc_format_decimal( $values['line_tax'] ) ); - wc_add_order_item_meta( $item_id, '_line_subtotal_tax', wc_format_decimal( $values['line_subtotal_tax'] ) ); - - // Store variation data in meta so admin can view it - if ( $values['variation'] && is_array( $values['variation'] ) ) { - foreach ( $values['variation'] as $key => $value ) { - $key = str_replace( 'attribute_', '', $key ); - wc_add_order_item_meta( $item_id, $key, $value ); - } - } - - // Add line item meta for backorder status - if ( $_product->backorders_require_notification() && $_product->is_on_backorder( $values['quantity'] ) ) { - wc_add_order_item_meta( $item_id, apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ), $cart_item_key, $order_id ), $values['quantity'] - max( 0, $_product->get_total_stock() ) ); - } - - // Allow plugins to add order item meta - do_action( 'woocommerce_add_order_item_meta', $item_id, $values, $cart_item_key ); - } + // Allow plugins to add order item meta + do_action( 'woocommerce_add_order_item_meta', $item_id, $values, $cart_item_key ); } // Store fees foreach ( WC()->cart->get_fees() as $fee_key => $fee ) { - $item_id = wc_add_order_item( $order_id, array( - 'order_item_name' => $fee->name, - 'order_item_type' => 'fee' - ) ); - - if ( $fee->taxable ) - wc_add_order_item_meta( $item_id, '_tax_class', $fee->tax_class ); - else - wc_add_order_item_meta( $item_id, '_tax_class', '0' ); - - wc_add_order_item_meta( $item_id, '_line_total', wc_format_decimal( $fee->amount ) ); - wc_add_order_item_meta( $item_id, '_line_tax', wc_format_decimal( $fee->tax ) ); + $item_id = $order->add_fee( $fee ); // Allow plugins to add order item meta to fees do_action( 'woocommerce_add_order_fee_meta', $order_id, $item_id, $fee, $fee_key ); } // Store shipping for all packages - $packages = WC()->shipping->get_packages(); - - foreach ( $packages as $i => $package ) { - if ( isset( $package['rates'][ $this->shipping_methods[ $i ] ] ) ) { - - $method = $package['rates'][ $this->shipping_methods[ $i ] ]; - - $item_id = wc_add_order_item( $order_id, array( - 'order_item_name' => $method->label, - 'order_item_type' => 'shipping' - ) ); - - if ( $item_id ) { - wc_add_order_item_meta( $item_id, 'method_id', $method->id ); - wc_add_order_item_meta( $item_id, 'cost', wc_format_decimal( $method->cost ) ); - do_action( 'woocommerce_add_shipping_order_item', $order_id, $item_id, $i ); - } + foreach ( WC()->shipping->get_packages() as $package_key => $package ) { + if ( isset( $package['rates'][ $this->shipping_methods[ $package_key ] ] ) ) { + $item_id = $order->add_shipping( $package['rates'][ $this->shipping_methods[ $package_key ] ] ); + + // Allows plugins to add order item meta to shipping + do_action( 'woocommerce_add_shipping_order_item', $order_id, $item_id, $package_key ); } } // Store tax rows - foreach ( array_keys( WC()->cart->taxes + WC()->cart->shipping_taxes ) as $key ) { - $code = WC()->cart->tax->get_rate_code( $key ); - - if ( $code ) { - $item_id = wc_add_order_item( $order_id, array( - 'order_item_name' => $code, - 'order_item_type' => 'tax' - ) ); - - // Add line item meta - if ( $item_id ) { - wc_add_order_item_meta( $item_id, 'rate_id', $key ); - wc_add_order_item_meta( $item_id, 'label', WC()->cart->tax->get_rate_label( $key ) ); - wc_add_order_item_meta( $item_id, 'compound', absint( WC()->cart->tax->is_compound( $key ) ? 1 : 0 ) ); - wc_add_order_item_meta( $item_id, 'tax_amount', wc_format_decimal( isset( WC()->cart->taxes[ $key ] ) ? WC()->cart->taxes[ $key ] : 0 ) ); - wc_add_order_item_meta( $item_id, 'shipping_tax_amount', wc_format_decimal( isset( WC()->cart->shipping_taxes[ $key ] ) ? WC()->cart->shipping_taxes[ $key ] : 0 ) ); - } - } + foreach ( array_keys( WC()->cart->taxes + WC()->cart->shipping_taxes ) as $tax_rate_id ) { + $order->add_tax( $tax_rate_id, WC()->cart->get_tax_amount( $tax_rate_id ), WC()->cart->get_shipping_tax_amount( $tax_rate_id ) ); } // Store coupons - if ( $applied_coupons = WC()->cart->get_coupons() ) { - foreach ( $applied_coupons as $code => $coupon ) { + foreach ( WC()->cart->get_coupons() as $code => $coupon ) { + $order->add_coupon( $code, WC()->cart->get_coupon_discount_amount( $code ) ); + } - $item_id = wc_add_order_item( $order_id, array( - 'order_item_name' => $code, - 'order_item_type' => 'coupon' - ) ); + // Billing address + $billing_address = array( + 'first_name' => $this->get_posted_address_data( 'first_name' ), + 'last_name' => $this->get_posted_address_data( 'last_name' ), + 'company' => $this->get_posted_address_data( 'company' ), + 'email' => $this->get_posted_address_data( 'email' ), + 'phone' => $this->get_posted_address_data( 'phone' ), + 'address_1' => $this->get_posted_address_data( 'address_1' ), + 'address_2' => $this->get_posted_address_data( 'address_2' ), + 'city' => $this->get_posted_address_data( 'city' ), + 'state' => $this->get_posted_address_data( 'state' ), + 'postcode' => $this->get_posted_address_data( 'postcode' ), + 'country' => $this->get_posted_address_data( 'country' ) + ); - // Add line item meta - if ( $item_id ) { - wc_add_order_item_meta( $item_id, 'discount_amount', isset( WC()->cart->coupon_discount_amounts[ $code ] ) ? WC()->cart->coupon_discount_amounts[ $code ] : 0 ); + $shipping_address = array( + 'first_name' => $this->get_posted_address_data( 'first_name', 'shipping' ), + 'last_name' => $this->get_posted_address_data( 'last_name', 'shipping' ), + 'company' => $this->get_posted_address_data( 'company', 'shipping' ), + 'address_1' => $this->get_posted_address_data( 'address_1', 'shipping' ), + 'address_2' => $this->get_posted_address_data( 'address_2', 'shipping' ), + 'city' => $this->get_posted_address_data( 'city', 'shipping' ), + 'state' => $this->get_posted_address_data( 'state', 'shipping' ), + 'postcode' => $this->get_posted_address_data( 'postcode', 'shipping' ), + 'country' => $this->get_posted_address_data( 'country', 'shipping' ), + ); + + $order->set_address( $billing_address, 'billing' ); + $order->set_address( $shipping_address, 'shipping' ); + $order->set_payment_method( $this->payment_method ); + $order->set_total( WC()->cart->shipping_total, 'shipping' ); + $order->set_total( WC()->cart->get_order_discount_total(), 'order_discount' ); + $order->set_total( WC()->cart->get_cart_discount_total(), 'cart_discount' ); + $order->set_total( WC()->cart->tax_total, 'tax' ); + $order->set_total( WC()->cart->shipping_tax_total, 'shipping_tax' ); + $order->set_total( WC()->cart->total ); + + // Update user meta + if ( $this->customer_id ) { + if ( apply_filters( 'woocommerce_checkout_update_customer_data', true, $this ) ) { + foreach( $billing_address as $key => $value ) { + update_user_meta( $this->customer_id, 'billing_' . $key, $value ); + } + foreach( $shipping_address as $key => $value ) { + update_user_meta( $this->customer_id, 'shipping_' . $key, $value ); } } + do_action( 'woocommerce_checkout_update_user_meta', $this->customer_id, $this->posted ); } - if ( $this->payment_method ) { - update_post_meta( $order_id, '_payment_method', $this->payment_method->id ); - update_post_meta( $order_id, '_payment_method_title', $this->payment_method->get_title() ); - } - if ( empty( $this->posted['billing_email'] ) && is_user_logged_in() ) { - $current_user = wp_get_current_user(); - update_post_meta( $order_id, '_billing_email', $current_user->user_email ); - } - update_post_meta( $order_id, '_order_shipping', wc_format_decimal( WC()->cart->shipping_total ) ); - update_post_meta( $order_id, '_order_discount', wc_format_decimal( WC()->cart->get_order_discount_total() ) ); - update_post_meta( $order_id, '_cart_discount', wc_format_decimal( WC()->cart->get_cart_discount_total() ) ); - update_post_meta( $order_id, '_order_tax', wc_format_decimal( WC()->cart->tax_total ) ); - update_post_meta( $order_id, '_order_shipping_tax', wc_format_decimal( WC()->cart->shipping_tax_total ) ); - update_post_meta( $order_id, '_order_total', wc_format_decimal( WC()->cart->total, get_option( 'woocommerce_price_num_decimals' ) ) ); - - update_post_meta( $order_id, '_order_key', 'wc_' . apply_filters('woocommerce_generate_order_key', uniqid('order_') ) ); - update_post_meta( $order_id, '_customer_user', absint( $this->customer_id ) ); - update_post_meta( $order_id, '_order_currency', get_woocommerce_currency() ); - update_post_meta( $order_id, '_prices_include_tax', get_option( 'woocommerce_prices_include_tax' ) ); - update_post_meta( $order_id, '_customer_ip_address', isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR'] ); - update_post_meta( $order_id, '_customer_user_agent', isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '' ); - // Let plugins add meta do_action( 'woocommerce_checkout_update_order_meta', $order_id, $this->posted ); @@ -748,6 +656,26 @@ class WC_Checkout { } } + /** + * Get a posted address field after sanitization and validation. + * @param string $key + * @param string $type billing for shipping + * @return string + */ + public function get_posted_address_data( $key, $type = 'billing' ) { + if ( 'billing' === $type || false === $this->posted['ship_to_different_address'] ) { + $return = isset( $this->posted[ 'billing_' . $key ] ) ? $this->posted[ 'billing_' . $key ] : ''; + } else { + $return = isset( $this->posted[ 'shipping_' . $key ] ) ? $this->posted[ 'shipping_' . $key ] : ''; + } + + // Use logged in user's billing email if neccessary + if ( 'email' === $key && empty( $return ) && is_user_logged_in() ) { + $current_user = wp_get_current_user(); + $return = $current_user->user_email; + } + return $return; + } /** * Gets the value either from the posted data, or from the users meta data diff --git a/includes/class-wc-order.php b/includes/class-wc-order.php index 06e1e775936..c268a6f41d2 100644 --- a/includes/class-wc-order.php +++ b/includes/class-wc-order.php @@ -34,48 +34,220 @@ class WC_Order { } } + /** + * Remove all line items (products, coupons, shipping, taxes) from the order. + */ + public function remove_order_items() { + global $wpdb; - - public function set_address( $address_data, $type = 'billing' ) { - update_post_meta( $order_id, '_' . $type . '_address', $postvalue ); + $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id IN ( SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d )", $order_id ) ); + $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $order_id ) ); } + /** + * Set the payment method for the order + * @param WC_Payment_Gateway + */ public function set_payment_method( $payment_method ) { - - } - - public function add_item( $item_name, $meta = array() ) { - $item_id = wc_add_order_item( $this->id, array( - 'order_item_name' => $item_name, - 'order_item_type' => 'line_item' - ) ); - - if ( $item_id ) { - wc_add_order_item_meta( $item_id, '_qty', apply_filters( 'woocommerce_stock_amount', $values['quantity'] ) ); - - foreach ( $meta as $key => $value ) { - wc_add_order_item_meta( $item_id, $key, $value ); - } + if ( is_object( $payment_method ) ) { + update_post_meta( $this->id, '_payment_method', $payment_method->id ); + update_post_meta( $this->id, '_payment_method_title', $payment_method->get_title() ); } } - public function add_fee() { + /** + * Set the customer address + * @param array $address Address data + * @param string $type billing or shipping + */ + public function set_address( $address, $type = 'billing' ) { + foreach( $address as $key => $value ) { + update_post_meta( $this->id, "_{$type}_" . $key, $value ); + } + } + /** + * Add a product line item to the order + * @param WC_Product $item + * @param int $qty Line item quantity + * @param array args + * @return int|bool Item ID or false + */ + public function add_product( $product, $qty = 1, $args = array() ) { + $default_args = array( + 'variation' => array(), + 'totals' => array() + ); + + $args = wp_parse_args( $args, $default_args ); + $item_id = wc_add_order_item( $this->id, array( + 'order_item_name' => $product->get_title(), + 'order_item_type' => 'line_item' + ) ); + + if ( ! $item_id ) { + return false; + } + + wc_add_order_item_meta( $item_id, '_qty', apply_filters( 'woocommerce_stock_amount', $qty ) ); + wc_add_order_item_meta( $item_id, '_tax_class', $product->get_tax_class() ); + wc_add_order_item_meta( $item_id, '_product_id', $product->id ); + wc_add_order_item_meta( $item_id, '_variation_id', isset( $product->variation_id ) ? $product->variation_id : 0 ); + + // Add totals + foreach ( $args['totals'] as $key => $value ) { + if ( ! in_array( $key, array( 'subtotal', 'subtotal_tax', 'total', 'tax' ) ) ) { + continue; + } + wc_add_order_item_meta( $item_id, '_line_' . $key, wc_format_decimal( $value ) ); + } + + // Add variation meta + foreach ( $args['variation'] as $key => $value ) { + wc_add_order_item_meta( $item_id, str_replace( 'attribute_', '', $key ), $value ); + } + + // Backorders + if ( $product->backorders_require_notification() && $product->is_on_backorder( $qty ) ) { + wc_add_order_item_meta( $item_id, apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $qty - max( 0, $product->get_total_stock() ) ); + } + + return $item_id; } - public function add_tax() { + /** + * Add coupon code to the order + * @param string $code + * @param float $discount_amount + * @return int|bool Item ID or false + */ + public function add_coupon( $code, $discount_amount = 0 ) { + $item_id = wc_add_order_item( $this->id, array( + 'order_item_name' => $code, + 'order_item_type' => 'coupon' + ) ); + if ( ! $item_id ) { + return false; + } + + wc_add_order_item_meta( $item_id, 'discount_amount', $discount_amount ); + + do_action( 'woocommerce_order_add_coupon', $this->id, $item_id, $code, $discount_amount ); + + return $item_id; } - public function add_shipping() { + /** + * Add a tax row to the order + * @param int tax_rate_id + * @return int|bool Item ID or false + */ + public function add_tax( $tax_rate_id, $tax_amount = 0, $shipping_tax_amount = 0 ) { + $code = WC()->cart->tax->get_rate_code( $tax_rate_id ); + if ( ! $code ) { + return false; + } + + $item_id = wc_add_order_item( $this->id, array( + 'order_item_name' => $code, + 'order_item_type' => 'tax' + ) ); + + if ( ! $item_id ) { + return false; + } + + wc_add_order_item_meta( $item_id, 'rate_id', $tax_rate_id ); + wc_add_order_item_meta( $item_id, 'label', WC()->cart->tax->get_rate_label( $key ) ); + wc_add_order_item_meta( $item_id, 'compound', WC()->cart->tax->is_compound( $key ) ? 1 : 0 ); + wc_add_order_item_meta( $item_id, 'tax_amount', wc_format_decimal( $tax_amount ) ); + wc_add_order_item_meta( $item_id, 'shipping_tax_amount', wc_format_decimal( $shipping_tax_amount ) ); + + do_action( 'woocommerce_order_add_tax', $this->id, $item_id, $tax_rate_id, $tax_amount, $shipping_tax_amount ); + + return $item_id; } + /** + * Add a shipping row to the order + * @param WC_Shipping_Rate shipping_rate + * @return int|bool Item ID or false + */ + public function add_shipping( $shipping_rate ) { + $item_id = wc_add_order_item( $this->id, array( + 'order_item_name' => $shipping_rate->label, + 'order_item_type' => 'shipping' + ) ); + if ( ! $item_id ) { + return false; + } + wc_add_order_item_meta( $item_id, 'method_id', $shipping_rate->id ); + wc_add_order_item_meta( $item_id, 'cost', wc_format_decimal( $shipping_rate->cost ) ); + do_action( 'woocommerce_order_add_shipping', $this->id, $item_id, $shipping_rate ); + return $item_id; + } + /** + * Add a fee to the order + * @param object $fee + * @return int|bool Item ID or false + */ + public function add_fee( $fee ) { + $item_id = wc_add_order_item( $this->id, array( + 'order_item_name' => $fee->name, + 'order_item_type' => 'fee' + ) ); + + if ( ! $item_id ) { + return false; + } + + if ( $fee->taxable ) { + wc_add_order_item_meta( $item_id, '_tax_class', $fee->tax_class ); + } else { + wc_add_order_item_meta( $item_id, '_tax_class', '0' ); + } + + wc_add_order_item_meta( $item_id, '_line_total', wc_format_decimal( $fee->amount ) ); + wc_add_order_item_meta( $item_id, '_line_tax', wc_format_decimal( $fee->tax ) ); + + do_action( 'woocommerce_order_add_shipping', $this->id, $item_id, $fee ); + + return $item_id; + } + + /** + * Set an order total + * @param float $amount + * @param string $total_type + */ + public function set_total( $amount, $total_type = 'total' ) { + if ( ! in_array( $total_type, array( 'shipping', 'order_discount', 'tax', 'shipping_tax', 'total', 'cart_discount' ) ) ) { + return false; + } + switch ( $total_type ) { + case 'total' : + $key = '_order_total'; + $amount = wc_format_decimal( $amount, get_option( 'woocommerce_price_num_decimals' ) ); + break; + case 'order_discount' : + case 'cart_discount' : + $key = '_' . $total_type; + $amount = wc_format_decimal( $amount ); + break; + default : + $key = '_order_' . $total_type; + $amount = wc_format_decimal( $amount ); + break; + } + update_post_meta( $this->id, $key, $amount ); + } /** * Gets an order from the database. @@ -95,7 +267,6 @@ class WC_Order { return false; } - /** * Populates an order from the loaded post data. * @@ -1394,6 +1565,9 @@ class WC_Order { // Record the completed date of the order update_post_meta( $this->id, '_completed_date', current_time('mysql') ); + + // Update reports + wc_delete_shop_order_transients( $this->id ); break; case 'processing' : case 'on-hold' : @@ -1402,15 +1576,19 @@ class WC_Order { // Increase coupon usage counts $this->increase_coupon_usage_counts(); + + // Update reports + wc_delete_shop_order_transients( $this->id ); break; case 'cancelled' : // If the order is cancelled, restore used coupons $this->decrease_coupon_usage_counts(); + + // Update reports + wc_delete_shop_order_transients( $this->id ); break; } } - - wc_delete_shop_order_transients( $this->id ); } diff --git a/includes/wc-core-functions.php b/includes/wc-core-functions.php index 9b43f5a1025..e938dff85fd 100644 --- a/includes/wc-core-functions.php +++ b/includes/wc-core-functions.php @@ -51,41 +51,76 @@ add_filter( 'woocommerce_short_description', 'do_shortcode', 11 ); // AFTER wpau */ function wc_create_order( $args ) { $default_args = array( - 'status' => apply_filters( 'woocommerce_default_order_status', 'pending' ), - 'customer_id' => 0, - 'customer_note' => '' + 'status' => '', + 'customer_id' => null, + 'customer_note' => null, + 'order_id' => 0 ); - $args = wp_parse_args( $args, $default_args ); + $args = wp_parse_args( $args, $default_args ); + $order_data = array(); - // Validate status - if ( ! in_array( 'wc-' . $args['status'], array_keys( wc_get_order_statuses() ) ) ) { - return new WP_Error( __( 'Invalid order status', 'woocommerce' ) ); + if ( $args['order_id'] > 0 ) { + $updating = true; + $order_data['ID'] = $args['order_id']; + } else { + $updating = false; + $order_data['post_type'] = $args['shop_order']; + $order_data['post_status'] = 'wc-' . apply_filters( 'woocommerce_default_order_status', 'pending' ); + $order_data['ping_status'] = 'closed'; + $order_data['post_author'] = 1; + $order_data['post_password'] = uniqid( 'order_' ); + $order_data['post_title'] = sprintf( __( 'Order – %s', 'woocommerce' ), strftime( _x( '%b %d, %Y @ %I:%M %p', 'Order date parsed by strftime', 'woocommerce' ) ) ); } - $order_data = apply_filters( 'woocommerce_new_order_data', array( - 'post_type' => 'shop_order', - 'post_title' => sprintf( __( 'Order – %s', 'woocommerce' ), strftime( _x( '%b %d, %Y @ %I:%M %p', 'Order date parsed by strftime', 'woocommerce' ) ) ), - 'post_status' => 'wc-' . $args['status'], - 'ping_status' => 'closed', - 'post_excerpt' => $args['customer_note'], - 'post_author' => 1, - 'post_password' => uniqid( 'order_' ) - ) ); + if ( $args['status'] ) { + if ( ! in_array( 'wc-' . $args['status'], array_keys( wc_get_order_statuses() ) ) ) { + return new WP_Error( __( 'Invalid order status', 'woocommerce' ) ); + } + $order_data['post_status'] = 'wc-' . $args['status']; + } - $order_id = wp_insert_post( $order_data, true ); + if ( ! is_null( $args['customer_note'] ) ) { + $order_data['post_excerpt'] = $args['customer_note']; + } + + if ( $updating ) { + $order_id = wp_update_post( $order_data ); + } else { + $order_id = wp_insert_post( apply_filters( 'woocommerce_new_order_data', $order_data ), true ); + } if ( is_wp_error( $order_id ) ) { return $order_id; - } else { - // Set meta data automatically + } + + // Default order meta data. + if ( ! $updating ) { update_post_meta( $order_id, '_order_key', 'wc_' . apply_filters( 'woocommerce_generate_order_key', uniqid( 'order_' ) ) ); - update_post_meta( $order_id, '_customer_user', absint( $args['customer_id'] ) ); update_post_meta( $order_id, '_order_currency', get_woocommerce_currency() ); update_post_meta( $order_id, '_prices_include_tax', get_option( 'woocommerce_prices_include_tax' ) ); - - return new WC_Order( $order_id ); + update_post_meta( $order_id, '_customer_ip_address', isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR'] ); + update_post_meta( $order_id, '_customer_user_agent', isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '' ); + update_post_meta( $order_id, '_customer_user', 0 ); } + + if ( is_numeric( $args['customer_id'] ) ) { + update_post_meta( $order_id, '_customer_user', $args['customer_id'] ); + } + + return new WC_Order( $order_id ); +} + +/** + * Update an order. Uses wc_create_order. + * @param array $args + * @return WC_Error | WC_Order + */ +function wc_update_order( $args ) { + if ( ! $args['order_id'] ) { + return new WP_Error( __( 'Invalid order ID', 'woocommerce' ) ); + } + return wc_create_order( $args ); } /** From c591a3fb54fb99d4922e3288591724bf01ccce07 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Wed, 11 Jun 2014 16:11:28 +0100 Subject: [PATCH 03/13] Set correct post type --- includes/wc-core-functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/wc-core-functions.php b/includes/wc-core-functions.php index e938dff85fd..016e1f23235 100644 --- a/includes/wc-core-functions.php +++ b/includes/wc-core-functions.php @@ -65,7 +65,7 @@ function wc_create_order( $args ) { $order_data['ID'] = $args['order_id']; } else { $updating = false; - $order_data['post_type'] = $args['shop_order']; + $order_data['post_type'] = 'shop_order'; $order_data['post_status'] = 'wc-' . apply_filters( 'woocommerce_default_order_status', 'pending' ); $order_data['ping_status'] = 'closed'; $order_data['post_author'] = 1; From 80ba9aa9c9060a4cbbc602b7af85ce0a1ac87255 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Wed, 11 Jun 2014 16:53:49 +0100 Subject: [PATCH 04/13] woocommerce_order_add_fee --- includes/class-wc-order.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-wc-order.php b/includes/class-wc-order.php index c268a6f41d2..9452b72c24e 100644 --- a/includes/class-wc-order.php +++ b/includes/class-wc-order.php @@ -217,7 +217,7 @@ class WC_Order { wc_add_order_item_meta( $item_id, '_line_total', wc_format_decimal( $fee->amount ) ); wc_add_order_item_meta( $item_id, '_line_tax', wc_format_decimal( $fee->tax ) ); - do_action( 'woocommerce_order_add_shipping', $this->id, $item_id, $fee ); + do_action( 'woocommerce_order_add_fee', $this->id, $item_id, $fee ); return $item_id; } From 375a0e2652260c5b45db5bcca3c601e0c9d66b58 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Thu, 12 Jun 2014 16:47:43 +0100 Subject: [PATCH 05/13] static WC_Tax class - no need for a constructor in this --- includes/abstracts/abstract-wc-product.php | 27 +++--- .../abstracts/abstract-wc-shipping-method.php | 13 ++- includes/class-wc-ajax.php | 17 ++-- includes/class-wc-order.php | 6 +- includes/class-wc-tax.php | 87 +++++++++---------- 5 files changed, 70 insertions(+), 80 deletions(-) diff --git a/includes/abstracts/abstract-wc-product.php b/includes/abstracts/abstract-wc-product.php index 85b9c9e53b1..cbd4d7d846f 100644 --- a/includes/abstracts/abstract-wc-product.php +++ b/includes/abstracts/abstract-wc-product.php @@ -786,8 +786,6 @@ class WC_Product { * @return string */ public function get_price_including_tax( $qty = 1, $price = '' ) { - $_tax = new WC_Tax(); - if ( ! $price ) { $price = $this->get_price(); } @@ -796,26 +794,26 @@ class WC_Product { if ( get_option('woocommerce_prices_include_tax') === 'no' ) { - $tax_rates = $_tax->get_rates( $this->get_tax_class() ); - $taxes = $_tax->calc_tax( $price * $qty, $tax_rates, false ); - $tax_amount = $_tax->get_tax_total( $taxes ); + $tax_rates = WC_Tax::get_rates( $this->get_tax_class() ); + $taxes = WC_Tax::calc_tax( $price * $qty, $tax_rates, false ); + $tax_amount = WC_Tax::get_tax_total( $taxes ); $price = round( $price * $qty + $tax_amount, absint( get_option( 'woocommerce_price_num_decimals' ) ) ); } else { - $tax_rates = $_tax->get_rates( $this->get_tax_class() ); - $base_tax_rates = $_tax->get_shop_base_rate( $this->tax_class ); + $tax_rates = WC_Tax::get_rates( $this->get_tax_class() ); + $base_tax_rates = WC_Tax::get_shop_base_rate( $this->tax_class ); if ( ! empty( WC()->customer ) && WC()->customer->is_vat_exempt() ) { - $base_taxes = $_tax->calc_tax( $price * $qty, $base_tax_rates, true ); + $base_taxes = WC_Tax::calc_tax( $price * $qty, $base_tax_rates, true ); $base_tax_amount = array_sum( $base_taxes ); $price = round( $price * $qty - $base_tax_amount, absint( get_option( 'woocommerce_price_num_decimals' ) ) ); } elseif ( $tax_rates !== $base_tax_rates ) { - $base_taxes = $_tax->calc_tax( $price * $qty, $base_tax_rates, true ); - $modded_taxes = $_tax->calc_tax( ( $price * $qty ) - array_sum( $base_taxes ), $tax_rates, false ); + $base_taxes = WC_Tax::calc_tax( $price * $qty, $base_tax_rates, true ); + $modded_taxes = WC_Tax::calc_tax( ( $price * $qty ) - array_sum( $base_taxes ), $tax_rates, false ); $price = round( ( $price * $qty ) - array_sum( $base_taxes ) + array_sum( $modded_taxes ), absint( get_option( 'woocommerce_price_num_decimals' ) ) ); } else { @@ -848,12 +846,9 @@ class WC_Product { } if ( $this->is_taxable() && get_option('woocommerce_prices_include_tax') === 'yes' ) { - - $_tax = new WC_Tax(); - $tax_rates = $_tax->get_shop_base_rate( $this->tax_class ); - $taxes = $_tax->calc_tax( $price * $qty, $tax_rates, true ); - $price = $_tax->round( $price * $qty - array_sum( $taxes ) ); - + $tax_rates = WC_Tax::get_shop_base_rate( $this->tax_class ); + $taxes = WC_Tax::calc_tax( $price * $qty, $tax_rates, true ); + $price = WC_Tax::round( $price * $qty - array_sum( $taxes ) ); } else { $price = $price * $qty; } diff --git a/includes/abstracts/abstract-wc-shipping-method.php b/includes/abstracts/abstract-wc-shipping-method.php index 5e3857d01c6..d8f575dc157 100644 --- a/includes/abstracts/abstract-wc-shipping-method.php +++ b/includes/abstracts/abstract-wc-shipping-method.php @@ -95,7 +95,6 @@ abstract class WC_Shipping_Method extends WC_Settings_API { // This saves shipping methods having to do complex tax calculations if ( ! is_array( $taxes ) && $taxes !== false && $total_cost > 0 && $this->is_taxable() ) { - $_tax = new WC_Tax(); $taxes = array(); switch ( $calc_tax ) { @@ -114,8 +113,8 @@ abstract class WC_Shipping_Method extends WC_Settings_API { $_product = $cart[ $cost_key ]['data']; - $rates = $_tax->get_shipping_tax_rates( $_product->get_tax_class() ); - $item_taxes = $_tax->calc_shipping_tax( $amount, $rates ); + $rates = WC_Tax::get_shipping_tax_rates( $_product->get_tax_class() ); + $item_taxes = WC_Tax::calc_shipping_tax( $amount, $rates ); // Sum the item taxes foreach ( array_keys( $taxes + $item_taxes ) as $key ) @@ -126,8 +125,8 @@ abstract class WC_Shipping_Method extends WC_Settings_API { // Add any cost for the order - order costs are in the key 'order' if ( isset( $cost['order'] ) ) { - $rates = $_tax->get_shipping_tax_rates(); - $item_taxes = $_tax->calc_shipping_tax( $cost['order'], $rates ); + $rates = WC_Tax::get_shipping_tax_rates(); + $item_taxes = WC_Tax::calc_shipping_tax( $cost['order'], $rates ); // Sum the item taxes foreach ( array_keys( $taxes + $item_taxes ) as $key ) @@ -140,8 +139,8 @@ abstract class WC_Shipping_Method extends WC_Settings_API { default : - $rates = $_tax->get_shipping_tax_rates(); - $taxes = $_tax->calc_shipping_tax( $total_cost, $rates ); + $rates = WC_Tax::get_shipping_tax_rates(); + $taxes = WC_Tax::calc_shipping_tax( $total_cost, $rates ); break; diff --git a/includes/class-wc-ajax.php b/includes/class-wc-ajax.php index 101e6a60612..e318fbf9f5a 100644 --- a/includes/class-wc-ajax.php +++ b/includes/class-wc-ajax.php @@ -1195,7 +1195,6 @@ class WC_AJAX { self::json_headers(); - $tax = new WC_Tax(); $taxes = $tax_rows = $item_taxes = $shipping_taxes = array(); $order_id = absint( $_POST['order_id'] ); $order = new WC_Order( $order_id ); @@ -1232,7 +1231,7 @@ class WC_AJAX { // Only calc if taxable if ( 'taxable' == $item_tax_status ) { - $tax_rates = $tax->find_rates( array( + $tax_rates = WC_Tax::find_rates( array( 'country' => $country, 'state' => $state, 'postcode' => $postcode, @@ -1240,8 +1239,8 @@ class WC_AJAX { 'tax_class' => $tax_class ) ); - $line_subtotal_taxes = $tax->calc_tax( $line_subtotal, $tax_rates, false ); - $line_taxes = $tax->calc_tax( $line_total, $tax_rates, false ); + $line_subtotal_taxes = WC_Tax::calc_tax( $line_subtotal, $tax_rates, false ); + $line_taxes = WC_Tax::calc_tax( $line_total, $tax_rates, false ); $line_subtotal_tax = array_sum( $line_subtotal_taxes ); $line_tax = array_sum( $line_taxes ); @@ -1272,7 +1271,7 @@ class WC_AJAX { // Now calculate shipping tax $matched_tax_rates = array(); - $tax_rates = $tax->find_rates( array( + $tax_rates = WC_Tax::find_rates( array( 'country' => $country, 'state' => $state, 'postcode' => $postcode, @@ -1288,8 +1287,8 @@ class WC_AJAX { } } - $shipping_taxes = $tax->calc_shipping_tax( $shipping, $matched_tax_rates ); - $shipping_tax = $tax->round( array_sum( $shipping_taxes ) ); + $shipping_taxes = WC_Tax::calc_shipping_tax( $shipping, $matched_tax_rates ); + $shipping_tax = WC_Tax::round( array_sum( $shipping_taxes ) ); // Remove old tax rows $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id IN ( SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = 'tax' )", $order_id ) ); @@ -1320,8 +1319,8 @@ class WC_AJAX { $item = array(); $item['rate_id'] = $key; $item['name'] = $tax_codes[ $key ]; - $item['label'] = $tax->get_rate_label( $key ); - $item['compound'] = $tax->is_compound( $key ) ? 1 : 0; + $item['label'] = WC_Tax::get_rate_label( $key ); + $item['compound'] = WC_Tax::is_compound( $key ) ? 1 : 0; $item['tax_amount'] = wc_format_decimal( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 ); $item['shipping_tax_amount'] = wc_format_decimal( isset( $shipping_taxes[ $key ] ) ? $shipping_taxes[ $key ] : 0 ); diff --git a/includes/class-wc-order.php b/includes/class-wc-order.php index 9452b72c24e..4c0768a2899 100644 --- a/includes/class-wc-order.php +++ b/includes/class-wc-order.php @@ -144,7 +144,7 @@ class WC_Order { * @return int|bool Item ID or false */ public function add_tax( $tax_rate_id, $tax_amount = 0, $shipping_tax_amount = 0 ) { - $code = WC()->cart->tax->get_rate_code( $tax_rate_id ); + $code = WC_Tax::get_rate_code( $tax_rate_id ); if ( ! $code ) { return false; @@ -160,8 +160,8 @@ class WC_Order { } wc_add_order_item_meta( $item_id, 'rate_id', $tax_rate_id ); - wc_add_order_item_meta( $item_id, 'label', WC()->cart->tax->get_rate_label( $key ) ); - wc_add_order_item_meta( $item_id, 'compound', WC()->cart->tax->is_compound( $key ) ? 1 : 0 ); + wc_add_order_item_meta( $item_id, 'label', WC_Tax::get_rate_label( $key ) ); + wc_add_order_item_meta( $item_id, 'compound', WC_Tax::is_compound( $key ) ? 1 : 0 ); wc_add_order_item_meta( $item_id, 'tax_amount', wc_format_decimal( $tax_amount ) ); wc_add_order_item_meta( $item_id, 'shipping_tax_amount', wc_format_decimal( $shipping_tax_amount ) ); diff --git a/includes/class-wc-tax.php b/includes/class-wc-tax.php index c415afae117..b1177a88996 100644 --- a/includes/class-wc-tax.php +++ b/includes/class-wc-tax.php @@ -3,28 +3,24 @@ * Performs tax calculations and loads tax rates. * * @class WC_Tax - * @version 2.0.0 + * @version 2.2.0 * @package WooCommerce/Classes * @category Class * @author WooThemes */ class WC_Tax { - /** @var array */ - public $matched_rates; - - - var $log = array(); - + public static $precision; + public static $round_at_subtotal; + /** - * __construct function. + * Load options * * @access public */ - public function __construct() { - $this->precision = WC_ROUNDING_PRECISION; - $this->dp = (int) get_option( 'woocommerce_price_num_decimals' ); - $this->round_at_subtotal = get_option('woocommerce_tax_round_at_subtotal') == 'yes'; + public static function init() { + self::$precision = WC_ROUNDING_PRECISION; + self::$round_at_subtotal = 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' ); } /** @@ -35,22 +31,22 @@ class WC_Tax { * @param boolean $suppress_rounding Whether to suppress any rounding from taking place * @return array Array of rates + prices after tax */ - public function calc_tax( $price, $rates, $price_includes_tax = false, $suppress_rounding = false ) { + public static function calc_tax( $price, $rates, $price_includes_tax = false, $suppress_rounding = false ) { // Work in pence to X precision - $price = $this->precision( $price ); + $price = self::$precision( $price ); if ( $price_includes_tax ) - $taxes = $this->calc_inclusive_tax( $price, $rates ); + $taxes = self::calc_inclusive_tax( $price, $rates ); else - $taxes = $this->calc_exclusive_tax( $price, $rates ); + $taxes = self::calc_exclusive_tax( $price, $rates ); // Round to precision - if ( ! $this->round_at_subtotal && ! $suppress_rounding ) { + if ( ! self::$round_at_subtotal && ! $suppress_rounding ) { $taxes = array_map( 'round', $taxes ); // Round to precision } // Remove precision - $price = $this->remove_precision( $price ); + $price = self::remove_precision( $price ); $taxes = array_map( array( $this, 'remove_precision' ), $taxes ); return apply_filters( 'woocommerce_calc_tax', $taxes, $price, $rates, $price_includes_tax, $suppress_rounding ); @@ -63,8 +59,8 @@ class WC_Tax { * @param array Taxation Rate * @return array */ - public function calc_shipping_tax( $price, $rates ) { - return $this->calc_exclusive_tax( $price, $rates ); + public static function calc_shipping_tax( $price, $rates ) { + return self::calc_exclusive_tax( $price, $rates ); } /** @@ -72,8 +68,8 @@ class WC_Tax { * @param float $price * @return float */ - private function precision( $price ) { - return $price * ( pow( 10, $this->precision ) ); + private static function precision( $price ) { + return $price * ( pow( 10, self::$precision ) ); } /** @@ -81,8 +77,8 @@ class WC_Tax { * @param float $price * @return float */ - private function remove_precision( $price ) { - return $price / ( pow( 10, $this->precision ) ); + private static function remove_precision( $price ) { + return $price / ( pow( 10, self::$precision ) ); } /** @@ -95,8 +91,8 @@ class WC_Tax { * } * add_filter( 'woocommerce_tax_round', 'euro_5cent_rounding' ); */ - public function round( $in ) { - return apply_filters( 'woocommerce_tax_round', round( $in, $this->precision ), $in ); + public static function round( $in ) { + return apply_filters( 'woocommerce_tax_round', round( $in, self::$precision ), $in ); } /** @@ -106,7 +102,7 @@ class WC_Tax { * @param array $rates * @return array */ - private function calc_inclusive_tax( $price, $rates ) { + private static function calc_inclusive_tax( $price, $rates ) { $taxes = array(); $regular_tax_rates = $compound_tax_rates = 0; @@ -150,7 +146,7 @@ class WC_Tax { * @param array $rates * @return array */ - private function calc_exclusive_tax( $price, $rates ) { + private static function calc_exclusive_tax( $price, $rates ) { $taxes = array(); // Multiple taxes @@ -205,7 +201,7 @@ class WC_Tax { * @param array $args * @return array */ - public function find_rates( $args = array() ) { + public static function find_rates( $args = array() ) { global $wpdb; $defaults = array( @@ -318,7 +314,7 @@ class WC_Tax { * @param string $tax_class * @return array */ - public function get_rates( $tax_class = '' ) { + public static function get_rates( $tax_class = '' ) { $tax_class = sanitize_title( $tax_class ); @@ -327,7 +323,7 @@ class WC_Tax { list( $country, $state, $postcode, $city ) = WC()->customer->get_taxable_address(); - $matched_tax_rates = $this->find_rates( array( + $matched_tax_rates = self::find_rates( array( 'country' => $country, 'state' => $state, 'postcode' => $postcode, @@ -341,7 +337,7 @@ class WC_Tax { // Prices excluding tax however should just not add any taxes, as they will be added during checkout. // The woocommerce_default_customer_address option (when set to base) is also used here. $matched_tax_rates = get_option( 'woocommerce_prices_include_tax' ) == 'yes' || get_option( 'woocommerce_default_customer_address' ) == 'base' - ? $this->get_shop_base_rate( $tax_class ) + ? self::get_shop_base_rate( $tax_class ) : array(); } @@ -355,8 +351,8 @@ class WC_Tax { * @param string Tax Class * @return array */ - public function get_shop_base_rate( $tax_class = '' ) { - return $this->find_rates( array( + public static function get_shop_base_rate( $tax_class = '' ) { + return self::find_rates( array( 'country' => WC()->countries->get_base_country(), 'state' => WC()->countries->get_base_state(), 'postcode' => WC()->countries->get_base_postcode(), @@ -371,7 +367,7 @@ class WC_Tax { * @param string Tax Class * @return mixed */ - public function get_shipping_tax_rates( $tax_class = null ) { + public static function get_shipping_tax_rates( $tax_class = null ) { // See if we have an explicitly set shipping tax class if ( $shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' ) ) { @@ -403,7 +399,7 @@ class WC_Tax { $matched_tax_rates = array(); // This will be per item shipping - $rates = $this->find_rates( array( + $rates = self::find_rates( array( 'country' => $country, 'state' => $state, 'postcode' => $postcode, @@ -418,7 +414,7 @@ class WC_Tax { if ( sizeof( $matched_tax_rates ) == 0 ) { // Get standard rate - $rates = $this->find_rates( array( + $rates = self::find_rates( array( 'country' => $country, 'state' => $state, 'city' => $city, @@ -451,7 +447,7 @@ class WC_Tax { if ( sizeof( $found_tax_classes ) > 1 ) { if ( in_array( '', $found_tax_classes ) ) { - $rates = $this->find_rates( array( + $rates = self::find_rates( array( 'country' => $country, 'state' => $state, 'city' => $city, @@ -462,7 +458,7 @@ class WC_Tax { foreach ( $tax_classes as $tax_class ) { if ( in_array( $tax_class, $found_tax_classes ) ) { - $rates = $this->find_rates( array( + $rates = self::find_rates( array( 'country' => $country, 'state' => $state, 'postcode' => $postcode, @@ -477,7 +473,7 @@ class WC_Tax { // If a single tax class is found, use it } elseif ( sizeof( $found_tax_classes ) == 1 ) { - $rates = $this->find_rates( array( + $rates = self::find_rates( array( 'country' => $country, 'state' => $state, 'postcode' => $postcode, @@ -489,7 +485,7 @@ class WC_Tax { // If no class rate are found, use standard rates if ( ! $rates ) - $rates = $this->find_rates( array( + $rates = self::find_rates( array( 'country' => $country, 'state' => $state, 'postcode' => $postcode, @@ -513,7 +509,7 @@ class WC_Tax { * @param int key * @return bool */ - public function is_compound( $key ) { + public static function is_compound( $key ) { global $wpdb; return $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate_compound FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %s", $key ) ) ? true : false; } @@ -524,7 +520,7 @@ class WC_Tax { * @param int key * @return string */ - public function get_rate_label( $key ) { + public static function get_rate_label( $key ) { global $wpdb; $rate_name = $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate_name FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %s", $key ) ); @@ -542,7 +538,7 @@ class WC_Tax { * @param mixed $key * @return string */ - public function get_rate_code( $key ) { + public static function get_rate_code( $key ) { global $wpdb; $rate = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %s", $key ) ); @@ -567,7 +563,8 @@ class WC_Tax { * @param array * @return float */ - public function get_tax_total( $taxes ) { + public static function get_tax_total( $taxes ) { return array_sum( array_map( array( $this, 'round' ), $taxes ) ); } } +WC_Tax::init(); \ No newline at end of file From 5aa8b47c6b634affdf7977fdd0d63c76c897cd4b Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Fri, 13 Jun 2014 10:14:18 +0100 Subject: [PATCH 06/13] When setting totals, if not set get the data from the product --- includes/class-wc-order.php | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/includes/class-wc-order.php b/includes/class-wc-order.php index 4c0768a2899..609e9880b34 100644 --- a/includes/class-wc-order.php +++ b/includes/class-wc-order.php @@ -93,14 +93,12 @@ class WC_Order { wc_add_order_item_meta( $item_id, '_tax_class', $product->get_tax_class() ); wc_add_order_item_meta( $item_id, '_product_id', $product->id ); wc_add_order_item_meta( $item_id, '_variation_id', isset( $product->variation_id ) ? $product->variation_id : 0 ); - - // Add totals - foreach ( $args['totals'] as $key => $value ) { - if ( ! in_array( $key, array( 'subtotal', 'subtotal_tax', 'total', 'tax' ) ) ) { - continue; - } - wc_add_order_item_meta( $item_id, '_line_' . $key, wc_format_decimal( $value ) ); - } + + // Set line item totals, either passed in or from the product + wc_add_order_item_meta( $item_id, '_line_subtotal' . $key, wc_format_decimal( isset( $args['totals']['subtotal'] ) ? $args['totals']['subtotal'] : $product->get_price_excluding_tax() ) ); + wc_add_order_item_meta( $item_id, '_line_subtotal_tax' . $key, wc_format_decimal( isset( $args['totals']['subtotal_tax'] ) ? $args['totals']['subtotal_tax'] : 0 ) ); + wc_add_order_item_meta( $item_id, '_line_total' . $key, wc_format_decimal( isset( $args['totals']['total'] ) ? $args['total']['subtotal_tax'] : $product->get_price_excluding_tax() ) ); + wc_add_order_item_meta( $item_id, '_line_tax' . $key, wc_format_decimal( isset( $args['totals']['tax'] ) ? $args['totals']['tax'] : 0 ) ); // Add variation meta foreach ( $args['variation'] as $key => $value ) { From a6da6e3353d3b84445c1ccea8aa28d867e522a4b Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Fri, 13 Jun 2014 10:18:03 +0100 Subject: [PATCH 07/13] Also use qty --- includes/class-wc-order.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/class-wc-order.php b/includes/class-wc-order.php index 609e9880b34..de1da390d9a 100644 --- a/includes/class-wc-order.php +++ b/includes/class-wc-order.php @@ -95,9 +95,9 @@ class WC_Order { wc_add_order_item_meta( $item_id, '_variation_id', isset( $product->variation_id ) ? $product->variation_id : 0 ); // Set line item totals, either passed in or from the product - wc_add_order_item_meta( $item_id, '_line_subtotal' . $key, wc_format_decimal( isset( $args['totals']['subtotal'] ) ? $args['totals']['subtotal'] : $product->get_price_excluding_tax() ) ); + wc_add_order_item_meta( $item_id, '_line_subtotal' . $key, wc_format_decimal( isset( $args['totals']['subtotal'] ) ? $args['totals']['subtotal'] : $product->get_price_excluding_tax( $qty ) ) ); + wc_add_order_item_meta( $item_id, '_line_total' . $key, wc_format_decimal( isset( $args['totals']['total'] ) ? $args['total']['subtotal_tax'] : $product->get_price_excluding_tax( $qty ) ) ); wc_add_order_item_meta( $item_id, '_line_subtotal_tax' . $key, wc_format_decimal( isset( $args['totals']['subtotal_tax'] ) ? $args['totals']['subtotal_tax'] : 0 ) ); - wc_add_order_item_meta( $item_id, '_line_total' . $key, wc_format_decimal( isset( $args['totals']['total'] ) ? $args['total']['subtotal_tax'] : $product->get_price_excluding_tax() ) ); wc_add_order_item_meta( $item_id, '_line_tax' . $key, wc_format_decimal( isset( $args['totals']['tax'] ) ? $args['totals']['tax'] : 0 ) ); // Add variation meta From 76d160c8fce2d3deeea8dcf42eeb8036d7882cec Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Fri, 13 Jun 2014 10:20:38 +0100 Subject: [PATCH 08/13] Fire action when adding product --- includes/class-wc-order.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/includes/class-wc-order.php b/includes/class-wc-order.php index de1da390d9a..cbf0d1d9d2c 100644 --- a/includes/class-wc-order.php +++ b/includes/class-wc-order.php @@ -108,7 +108,9 @@ class WC_Order { // Backorders if ( $product->backorders_require_notification() && $product->is_on_backorder( $qty ) ) { wc_add_order_item_meta( $item_id, apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $qty - max( 0, $product->get_total_stock() ) ); - } + } + + do_action( 'woocommerce_order_add_product', $this->id, $item_id, $product, $qty, $args ); return $item_id; } From 0f226917670c82e4a2f512ffd0078d44b255f078 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Fri, 13 Jun 2014 15:21:15 +0100 Subject: [PATCH 09/13] Calculate tax and totals methods --- includes/class-wc-order.php | 155 ++++++++++++++++++++++++++++++++++-- 1 file changed, 148 insertions(+), 7 deletions(-) diff --git a/includes/class-wc-order.php b/includes/class-wc-order.php index cbf0d1d9d2c..44ee99c27aa 100644 --- a/includes/class-wc-order.php +++ b/includes/class-wc-order.php @@ -37,11 +37,16 @@ class WC_Order { /** * Remove all line items (products, coupons, shipping, taxes) from the order. */ - public function remove_order_items() { + public function remove_order_items( $type = null ) { global $wpdb; - $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id IN ( SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d )", $order_id ) ); - $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $order_id ) ); + if ( $type ) { + $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id IN ( SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = %s )", $order_id, $type ) ); + $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = %s", $order_id, $type ) ); + } else { + $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id IN ( SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d )", $order_id ) ); + $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $order_id ) ); + } } /** @@ -95,10 +100,10 @@ class WC_Order { wc_add_order_item_meta( $item_id, '_variation_id', isset( $product->variation_id ) ? $product->variation_id : 0 ); // Set line item totals, either passed in or from the product - wc_add_order_item_meta( $item_id, '_line_subtotal' . $key, wc_format_decimal( isset( $args['totals']['subtotal'] ) ? $args['totals']['subtotal'] : $product->get_price_excluding_tax( $qty ) ) ); - wc_add_order_item_meta( $item_id, '_line_total' . $key, wc_format_decimal( isset( $args['totals']['total'] ) ? $args['total']['subtotal_tax'] : $product->get_price_excluding_tax( $qty ) ) ); - wc_add_order_item_meta( $item_id, '_line_subtotal_tax' . $key, wc_format_decimal( isset( $args['totals']['subtotal_tax'] ) ? $args['totals']['subtotal_tax'] : 0 ) ); - wc_add_order_item_meta( $item_id, '_line_tax' . $key, wc_format_decimal( isset( $args['totals']['tax'] ) ? $args['totals']['tax'] : 0 ) ); + wc_add_order_item_meta( $item_id, '_line_subtotal', wc_format_decimal( isset( $args['totals']['subtotal'] ) ? $args['totals']['subtotal'] : $product->get_price_excluding_tax( $qty ) ) ); + wc_add_order_item_meta( $item_id, '_line_total', wc_format_decimal( isset( $args['totals']['total'] ) ? $args['total']['subtotal_tax'] : $product->get_price_excluding_tax( $qty ) ) ); + wc_add_order_item_meta( $item_id, '_line_subtotal_tax', wc_format_decimal( isset( $args['totals']['subtotal_tax'] ) ? $args['totals']['subtotal_tax'] : 0 ) ); + wc_add_order_item_meta( $item_id, '_line_tax', wc_format_decimal( isset( $args['totals']['tax'] ) ? $args['totals']['tax'] : 0 ) ); // Add variation meta foreach ( $args['variation'] as $key => $value ) { @@ -190,6 +195,9 @@ class WC_Order { do_action( 'woocommerce_order_add_shipping', $this->id, $item_id, $shipping_rate ); + // Update total + $this->set_total( $this->order_shipping + wc_format_decimal( $shipping_rate->cost ), 'shipping' ); + return $item_id; } @@ -249,6 +257,139 @@ class WC_Order { update_post_meta( $this->id, $key, $amount ); } + /** + * Calculate taxes for all line items and shipping, and store the totals and tax rows. + * + * Will use the base country unless customer addresses are set. + * + * @return bool success or fail + */ + public function calculate_taxes() { + $shipping_tax_total = 0; + $tax_total = 0; + $taxes = array(); + $tax_based_on = get_option( 'woocommerce_tax_based_on' ); + + if ( 'base' === $tax_based_on ) { + $default = get_option( 'woocommerce_default_country' ); + $postcode = ''; + $city = ''; + if ( strstr( $default, ':' ) ) { + list( $country, $state ) = explode( ':', $default ); + } else { + $country = $default; + $state = ''; + } + } elseif ( 'billing' === $tax_based_on ) { + $country = $this->billing_country; + $state = $this->billing_state; + $postcode = $this->billing_postcode; + $city = $this->billing_city; + } else { + $country = $this->shipping_country; + $state = $this->shipping_state; + $postcode = $this->shipping_postcode; + $city = $this->shipping_city; + } + + // Get items + foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item ) { + $product = $this->get_product_from_item( $item ); + $line_total = isset( $item['line_total'] ) ? $item['line_total'] : 0; + $line_subtotal = isset( $item['line_subtotal'] ) ? $item['line_subtotal'] : 0; + $tax_class = $item['tax_class']; + $item_tax_status = $product ? $product->get_tax_status() : 'taxable'; + + if ( '0' !== $tax_class && 'taxable' === $item_tax_status ) { + $tax_rates = WC_Tax::find_rates( array( + 'country' => $country, + 'state' => $state, + 'postcode' => $postcode, + 'city' => $city, + 'tax_class' => $tax_class + ) ); + $line_subtotal_taxes = WC_Tax::calc_tax( $line_subtotal, $tax_rates, false ); + $line_taxes = WC_Tax::calc_tax( $line_total, $tax_rates, false ); + $line_subtotal_tax = max( 0, array_sum( $line_subtotal_taxes ) ); + $line_tax = max( 0, array_sum( $line_taxes ) ); + $tax_total += $line_tax; + + wc_update_order_item_meta( $item_id, '_line_subtotal_tax', wc_format_decimal( $line_subtotal_tax ) ); + wc_update_order_item_meta( $item_id, '_line_tax', wc_format_decimal( $line_tax ) ); + + // Sum the item taxes + foreach ( array_keys( $taxes + $line_taxes ) as $key ) { + $taxes[ $key ] = ( isset( $line_taxes[ $key ] ) ? $line_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 ); + } + } + } + + // Now calculate shipping tax + $matched_tax_rates = array(); + $tax_rates = WC_Tax::find_rates( array( + 'country' => $country, + 'state' => $state, + 'postcode' => $postcode, + 'city' => $city, + 'tax_class' => '' + ) ); + + if ( $tax_rates ) { + foreach ( $tax_rates as $key => $rate ) { + if ( isset( $rate['shipping'] ) && 'yes' === $rate['shipping'] ) { + $matched_tax_rates[ $key ] = $rate; + } + } + } + + $shipping_taxes = WC_Tax::calc_shipping_tax( $this->order_shipping, $matched_tax_rates ); + $shipping_tax_total = WC_Tax::round( array_sum( $shipping_taxes ) ); + + // Save tax totals + $this->set_total( $shipping_tax_total, 'shipping_tax' ); + $this->set_total( $tax_total, 'tax' ); + + // Tax rows + $this->remove_order_items( 'tax' ); + + // Now merge to keep tax rows + foreach ( array_keys( $taxes + $shipping_taxes ) as $tax_rate_id ) { + $this->add_tax( $tax_rate_id, isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0, isset( $shipping_taxes[ $key ] ) ? $shipping_taxes[ $key ] : 0 ); + } + + return true; + } + + /** + * Calculate totals by looking at the contents of the order. Stores the totals and returns the orders final total. + * + * @return $total calculated grand total + */ + public function calculate_totals() { + $cart_subtotal = 0; + $cart_total = 0; + $fee_total = 0; + + $this->calculate_taxes(); + + foreach ( $this->get_items() as $item ) { + $cart_subtotal += wc_format_decimal( isset( $item['line_subtotal'] ) ? $item['line_subtotal'] : 0 ); + $cart_total += wc_format_decimal( isset( $item['line_total'] ) ? $item['line_total'] : 0 ); + } + + foreach ( $this->get_fees() as $item ) { + $fee_total += $item['line_total']; + } + + $grand_total = round( $cart_total + $fee_total + $this->get_total_shipping() - $this->get_order_discount() + $this->get_cart_tax() + $this->get_shipping_tax(), absint( get_option( 'woocommerce_price_num_decimals' ) ) ); + + $this->set_total( $cart_subtotal - $cart_total, 'cart_discount' ); + $this->set_total( $shipping_total, 'shipping' ); + $this->set_total( $grand_total, 'total' ); + + return $grand_total; + } + /** * Gets an order from the database. * From 93778e72b021325dec472ca069334904282332b7 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Fri, 13 Jun 2014 15:53:59 +0100 Subject: [PATCH 10/13] Tax class fixes + notices --- assets/js/admin/meta-boxes.js | 1 - includes/class-wc-order.php | 16 ++++++++-------- includes/class-wc-tax.php | 10 +++++----- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/assets/js/admin/meta-boxes.js b/assets/js/admin/meta-boxes.js index 3f4c11d01e2..3d2e2b3f13c 100644 --- a/assets/js/admin/meta-boxes.js +++ b/assets/js/admin/meta-boxes.js @@ -451,7 +451,6 @@ jQuery( function($){ }; $.post( woocommerce_admin_meta_boxes.ajax_url, data, function( response ) { - if ( response ) { $items.each( function() { var $row = $(this); diff --git a/includes/class-wc-order.php b/includes/class-wc-order.php index 44ee99c27aa..65094b57a06 100644 --- a/includes/class-wc-order.php +++ b/includes/class-wc-order.php @@ -41,11 +41,11 @@ class WC_Order { global $wpdb; if ( $type ) { - $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id IN ( SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = %s )", $order_id, $type ) ); - $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = %s", $order_id, $type ) ); + $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id IN ( SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = %s )", $this->id, $type ) ); + $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = %s", $this->id, $type ) ); } else { - $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id IN ( SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d )", $order_id ) ); - $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $order_id ) ); + $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id IN ( SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d )", $this->id ) ); + $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $this->id ) ); } } @@ -165,8 +165,8 @@ class WC_Order { } wc_add_order_item_meta( $item_id, 'rate_id', $tax_rate_id ); - wc_add_order_item_meta( $item_id, 'label', WC_Tax::get_rate_label( $key ) ); - wc_add_order_item_meta( $item_id, 'compound', WC_Tax::is_compound( $key ) ? 1 : 0 ); + wc_add_order_item_meta( $item_id, 'label', WC_Tax::get_rate_label( $tax_rate_id ) ); + wc_add_order_item_meta( $item_id, 'compound', WC_Tax::is_compound( $tax_rate_id ) ? 1 : 0 ); wc_add_order_item_meta( $item_id, 'tax_amount', wc_format_decimal( $tax_amount ) ); wc_add_order_item_meta( $item_id, 'shipping_tax_amount', wc_format_decimal( $shipping_tax_amount ) ); @@ -354,7 +354,7 @@ class WC_Order { // Now merge to keep tax rows foreach ( array_keys( $taxes + $shipping_taxes ) as $tax_rate_id ) { - $this->add_tax( $tax_rate_id, isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0, isset( $shipping_taxes[ $key ] ) ? $shipping_taxes[ $key ] : 0 ); + $this->add_tax( $tax_rate_id, isset( $taxes[ $tax_rate_id ] ) ? $taxes[ $tax_rate_id ] : 0, isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0 ); } return true; @@ -1284,7 +1284,7 @@ class WC_Order { * @return WC_Product */ public function get_product_from_item( $item ) { - $_product = get_product( $item['variation_id'] ? $item['variation_id'] : $item['product_id'] ); + $_product = get_product( ! empty( $item['variation_id'] ) ? $item['variation_id'] : $item['product_id'] ); return apply_filters( 'woocommerce_get_product_from_item', $_product, $item, $this ); } diff --git a/includes/class-wc-tax.php b/includes/class-wc-tax.php index b1177a88996..82e315b53c8 100644 --- a/includes/class-wc-tax.php +++ b/includes/class-wc-tax.php @@ -33,7 +33,7 @@ class WC_Tax { */ public static function calc_tax( $price, $rates, $price_includes_tax = false, $suppress_rounding = false ) { // Work in pence to X precision - $price = self::$precision( $price ); + $price = self::precision( $price ); if ( $price_includes_tax ) $taxes = self::calc_inclusive_tax( $price, $rates ); @@ -47,7 +47,7 @@ class WC_Tax { // Remove precision $price = self::remove_precision( $price ); - $taxes = array_map( array( $this, 'remove_precision' ), $taxes ); + $taxes = array_map( array( __CLASS__, 'remove_precision' ), $taxes ); return apply_filters( 'woocommerce_calc_tax', $taxes, $price, $rates, $price_includes_tax, $suppress_rounding ); } @@ -528,7 +528,7 @@ class WC_Tax { if ( ! $rate_name ) $rate_name = WC()->countries->tax_or_vat(); - return apply_filters( 'woocommerce_rate_label', $rate_name, $key, $this ); + return apply_filters( 'woocommerce_rate_label', $rate_name, $key ); } /** @@ -554,7 +554,7 @@ class WC_Tax { $code[] = $rate->tax_rate_name ? $rate->tax_rate_name : 'TAX'; $code[] = absint( $rate->tax_rate_priority ); - return apply_filters( 'woocommerce_rate_code', strtoupper( implode( '-', array_filter( $code ) ) ), $key, $this ); + return apply_filters( 'woocommerce_rate_code', strtoupper( implode( '-', array_filter( $code ) ) ), $key ); } /** @@ -564,7 +564,7 @@ class WC_Tax { * @return float */ public static function get_tax_total( $taxes ) { - return array_sum( array_map( array( $this, 'round' ), $taxes ) ); + return array_sum( array_map( array( __CLASS__, 'round' ), $taxes ) ); } } WC_Tax::init(); \ No newline at end of file From f37d6e12b8f1893ce278a0462315dd5ac387f78f Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Fri, 13 Jun 2014 15:54:08 +0100 Subject: [PATCH 11/13] Cleaned up ajax tax calc function --- includes/class-wc-ajax.php | 74 +++++--------------------------------- 1 file changed, 8 insertions(+), 66 deletions(-) diff --git a/includes/class-wc-ajax.php b/includes/class-wc-ajax.php index e318fbf9f5a..05148600d63 100644 --- a/includes/class-wc-ajax.php +++ b/includes/class-wc-ajax.php @@ -1209,17 +1209,12 @@ class WC_AJAX { // Calculate sales tax first if ( sizeof( $items ) > 0 ) { foreach( $items as $item_id => $item ) { - $item_id = absint( $item_id ); $line_subtotal = isset( $item['line_subtotal'] ) ? wc_format_decimal( $item['line_subtotal'] ) : 0; $line_total = wc_format_decimal( $item['line_total'] ); $tax_class = sanitize_text_field( $item['tax_class'] ); $product_id = $order->get_item_meta( $item_id, '_product_id', true ); - if ( ! $item_id || '0' == $tax_class ) { - continue; - } - // Get product details if ( get_post_type( $product_id ) == 'product' ) { $_product = get_product( $product_id ); @@ -1228,9 +1223,7 @@ class WC_AJAX { $item_tax_status = 'taxable'; } - // Only calc if taxable - if ( 'taxable' == $item_tax_status ) { - + if ( '0' !== $tax_class && 'taxable' === $item_tax_status ) { $tax_rates = WC_Tax::find_rates( array( 'country' => $country, 'state' => $state, @@ -1241,16 +1234,8 @@ class WC_AJAX { $line_subtotal_taxes = WC_Tax::calc_tax( $line_subtotal, $tax_rates, false ); $line_taxes = WC_Tax::calc_tax( $line_total, $tax_rates, false ); - $line_subtotal_tax = array_sum( $line_subtotal_taxes ); - $line_tax = array_sum( $line_taxes ); - - if ( $line_subtotal_tax < 0 ) { - $line_subtotal_tax = 0; - } - - if ( $line_tax < 0 ) { - $line_tax = 0; - } + $line_subtotal_tax = max( 0, array_sum( $line_subtotal_taxes ) ); + $line_tax = max( 0, array_sum( $line_taxes ) ); $item_taxes[ $item_id ] = array( 'line_subtotal_tax' => wc_format_localized_price( $line_subtotal_tax ), @@ -1264,7 +1249,6 @@ class WC_AJAX { $taxes[ $key ] = ( isset( $line_taxes[ $key ] ) ? $line_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 ); } } - } } @@ -1291,58 +1275,16 @@ class WC_AJAX { $shipping_tax = WC_Tax::round( array_sum( $shipping_taxes ) ); // Remove old tax rows - $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id IN ( SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = 'tax' )", $order_id ) ); + $order->remove_order_items( 'tax' ); - $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = 'tax'", $order_id ) ); - - // Get tax rates - $rates = $wpdb->get_results( "SELECT tax_rate_id, tax_rate_country, tax_rate_state, tax_rate_name, tax_rate_priority FROM {$wpdb->prefix}woocommerce_tax_rates ORDER BY tax_rate_name" ); - - $tax_codes = array(); - - foreach( $rates as $rate ) { - $code = array(); - - $code[] = $rate->tax_rate_country; - $code[] = $rate->tax_rate_state; - $code[] = $rate->tax_rate_name ? sanitize_title( $rate->tax_rate_name ) : 'TAX'; - $code[] = absint( $rate->tax_rate_priority ); - - $tax_codes[ $rate->tax_rate_id ] = strtoupper( implode( '-', array_filter( $code ) ) ); + // Add tax rows + foreach ( array_keys( $taxes + $shipping_taxes ) as $tax_rate_id ) { + $order->add_tax( $tax_rate_id, isset( $taxes[ $tax_rate_id ] ) ? $taxes[ $tax_rate_id ] : 0, isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0 ); } - // Now merge to keep tax rows ob_start(); - foreach ( array_keys( $taxes + $shipping_taxes ) as $key ) { - - $item = array(); - $item['rate_id'] = $key; - $item['name'] = $tax_codes[ $key ]; - $item['label'] = WC_Tax::get_rate_label( $key ); - $item['compound'] = WC_Tax::is_compound( $key ) ? 1 : 0; - $item['tax_amount'] = wc_format_decimal( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 ); - $item['shipping_tax_amount'] = wc_format_decimal( isset( $shipping_taxes[ $key ] ) ? $shipping_taxes[ $key ] : 0 ); - - if ( ! $item['label'] ) { - $item['label'] = WC()->countries->tax_or_vat(); - } - - // Add line item - $item_id = wc_add_order_item( $order_id, array( - 'order_item_name' => $item['name'], - 'order_item_type' => 'tax' - ) ); - - // Add line item meta - if ( $item_id ) { - wc_add_order_item_meta( $item_id, 'rate_id', $item['rate_id'] ); - wc_add_order_item_meta( $item_id, 'label', $item['label'] ); - wc_add_order_item_meta( $item_id, 'compound', $item['compound'] ); - wc_add_order_item_meta( $item_id, 'tax_amount', $item['tax_amount'] ); - wc_add_order_item_meta( $item_id, 'shipping_tax_amount', $item['shipping_tax_amount'] ); - } - + foreach ( $order->get_taxes() as $item_id => $item ) { include( 'admin/meta-boxes/views/html-order-tax.php' ); } From 94549e146265beb902953eab39219ee8f2609980 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Fri, 13 Jun 2014 16:11:15 +0100 Subject: [PATCH 12/13] Notices --- includes/class-wc-order.php | 3 +-- includes/wc-core-functions.php | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/includes/class-wc-order.php b/includes/class-wc-order.php index 65094b57a06..1db44c7044d 100644 --- a/includes/class-wc-order.php +++ b/includes/class-wc-order.php @@ -293,7 +293,7 @@ class WC_Order { } // Get items - foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item ) { + foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) { $product = $this->get_product_from_item( $item ); $line_total = isset( $item['line_total'] ) ? $item['line_total'] : 0; $line_subtotal = isset( $item['line_subtotal'] ) ? $item['line_subtotal'] : 0; @@ -384,7 +384,6 @@ class WC_Order { $grand_total = round( $cart_total + $fee_total + $this->get_total_shipping() - $this->get_order_discount() + $this->get_cart_tax() + $this->get_shipping_tax(), absint( get_option( 'woocommerce_price_num_decimals' ) ) ); $this->set_total( $cart_subtotal - $cart_total, 'cart_discount' ); - $this->set_total( $shipping_total, 'shipping' ); $this->set_total( $grand_total, 'total' ); return $grand_total; diff --git a/includes/wc-core-functions.php b/includes/wc-core-functions.php index 016e1f23235..8781c5c8ed9 100644 --- a/includes/wc-core-functions.php +++ b/includes/wc-core-functions.php @@ -49,7 +49,7 @@ add_filter( 'woocommerce_short_description', 'do_shortcode', 11 ); // AFTER wpau * * @return WC_Order on success, WP_Error on failure */ -function wc_create_order( $args ) { +function wc_create_order( $args = array() ) { $default_args = array( 'status' => '', 'customer_id' => null, From f2ca5e76bdbbe2908aac98c87259a997bd676920 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Wed, 18 Jun 2014 16:03:46 +0100 Subject: [PATCH 13/13] Use a transaction during checkout's order creation method to ensure all data gets set Closes #5368 --- includes/class-wc-checkout.php | 290 ++++++++++++++++++--------------- 1 file changed, 162 insertions(+), 128 deletions(-) diff --git a/includes/class-wc-checkout.php b/includes/class-wc-checkout.php index 37ae424186a..28dd7347b32 100644 --- a/includes/class-wc-checkout.php +++ b/includes/class-wc-checkout.php @@ -155,148 +155,179 @@ class WC_Checkout { * create_order function. * @access public * @throws Exception - * @return int + * @return int|WP_ERROR */ public function create_order() { + global $wpdb; + // Give plugins the opportunity to create an order themselves if ( $order_id = apply_filters( 'woocommerce_create_order', null, $this ) ) { return $order_id; } - $order_data = array( - 'status' => apply_filters( 'woocommerce_default_order_status', 'pending' ), - 'customer_id' => $this->customer_id, - 'customer_note' => isset( $this->posted['order_comments'] ) ? $this->posted['order_comments'] : '' - ); + try { + // Start transaction if available + $wpdb->query( 'START TRANSACTION' ); - // Insert or update the post data - $order_id = absint( WC()->session->order_awaiting_payment ); - - // Resume the unpaid order if its pending - if ( $order_id > 0 && ( $order = new WC_Order( $order_id ) ) && $order->has_status( array( 'pending', 'failed' ) ) ) { - - $order_data['order_id'] = $order_id; - $order = wc_update_order( $order_data ); - - if ( is_wp_error( $order ) ) { - throw new Exception( 'Error: Unable to update order. Please try again.' ); - } else { - $order->remove_order_items(); - do_action( 'woocommerce_resume_order', $order_id ); - } - - } else { - - $order = wc_create_order( $order_data ); - - if ( is_wp_error( $order ) ) { - throw new Exception( 'Error: Unable to create order. Please try again.' ); - } else { - $order_id = $order->id; - do_action( 'woocommerce_new_order', $order_id ); - } - } - - // Store the line items to the new/resumed order - foreach ( WC()->cart->get_cart() as $cart_item_key => $values ) { - $item_id = $order->add_product( - $values['data'], - $values['quantity'], - array( - 'variation' => $values['variation'], - 'totals' => array( - 'subtotal' => $values['line_subtotal'], - 'subtotal_tax' => $values['line_subtotal_tax'], - 'total' => $values['line_total'], - 'tax' => $values['line_tax'] - ) - ) + $order_data = array( + 'status' => apply_filters( 'woocommerce_default_order_status', 'pending' ), + 'customer_id' => $this->customer_id, + 'customer_note' => isset( $this->posted['order_comments'] ) ? $this->posted['order_comments'] : '' ); - // Allow plugins to add order item meta - do_action( 'woocommerce_add_order_item_meta', $item_id, $values, $cart_item_key ); - } + // Insert or update the post data + $order_id = absint( WC()->session->order_awaiting_payment ); - // Store fees - foreach ( WC()->cart->get_fees() as $fee_key => $fee ) { - $item_id = $order->add_fee( $fee ); - - // Allow plugins to add order item meta to fees - do_action( 'woocommerce_add_order_fee_meta', $order_id, $item_id, $fee, $fee_key ); - } + // Resume the unpaid order if its pending + if ( $order_id > 0 && ( $order = new WC_Order( $order_id ) ) && $order->has_status( array( 'pending', 'failed' ) ) ) { - // Store shipping for all packages - foreach ( WC()->shipping->get_packages() as $package_key => $package ) { - if ( isset( $package['rates'][ $this->shipping_methods[ $package_key ] ] ) ) { - $item_id = $order->add_shipping( $package['rates'][ $this->shipping_methods[ $package_key ] ] ); - - // Allows plugins to add order item meta to shipping - do_action( 'woocommerce_add_shipping_order_item', $order_id, $item_id, $package_key ); - } - } + $order_data['order_id'] = $order_id; + $order = wc_update_order( $order_data ); - // Store tax rows - foreach ( array_keys( WC()->cart->taxes + WC()->cart->shipping_taxes ) as $tax_rate_id ) { - $order->add_tax( $tax_rate_id, WC()->cart->get_tax_amount( $tax_rate_id ), WC()->cart->get_shipping_tax_amount( $tax_rate_id ) ); - } - - // Store coupons - foreach ( WC()->cart->get_coupons() as $code => $coupon ) { - $order->add_coupon( $code, WC()->cart->get_coupon_discount_amount( $code ) ); - } - - // Billing address - $billing_address = array( - 'first_name' => $this->get_posted_address_data( 'first_name' ), - 'last_name' => $this->get_posted_address_data( 'last_name' ), - 'company' => $this->get_posted_address_data( 'company' ), - 'email' => $this->get_posted_address_data( 'email' ), - 'phone' => $this->get_posted_address_data( 'phone' ), - 'address_1' => $this->get_posted_address_data( 'address_1' ), - 'address_2' => $this->get_posted_address_data( 'address_2' ), - 'city' => $this->get_posted_address_data( 'city' ), - 'state' => $this->get_posted_address_data( 'state' ), - 'postcode' => $this->get_posted_address_data( 'postcode' ), - 'country' => $this->get_posted_address_data( 'country' ) - ); - - $shipping_address = array( - 'first_name' => $this->get_posted_address_data( 'first_name', 'shipping' ), - 'last_name' => $this->get_posted_address_data( 'last_name', 'shipping' ), - 'company' => $this->get_posted_address_data( 'company', 'shipping' ), - 'address_1' => $this->get_posted_address_data( 'address_1', 'shipping' ), - 'address_2' => $this->get_posted_address_data( 'address_2', 'shipping' ), - 'city' => $this->get_posted_address_data( 'city', 'shipping' ), - 'state' => $this->get_posted_address_data( 'state', 'shipping' ), - 'postcode' => $this->get_posted_address_data( 'postcode', 'shipping' ), - 'country' => $this->get_posted_address_data( 'country', 'shipping' ), - ); - - $order->set_address( $billing_address, 'billing' ); - $order->set_address( $shipping_address, 'shipping' ); - $order->set_payment_method( $this->payment_method ); - $order->set_total( WC()->cart->shipping_total, 'shipping' ); - $order->set_total( WC()->cart->get_order_discount_total(), 'order_discount' ); - $order->set_total( WC()->cart->get_cart_discount_total(), 'cart_discount' ); - $order->set_total( WC()->cart->tax_total, 'tax' ); - $order->set_total( WC()->cart->shipping_tax_total, 'shipping_tax' ); - $order->set_total( WC()->cart->total ); - - // Update user meta - if ( $this->customer_id ) { - if ( apply_filters( 'woocommerce_checkout_update_customer_data', true, $this ) ) { - foreach( $billing_address as $key => $value ) { - update_user_meta( $this->customer_id, 'billing_' . $key, $value ); + if ( is_wp_error( $order ) ) { + throw new Exception( __( 'Error: Unable to create order. Please try again.', 'woocommerce' ) ); + } else { + $order->remove_order_items(); + do_action( 'woocommerce_resume_order', $order_id ); } - foreach( $shipping_address as $key => $value ) { - update_user_meta( $this->customer_id, 'shipping_' . $key, $value ); + + } else { + + $order = wc_create_order( $order_data ); + + if ( is_wp_error( $order ) ) { + throw new Exception( __( 'Error: Unable to create order. Please try again.', 'woocommerce' ) ); + } else { + $order_id = $order->id; + do_action( 'woocommerce_new_order', $order_id ); } } - do_action( 'woocommerce_checkout_update_user_meta', $this->customer_id, $this->posted ); - } - // Let plugins add meta - do_action( 'woocommerce_checkout_update_order_meta', $order_id, $this->posted ); + // Store the line items to the new/resumed order + foreach ( WC()->cart->get_cart() as $cart_item_key => $values ) { + $item_id = $order->add_product( + $values['data'], + $values['quantity'], + array( + 'variation' => $values['variation'], + 'totals' => array( + 'subtotal' => $values['line_subtotal'], + 'subtotal_tax' => $values['line_subtotal_tax'], + 'total' => $values['line_total'], + 'tax' => $values['line_tax'] + ) + ) + ); + + if ( ! $item_id ) { + throw new Exception( __( 'Error: Unable to create order. Please try again.', 'woocommerce' ) ); + } + + // Allow plugins to add order item meta + do_action( 'woocommerce_add_order_item_meta', $item_id, $values, $cart_item_key ); + } + + // Store fees + foreach ( WC()->cart->get_fees() as $fee_key => $fee ) { + $item_id = $order->add_fee( $fee ); + + if ( ! $item_id ) { + throw new Exception( __( 'Error: Unable to create order. Please try again.', 'woocommerce' ) ); + } + + // Allow plugins to add order item meta to fees + do_action( 'woocommerce_add_order_fee_meta', $order_id, $item_id, $fee, $fee_key ); + } + + // Store shipping for all packages + foreach ( WC()->shipping->get_packages() as $package_key => $package ) { + if ( isset( $package['rates'][ $this->shipping_methods[ $package_key ] ] ) ) { + $item_id = $order->add_shipping( $package['rates'][ $this->shipping_methods[ $package_key ] ] ); + + if ( ! $item_id ) { + throw new Exception( __( 'Error: Unable to create order. Please try again.', 'woocommerce' ) ); + } + + // Allows plugins to add order item meta to shipping + do_action( 'woocommerce_add_shipping_order_item', $order_id, $item_id, $package_key ); + } + } + + // Store tax rows + foreach ( array_keys( WC()->cart->taxes + WC()->cart->shipping_taxes ) as $tax_rate_id ) { + if ( ! $order->add_tax( $tax_rate_id, WC()->cart->get_tax_amount( $tax_rate_id ), WC()->cart->get_shipping_tax_amount( $tax_rate_id ) ) ) { + throw new Exception( __( 'Error: Unable to create order. Please try again.', 'woocommerce' ) ); + } + } + + // Store coupons + foreach ( WC()->cart->get_coupons() as $code => $coupon ) { + if ( ! $order->add_coupon( $code, WC()->cart->get_coupon_discount_amount( $code ) ) ) { + throw new Exception( __( 'Error: Unable to create order. Please try again.', 'woocommerce' ) ); + } + } + + // Billing address + $billing_address = array( + 'first_name' => $this->get_posted_address_data( 'first_name' ), + 'last_name' => $this->get_posted_address_data( 'last_name' ), + 'company' => $this->get_posted_address_data( 'company' ), + 'email' => $this->get_posted_address_data( 'email' ), + 'phone' => $this->get_posted_address_data( 'phone' ), + 'address_1' => $this->get_posted_address_data( 'address_1' ), + 'address_2' => $this->get_posted_address_data( 'address_2' ), + 'city' => $this->get_posted_address_data( 'city' ), + 'state' => $this->get_posted_address_data( 'state' ), + 'postcode' => $this->get_posted_address_data( 'postcode' ), + 'country' => $this->get_posted_address_data( 'country' ) + ); + + $shipping_address = array( + 'first_name' => $this->get_posted_address_data( 'first_name', 'shipping' ), + 'last_name' => $this->get_posted_address_data( 'last_name', 'shipping' ), + 'company' => $this->get_posted_address_data( 'company', 'shipping' ), + 'address_1' => $this->get_posted_address_data( 'address_1', 'shipping' ), + 'address_2' => $this->get_posted_address_data( 'address_2', 'shipping' ), + 'city' => $this->get_posted_address_data( 'city', 'shipping' ), + 'state' => $this->get_posted_address_data( 'state', 'shipping' ), + 'postcode' => $this->get_posted_address_data( 'postcode', 'shipping' ), + 'country' => $this->get_posted_address_data( 'country', 'shipping' ), + ); + + $order->set_address( $billing_address, 'billing' ); + $order->set_address( $shipping_address, 'shipping' ); + $order->set_payment_method( $this->payment_method ); + $order->set_total( WC()->cart->shipping_total, 'shipping' ); + $order->set_total( WC()->cart->get_order_discount_total(), 'order_discount' ); + $order->set_total( WC()->cart->get_cart_discount_total(), 'cart_discount' ); + $order->set_total( WC()->cart->tax_total, 'tax' ); + $order->set_total( WC()->cart->shipping_tax_total, 'shipping_tax' ); + $order->set_total( WC()->cart->total ); + + // Update user meta + if ( $this->customer_id ) { + if ( apply_filters( 'woocommerce_checkout_update_customer_data', true, $this ) ) { + foreach( $billing_address as $key => $value ) { + update_user_meta( $this->customer_id, 'billing_' . $key, $value ); + } + foreach( $shipping_address as $key => $value ) { + update_user_meta( $this->customer_id, 'shipping_' . $key, $value ); + } + } + do_action( 'woocommerce_checkout_update_user_meta', $this->customer_id, $this->posted ); + } + + // Let plugins add meta + do_action( 'woocommerce_checkout_update_order_meta', $order_id, $this->posted ); + + // If we got here, the order was created without problems! + $wpdb->query( 'COMMIT' ); + + } catch ( Exception $e ) { + // There was an error adding order data! + $wpdb->query( 'ROLLBACK' ); + return new WP_Error( 'checkout-error', $e->getMessage() ); + } return $order_id; } @@ -568,6 +599,10 @@ class WC_Checkout { $order_id = $this->create_order(); + if ( is_wp_error( $order_id ) ) { + throw new Exception( $order_id->get_error_message() ); + } + do_action( 'woocommerce_checkout_order_processed', $order_id, $this->posted ); // Process payment @@ -627,10 +662,9 @@ class WC_Checkout { } } catch ( Exception $e ) { - - if ( ! empty( $e ) ) + if ( ! empty( $e ) ) { wc_add_notice( $e->getMessage(), 'error' ); - + } } } // endif