Files
WooBC/woo-business-central/includes/class-wbc-customer-sync.php
Malin b64397dcd3 feat: WooCommerce Business Central integration plugin
Native PHP plugin (no Composer) that syncs:
- Product stock and pricing from BC to WooCommerce (scheduled cron)
- Orders from WooCommerce to BC (on payment received)
- Auto-creates customers in BC from WooCommerce billing data

Product matching: WooCommerce SKU → BC Item Number, fallback to GTIN (EAN).
OAuth2 client credentials auth with encrypted secret storage.
Admin settings page with connection test, manual sync, and log viewer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:59:53 +01:00

271 lines
8.3 KiB
PHP

<?php
/**
* Customer Sync to Business Central
*
* @package WooBusinessCentral
*/
// Prevent direct access
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class WBC_Customer_Sync
*
* Handles syncing customers from WooCommerce to Business Central.
*/
class WBC_Customer_Sync {
/**
* Get or create a customer in Business Central
*
* @param WC_Order $order WooCommerce order.
* @return string|WP_Error BC customer number or error.
*/
public function get_or_create_customer( $order ) {
$email = $order->get_billing_email();
if ( empty( $email ) ) {
return new WP_Error( 'wbc_no_email', __( 'Order has no billing email.', 'woo-business-central' ) );
}
WBC_Logger::debug( 'CustomerSync', 'Getting or creating customer', array( 'email' => $email ) );
// Check if we have a cached BC customer ID for this user
$user_id = $order->get_user_id();
if ( $user_id ) {
$cached_customer_number = get_user_meta( $user_id, '_wbc_bc_customer_number', true );
if ( ! empty( $cached_customer_number ) ) {
// Verify the customer still exists in BC
$exists = $this->verify_customer_exists( $cached_customer_number );
if ( $exists ) {
WBC_Logger::debug( 'CustomerSync', 'Using cached BC customer', array(
'customer_number' => $cached_customer_number,
) );
return $cached_customer_number;
}
}
}
// Try to find existing customer in BC by email
$existing = $this->find_customer_by_email( $email );
if ( is_wp_error( $existing ) ) {
return $existing;
}
if ( $existing ) {
$customer_number = $existing['number'];
// Cache the customer number for this user
if ( $user_id ) {
update_user_meta( $user_id, '_wbc_bc_customer_id', $existing['id'] );
update_user_meta( $user_id, '_wbc_bc_customer_number', $customer_number );
}
WBC_Logger::info( 'CustomerSync', 'Found existing BC customer', array(
'email' => $email,
'customer_number' => $customer_number,
) );
return $customer_number;
}
// Create new customer in BC
$new_customer = $this->create_customer( $order );
if ( is_wp_error( $new_customer ) ) {
return $new_customer;
}
$customer_number = $new_customer['number'];
// Cache the customer number for this user
if ( $user_id ) {
update_user_meta( $user_id, '_wbc_bc_customer_id', $new_customer['id'] );
update_user_meta( $user_id, '_wbc_bc_customer_number', $customer_number );
}
WBC_Logger::info( 'CustomerSync', 'Created new BC customer', array(
'email' => $email,
'customer_number' => $customer_number,
) );
return $customer_number;
}
/**
* Find customer in BC by email
*
* @param string $email Customer email.
* @return array|false|WP_Error Customer data, false if not found, or error.
*/
private function find_customer_by_email( $email ) {
$filter = "email eq '" . WBC_API_Client::escape_odata_string( $email ) . "'";
$result = WBC_API_Client::get_customers( $filter );
if ( is_wp_error( $result ) ) {
return $result;
}
$customers = isset( $result['value'] ) ? $result['value'] : array();
if ( empty( $customers ) ) {
return false;
}
return $customers[0];
}
/**
* Verify a customer exists in BC
*
* @param string $customer_number BC customer number.
* @return bool Whether the customer exists.
*/
private function verify_customer_exists( $customer_number ) {
$filter = "number eq '" . WBC_API_Client::escape_odata_string( $customer_number ) . "'";
$result = WBC_API_Client::get_customers( $filter );
if ( is_wp_error( $result ) ) {
return false;
}
$customers = isset( $result['value'] ) ? $result['value'] : array();
return ! empty( $customers );
}
/**
* Create a new customer in BC
*
* @param WC_Order $order WooCommerce order.
* @return array|WP_Error Created customer data or error.
*/
private function create_customer( $order ) {
// Build customer data from order
$customer_data = $this->build_customer_data( $order );
WBC_Logger::debug( 'CustomerSync', 'Creating new customer in BC', array(
'data' => $customer_data,
) );
$result = WBC_API_Client::create_customer( $customer_data );
if ( is_wp_error( $result ) ) {
WBC_Logger::error( 'CustomerSync', 'Failed to create customer in BC', array(
'error' => $result->get_error_message(),
'data' => $customer_data,
) );
return $result;
}
return $result;
}
/**
* Build customer data from WC order
*
* @param WC_Order $order WooCommerce order.
* @return array Customer data for BC API.
*/
private function build_customer_data( $order ) {
// Get billing info
$first_name = $order->get_billing_first_name();
$last_name = $order->get_billing_last_name();
$company = $order->get_billing_company();
// Determine display name and type
if ( ! empty( $company ) ) {
$display_name = $company;
$type = 'Company';
} else {
$display_name = trim( $first_name . ' ' . $last_name );
$type = 'Person';
}
// Build address
$address_line1 = $order->get_billing_address_1();
$address_line2 = $order->get_billing_address_2();
if ( ! empty( $address_line2 ) ) {
$address_line1 .= ', ' . $address_line2;
}
// Map country code
$country = $order->get_billing_country();
// Build customer payload
$customer_data = array(
'displayName' => substr( $display_name, 0, 100 ), // BC has max length
'type' => $type,
'email' => $order->get_billing_email(),
);
// Add optional fields if present
$phone = $order->get_billing_phone();
if ( ! empty( $phone ) ) {
$customer_data['phoneNumber'] = substr( $phone, 0, 30 );
}
if ( ! empty( $address_line1 ) ) {
$customer_data['addressLine1'] = substr( $address_line1, 0, 100 );
}
$city = $order->get_billing_city();
if ( ! empty( $city ) ) {
$customer_data['city'] = substr( $city, 0, 30 );
}
$state = $order->get_billing_state();
if ( ! empty( $state ) ) {
$customer_data['state'] = substr( $state, 0, 30 );
}
$postcode = $order->get_billing_postcode();
if ( ! empty( $postcode ) ) {
$customer_data['postalCode'] = substr( $postcode, 0, 20 );
}
if ( ! empty( $country ) ) {
$customer_data['country'] = $country;
}
// Add currency if available
$currency = $order->get_currency();
if ( ! empty( $currency ) ) {
$customer_data['currencyCode'] = $currency;
}
return $customer_data;
}
/**
* Update customer in BC with WC order data
*
* @param string $customer_id BC customer ID.
* @param WC_Order $order WooCommerce order.
* @return array|WP_Error Updated customer data or error.
*/
public function update_customer( $customer_id, $order ) {
$customer_data = $this->build_customer_data( $order );
WBC_Logger::debug( 'CustomerSync', 'Updating customer in BC', array(
'customer_id' => $customer_id,
'data' => $customer_data,
) );
$result = WBC_API_Client::patch( '/customers(' . $customer_id . ')', $customer_data );
if ( is_wp_error( $result ) ) {
WBC_Logger::error( 'CustomerSync', 'Failed to update customer in BC', array(
'error' => $result->get_error_message(),
'customer_id' => $customer_id,
) );
}
return $result;
}
}