First of all we have to decide what is actually a product synchronisation? Let’s consider two scenarios here.
- Update a product with the same SKU on other WooCommerce stores automatically when it was updated by an administrator on the “Main store”. Here you can decide whether you would like to update a specific product data or all product data. And it is what we are going to do in this tutorial with code or with a plugin.
- Update product stock status and quantity on other WooCommerce stores not only when this product was updated by an administrator but also when the product was purchased (and its quantity was descreased, obviously). We are going to talk a little bit about it in this tutorial as well, but I recommend you to take a look at my another plugin which is intended to help you with that.
Without a plugin – using save_post action hook
Prepare WooCommerce stores authentication data
I assume that you would like to sync your products with multiple stores, so for each of the them we have to prepare a set of data:
- Store URL,
- Login and Application Password.
I am going to use an array for that.
$stores = array(
// store 1
array(
'url' => '', // store URL goes here with https://
'login' => '',
'pwd' => '',
),
// store 2
array(
'url' => '',
'login' => '',
'pwd' => '',
),
// store 3 etc
);
It is fully up to you how you would like to process and store this data, but will definitely use $stores array in the next chapter of this tutorial.
Sync a product with other WooCommerce stores when it was updated
In this chapter we are going to create multiple WooCommerce REST API requests in order to sync (publish or update) a product when it was published or updated in admin.
<?php
class wpdev_Sync_Woo_Products {
public function __construct() {
add_action( 'save_post_product', array( $this, 'sync' ), 99, 2 );
}
public function sync( $product_id, $post ) {
// do nothing if WooCommerce is not installed
if( ! function_exists( 'wc_get_product' ) ) {
return;
}
// get product object
$product = wc_get_product( $product_id );
// do the sync for published products only
if( 'publish' !== $product->get_status() ) {
return;
}
// that's an array of stores auth information we previously discussed
$stores = array( ... );
// prepare product data before the loop
$product_data = $product->get_data();
// Fix: "Error 400. Bad Request. Cannot create existing product."
unset( $product_data[ 'id' ] );
// Fix: "Error 400 Bad Request low_stock_amount is not of type integer,null." error
$product_data[ 'low_stock_amount' ] = (int) $product_data[ 'low_stock_amount' ];
// In order to sync a product image we have to do some additional stuff
if( $product_data[ 'image_id' ] ) {
$product_data[ 'images' ] = array(
array(
'src' => wp_get_attachment_url( $product_data[ 'image_id' ] )
)
);
unset( $product_data[ 'image_id' ] );
}
// let's loop through multiple stores and sync the product with each of them
foreach( $stores as $store ) {
$endpoint = "{$store[ 'url' ]}/wp-json/wc/v3/products";
$method = 'POST';
// let's check if the product with the same SKU already exists
if( $product_id_2 = $this->product_exists( $product, $store ) ) {
$endpoint = "{$endpoint}/{$product_id_2}";
$method = 'PUT';
}
// do the sync
wp_remote_request(
$endpoint,
array(
'method' => $method,
'headers' => array(
'Authorization' => 'Basic ' . base64_encode( "{$store[ 'login' ]}:{$store[ 'pwd' ]}" )
),
'body' => $product_data
)
);
}
}
private function product_exists( $product, $store ) {
$sku = $product->get_sku();
$request = wp_remote_get(
add_query_arg( 'sku', $sku, "{$store[ 'url' ]}/wp-json/wc/v3/products" ),
array(
'headers' => array(
'Authorization' => 'Basic ' . base64_encode( "{$store[ 'login' ]}:{$store[ 'pwd' ]}" )
)
)
);
if( 'OK' === wp_remote_retrieve_response_message( $request ) ) {
$products = json_decode( wp_remote_retrieve_body( $request ) );
if( $products ) {
$product = reset( $products );
return $product->id;
}
}
return false;
}
}
new wpdev_Sync_Woo_Products;
Please keep in mind the following:
- We have a choice to use either save_post or save_post_{post type} hook, I recommend the second one because it allows us to skip one more conditional statement.
- $stores = array( … ) – is what we did in the previous step. You have to provide application passwords here.
- $product->get_data() is an amazing method of WC_Product class that allows to get all the product information and pass it almost without changes into a REST API request.
- More about sending API requests with product images you can find in this tutorial or take a look at the plugin solution.
- I am pretty sure you don’t want product duplicates to be created on other stores every time you hit “Update” button, so I created a method product_exists() that performs one more REST API request in order to check whether a product with the same SKU already exists or not. But it is also possible to do with product meta data (and faster).