27

I needed to create a Woocommerce order programatically, however using the 'old' Woocommerce made this a very dirty procedure.

I had to insert all kind of database records manually, using many update_post_meta calls.

Looking for a better solution.

LoicTheAztec
  • 229,944
  • 23
  • 356
  • 399
Mattijs
  • 3,265
  • 3
  • 38
  • 35

4 Answers4

49

With latest version of WooCommerce is possible try this as something like

$address = array(
            'first_name' => 'Fresher',
            'last_name'  => 'StAcK OvErFloW',
            'company'    => 'stackoverflow',
            'email'      => 'test@test.com',
            'phone'      => '777-777-777-777',
            'address_1'  => '31 Main Street',
            'address_2'  => '', 
            'city'       => 'Chennai',
            'state'      => 'TN',
            'postcode'   => '12345',
            'country'    => 'IN'
        );

        $order = wc_create_order();
        $order->add_product( get_product( '12' ), 2 ); //(get_product with id and next is for quantity)
        $order->set_address( $address, 'billing' );
        $order->set_address( $address, 'shipping' );
        $order->add_coupon('Fresher','10','2'); // accepted param $couponcode, $couponamount,$coupon_tax
        $order->calculate_totals();

Call this above code with your function then it will work accordingly.

Note it not work with old version of WooCommerce like 2.1.12, It works only from 2.2 of WooCommerce.

Hope it helps

Vignesh Pichamani
  • 7,950
  • 22
  • 77
  • 115
  • yep, that would circumvent the REST API classes. Good point. You probably don't have to include any files to make this work, right? Much shorter solution, not bad! I'll try it and let you know. – Mattijs Oct 27 '14 at 23:02
  • 2
    It's very helpful to use this function `get_class_methods( $order) )` to see which functions are available to you when you have the order object! – Mattijs Oct 31 '14 at 05:28
  • Nice. What would I call to place this order? – Kirby Apr 21 '15 at 18:44
  • From what I've done this associates with a Guest user. How do I get it to associate with the current logged in user? – Kirby Apr 21 '15 at 23:50
  • I had same problem. Seems like some sort of API change as it worked without associating with Guest user before. Added following after the calculate_totals() to get it going ($orderId = ID of freshly created order, $userId = wp's customer's user id): update_post_meta( $orderId, '_customer_user', $userId ); – dsomnus Jun 23 '15 at 18:43
  • If i create an order this way, then the $order->get_formatted_billing_address() doesn't work properly. It doesn't apply my filters. How could I fix this? – horace Jul 10 '15 at 13:08
  • @horace may be you not have the billing info for the specified user? what doesn't work properly? – Vignesh Pichamani Jul 10 '15 at 13:24
  • I created the order like in your example. With billing info and shipping info. But there is the woocommerce_formatted_address_replacements filter hook I use to format the addresses (it gets used on the [my account] page for example). The filter gets called for normal shop orders, but not for the ones I create programmatically. --- I also have a problem with adding shipping costs. How do I get the object I have to pass to the add_shipping() method? Would be great if you could add this also in your example. :) – horace Jul 10 '15 at 13:50
  • I have another problem. How can I achieve that a notification gets sent to the shop admin after the order is created? Of course I can send my own with php mail() or something but I want to send the official woocommerce admin notification mail if possible. – horace Jul 20 '15 at 14:42
  • @kirby: wc_create_order itself places the order i.e. adds it in the database. (You probably figured it out already but just for future reference...) – jarnoan Oct 15 '15 at 06:15
  • @jarnoan, Thanks. Yeah, I figured it out. Though for the life of me I can't remember the solution...! – Kirby Oct 19 '15 at 17:11
  • Could you please check my question http://stackoverflow.com/questions/36526268/sql-query-to-download-order-report-in-woocommerce . – Manik Apr 11 '16 at 06:21
  • how to add order note? – Tomasz Mularczyk Feb 05 '17 at 16:28
  • @VigneshPichamani Hi! How can I use your code for REST API? as I need to apply coupon to the order which is going to be created through android app – DD77 May 05 '17 at 08:26
15

2017-2023 For WooCommerce 3 and Above

Woocommerce 3 has introduced CRUD objects and there is lot of changes on Order items… Also some WC_Order methods are now deprecated like add_coupon().

Here is a function that allow creating programmatically an order nicely with all required data in it, including the taxes:

function create_wc_order( $data ){
    $gateways = WC()->payment_gateways->get_available_payment_gateways();
    $order    = new WC_Order();

    // Set Billing and Shipping adresses
    foreach( array('billing_', 'shipping_') as $type ) {
        foreach ( $data['address'] as $key => $value ) {
            if( $type === 'shipping_' && in_array( $key, array( 'email', 'phone' ) ) )
                continue;

            $type_key = $type.$key;

            if ( is_callable( array( $order, "set_{$type_key}" ) ) ) {
                $order->{"set_{$type_key}"}( $value );
            }
        }
    }

    // Set other details
    $order->set_created_via( 'programatically' );
    $order->set_customer_id( $data['user_id'] );
    $order->set_currency( get_woocommerce_currency() );
    $order->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
    $order->set_customer_note( isset( $data['order_comments'] ) ? $data['order_comments'] : '' );
    $order->set_payment_method( isset( $gateways[ $data['payment_method'] ] ) ? $gateways[ $data['payment_method'] ] : $data['payment_method'] );

    $calculate_taxes_for = array(
        'country'  => $data['address']['country'],
        'state'    => $data['address']['state'],
        'postcode' => $data['address']['postcode'],
        'city'     => $data['address']['city']
    );

    // Line items
    foreach( $data['line_items'] as $line_item ) {
        $args = $line_item['args'];
        $product = wc_get_product( isset($args['variation_id']) && $args['variation_id'] > 0 ? $$args['variation_id'] : $args['product_id'] );
        $item_id = $order->add_product( $product, $line_item['quantity'], $line_item['args'] );

        $item    = $order->get_item( $item_id, false );

        $item->calculate_taxes($calculate_taxes_for);
        $item->save();
    }

    // Coupon items
    if( isset($data['coupon_items'])){
        foreach( $data['coupon_items'] as $coupon_item ) {
            $order->apply_coupon(sanitize_title($coupon_item['code']));
        }
    }

    // Fee items
    if( isset($data['fee_items'])){
        foreach( $data['fee_items'] as $fee_item ) {
            $item = new WC_Order_Item_Fee();

            $item->set_name( $fee_item['name'] );
            $item->set_total( $fee_item['total'] );
            $tax_class = isset($fee_item['tax_class']) && $fee_item['tax_class'] != 0 ? $fee_item['tax_class'] : 0;
            $item->set_tax_class( $tax_class ); // O if not taxable

            $item->calculate_taxes($calculate_taxes_for);

            $item->save();
            $order->add_item( $item );
        }
    }

    // Set calculated totals
    $order->calculate_totals();
        
    if( isset($data['order_status']) ) {
        // Update order status from pending to your defined status and save data
        $order->update_status($data['order_status']['status'], $data['order_status']['note']);
        $order_id = $order->get_id();
    } else {
        // Save order to database (returns the order ID)
        $order_id = $order->save();
    }
    
    // Returns the order ID
    return $order_id;
}

Code goes in function.php file of your active child theme (or active theme) or in a plugin file.


USAGE EXAMPLE from a data array:

create_wc_order( array(
    'address' => array(
        'first_name' => 'Fresher',
        'last_name'  => 'StAcK OvErFloW',
        'company'    => 'stackoverflow',
        'email'      => 'test1@testoo.com',
        'phone'      => '777-777-777-777',
        'address_1'  => '31 Main Street',
        'address_2'  => '',
        'city'       => 'Chennai',
        'state'      => 'TN',
        'postcode'   => '12345',
        'country'    => 'IN',
    ),
    'user_id'        => '',
    'order_comments' => '',
    'payment_method' => 'bacs',
    'order_status'   => array(
        'status' => 'on-hold',
        'note'   => '',
    ),
    'line_items' => array(
        array(
            'quantity' => 1,
            'args'     => array(
                'product_id'    => 37,
                'variation_id'  => '',
                'variation'     => array(),
            )
        ),
    ),
    'coupon_items' => array(
        array(
            'code'         => 'summer',
        ),
    ),
    'fee_items' => array(
        array(
            'name'      => 'Delivery',
            'total'     => 5,
            'tax_class' => 0, // Not taxable
        ),
    ),
) );
LoicTheAztec
  • 229,944
  • 23
  • 356
  • 399
  • Have you dealt with Notice: woocommerce_order_add_product is deprecated since version 3.0! Use woocommerce_new_order_item action instead When you call add_product ? – Dan Aug 10 '19 at 18:45
  • @Dan Yes, but it's not related to this thread (it's about hooks in your case, depending on what you are trying to do). You should ask a new question may be… – LoicTheAztec Aug 10 '19 at 18:55
7

With the new release of WC 2, it's much better.

However:

  • I do not want to use the REST API, cause I am doing a call from my own WP plugin directly. I see no use in doing a curl to my localhost
  • The 'WooCommerce REST API Client Library' is not useful for me cause it relay's on the REST API and it doesn't support a Create Order call

To be honest, WooCom's API Docs are limited, maybe they are still in the progress of updating it. They currently don't tell me how to create a new order, which params are required etc.

Any way, I figured out how to create an order with a line order (your product) using the classes and functions used by the REST API and I want to share it!

I created my own PHP class:

class WP_MyPlugin_woocommerce
{

public static function init()
{
    // required classes to create an order
    require_once WOOCOMMERCE_API_DIR . 'class-wc-api-exception.php';
    require_once WOOCOMMERCE_API_DIR . 'class-wc-api-server.php';
    require_once WOOCOMMERCE_API_DIR . 'class-wc-api-resource.php';
    require_once WOOCOMMERCE_API_DIR . 'interface-wc-api-handler.php';
    require_once WOOCOMMERCE_API_DIR . 'class-wc-api-json-handler.php';
    require_once WOOCOMMERCE_API_DIR . 'class-wc-api-orders.php';
}

public static function create_order()
{
    global $wp;

    // create order
    $server = new WC_API_Server( $wp->query_vars['wc-api-route'] );
    $order = new WC_API_Orders( $server );

    $order_id = $order->create_order( array
    (
        'order'             => array
        (
           'status'            => 'processing'
        ,  'customer_id'       =>  get_current_user_id()
        // ,   'order_meta'        => array
        //     (
        //        'some order meta'         => 'a value
        //     ,   some more order meta'    => 1
        //     )
        ,   'shipping_address'        => array
            (
                'first_name'          => $firstname
            ,   'last_name'           => $lastname
            ,   'address_1'           => $address
            ,   'address_2'           => $address2
            ,   'city'                => $city
            ,   'postcode'            => $postcode
            ,   'state'               => $state
            ,   'country'             => $country
            )

        ,   'billing_address'        => array(..can be same as shipping )

        ,   'line_items'        => array
            (
                array
                (
                    'product_id'         => 258
                ,   'quantity'           => 1
                )
            )
        )
    ) );
    var_dump($order_id);
    die();
}
}

Important:

  • The 'WOOCOMMERCE_API_DIR' constant points to '/woocommerce/includes/api/' in your plugin dir.
  • The order is assigned to a customer, in my case the current logged in user. Make sure your user has a role that has the capability to read, edit, create and delete orders. My role looks like this:

       $result = add_role(
        'customer'
    ,   __( 'Customer' )
    ,   array
        (
            'read'         => true
        // ,   'read_private_posts' => true
        // ,   'read_private_products' => true
        ,   'read_private_shop_orders' => true
        ,   'edit_private_shop_orders' => true
        ,   'delete_private_shop_orders' => true
        ,   'publish_shop_orders' => true
        // ,   'read_private_shop_coupons' => true
        ,   'edit_posts'   => false
        ,   'delete_posts' => false
        ,   'show_admin_bar_front' => false
        )
    );
    
  • If you want to look at the shop manager's rights, check

    var_dump(get_option( 'wp_user_roles'));

My create_order function nicely creates an order, with the lineitem in the order_items tables.

Hope I helped you out, it took me a while to get it right.

Mattijs
  • 3,265
  • 3
  • 38
  • 35
  • Could you please check my question http://stackoverflow.com/questions/36526268/sql-query-to-download-order-report-in-woocommerce . – Manik Apr 11 '16 at 06:21
0

A lot of answers do you show the best filter(hook) to use. I searched for days because when I used the 'init' it kept making more orders. In 2 mins I had 30 orders. Anyway use this code and 1 order will be created. Also, change $product = wc_get_product( '1001' ) to your product id.Reference is here on line 144: https://github.com/dipolukarov/wordpress/blob/master/wp-content/plugins/woocommerce/classes/class-wc-checkout.php

function my_init2() {
    $order    = wc_create_order();
    $order_id = $order->get_id();
    
    $product = wc_get_product( '10001' );
    
    $address = array(
        'first_name' => 'John2',
        'last_name'  => 'Smith1',
        'email'      => 'johnsmith1@gmail.com',
    );
    //$order->date_created(2020-07-21 );
    $order->add_product( $product, 1 );
    $order->set_address( $address, 'billing' );
    $order->set_created_via( 'programatically' );
    $order->calculate_totals();
    $order->set_total( $product->get_price() );
    $order->update_status( 'wc-completed' );
    
    error_log( '$order_id: ' . $order_id );
    
}          
//add_action('admin_init','my_init2');
function my_run_only_once() {
 
    if ( get_option( 'my_run_only_once_20' ) != 'completed' ) {
  my_init2();
        update_option( 'my_run_only_once_20', 'completed' );
    }
}
add_action( 'admin_init', 'my_run_only_once' );