Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Creating and changing orders, HPOS

Orders are created not only by a customer on the site: they are taken by phone, imported from a marketplace, extended with a tracking number. The order object has setter methods for that. The rule is the same as for products: set_... and save(), no direct database queries.

Creating an order

// wp-content/plugins/acme-shop/scripts/create-order.php
$order = wc_create_order(['customer_id' => 0]);
 
$order->add_product(wc_get_product(12), 2);
 
$order->set_address([
    'first_name' => 'Anna',
    'last_name' => 'Schmidt',
    'email' => 'anna@example.com',
    'address_1' => 'Hauptstrasse 5',
    'city' => 'Berlin',
    'postcode' => '10115',
    'country' => 'DE',
], 'billing');
 
$order->set_payment_method('cod');
$order->calculate_totals();
$order->update_status('processing', 'Order taken by phone.');
 
echo 'Created order ' . $order->get_id() . PHP_EOL;

wc_create_order() creates an empty order, add_product() adds an item, calculate_totals() computes totals and taxes. Run the file with wp eval-file: an order for two mugs appears in WooCommerce → Orders with the Processing status.

Changing

// wp-content/plugins/acme-shop/scripts/update-order.php
$order = wc_get_order(1024);
 
$order->set_shipping_city('Hamburg');
$order->set_customer_note('Call an hour before delivery.');
$order->save();

As with a product, changes reach the database only after save(). If items or shipping were changed, call calculate_totals() before saving.

Order meta

Your own data — a tracking number, an ID in an external system — is kept in meta data:

// wp-content/plugins/acme-shop/includes/orders.php
add_action('woocommerce_order_status_completed', function (int $orderId, WC_Order $order) {
    if ($order->get_meta('_acme_tracking') === '') {
        $order->update_meta_data('_acme_tracking', 'AB' . $orderId);
        $order->save();
    }
}, 10, 2);

When an order moves to Completed, a tracking number is written into it. The check before writing protects against the hook firing twice.

Notes

$order->add_order_note('Tracking number: AB1024', true);

The second argument, true, makes the note visible to the customer: they get an email and see it in My account → Orders. Without it the note is internal and visible only in the admin panel.

Finding by meta data

$orders = wc_get_orders([
    'limit' => 1,
    'meta_query' => [
        ['key' => '_acme_tracking', 'value' => 'AB1024'],
    ],
]);
 
$order = $orders[0] ?? null;

This is how an order is found by a number from an external system — for example, when the delivery service reports a handover.

HPOS rules

HPOS keeps orders in its own tables, not in wp_posts. Hence the rules:

Instead ofUse
get_post($orderId)wc_get_order($orderId)
get_post_meta($orderId, $key, true)$order->get_meta($key)
update_post_meta($orderId, $key, $value)$order->update_meta_data($key, $value) and save()
new WP_Query(['post_type' => 'shop_order'])wc_get_orders([...])
get_post_type($id) === 'shop_order'$order instanceof WC_Order

Code that follows these rules works with any storage. That is exactly what the custom_order_tables compatibility declaration from the plugin lesson promises.

A refund

wc_create_refund([
    'order_id' => 1024,
    'amount' => 9.90,
    'reason' => 'The product was damaged in delivery.',
]);

The function creates a refund for the given amount. When the whole order amount is refunded, the order moves to the Refunded status.

Common mistake

save() is forgotten after update_meta_data(). The value is in the object until the end of the request, so everything looks right while debugging, but it never reaches the database. The exception is hooks like woocommerce_checkout_create_order, which are called right before saving: there WooCommerce runs save() itself.

Thanks for reading!

Donate to the site — we work hard for you.