diff --git a/spiff-connect/spiff-connect.php b/spiff-connect/spiff-connect.php index 8b718a6..887a844 100644 --- a/spiff-connect/spiff-connect.php +++ b/spiff-connect/spiff-connect.php @@ -610,10 +610,26 @@ function spiff_post_order($application_key, $items, $woo_order_id, $paid, $exter $response_body = wp_remote_retrieve_body($response); $decoded = json_decode($response_body); if ($response_status !== 200 || !empty($decoded->errors) || empty($decoded->data->orderCreate->id)) { - error_log('Failed to create Spiff order for WooCommerce order ' . $woo_order_id . ': ' . $response_body); + $error = is_wp_error($response) ? $response->get_error_message() : $response_body; + spiff_report_order_failure($woo_order_id, $response_status, $error, $external_data); } } +// Surface a failed order sync in the WooCommerce logs and on the order itself, so it isn't silently lost. +function spiff_report_order_failure($woo_order_id, $response_status, $error, $external_data) { + $message = 'Failed to create Spiff order for WooCommerce order ' . $woo_order_id . ' (HTTP ' . $response_status . '): ' . $error; + wc_get_logger()->error($message, array('source' => 'spiff-connect')); + + // The ID may have been customised via spiff_order_external_id, so only annotate the order if it is the one we sent. + $order = wc_get_order($woo_order_id); + if (!$order || (string) $order->get_order_number() !== $external_data['orderNumber']) { + return; + } + $order->add_order_note('Failed to send this order to Spiff. See the spiff-connect log under WooCommerce > Status > Logs.'); + $order->update_meta_data('_spiff_sync_failed', 'yes'); + $order->save(); +} + /** * Shortcodes. */ diff --git a/tests/CreateOrderTest.php b/tests/CreateOrderTest.php index ab69c9d..df2b847 100644 --- a/tests/CreateOrderTest.php +++ b/tests/CreateOrderTest.php @@ -149,18 +149,69 @@ public function testSendsOrderToUnitedStatesWhenConfigured() { public function testFailedRequestDoesNotStopCheckout() { spiff_test_queue_response(array('errors' => array(array('message' => 'Unauthorised'))), 401); $this->addOrder(123, 'VEG-1042', array($this->spiffItem('transaction-1'))); - $log = ini_get('error_log'); - ini_set('error_log', '/dev/null'); - try { - spiff_create_order(123); - } finally { - ini_set('error_log', $log); - } + spiff_create_order(123); $this->assertCount(1, $GLOBALS['spiff_test_requests']); } + public function testFailedRequestIsLoggedWithStatusAndResponse() { + spiff_test_queue_response(array('errors' => array(array('message' => 'Unauthorised'))), 401); + $this->addOrder(123, 'VEG-1042', array($this->spiffItem('transaction-1'))); + + spiff_create_order(123); + + $this->assertCount(1, $GLOBALS['spiff_test_logs']); + $log = $GLOBALS['spiff_test_logs'][0]; + $this->assertSame('spiff-connect', $log['context']['source']); + $this->assertStringContainsString('WooCommerce order 123 (HTTP 401)', $log['message']); + $this->assertStringContainsString('Unauthorised', $log['message']); + } + + public function testFailedRequestAnnotatesAndFlagsOrder() { + spiff_test_queue_response(array('errors' => array(array('message' => 'Unauthorised'))), 401); + $this->addOrder(123, 'VEG-1042', array($this->spiffItem('transaction-1'))); + + spiff_create_order(123); + + $order = $GLOBALS['spiff_test_orders'][123]; + $this->assertCount(1, $order->notes); + $this->assertSame('yes', $order->meta['_spiff_sync_failed']); + } + + public function testTransportErrorMessageIsLogged() { + $GLOBALS['spiff_test_responses'][] = new WP_Error('http_request_failed', 'cURL error 28: timeout'); + $this->addOrder(123, 'VEG-1042', array($this->spiffItem('transaction-1'))); + + spiff_create_order(123); + + $this->assertStringContainsString('cURL error 28: timeout', $GLOBALS['spiff_test_logs'][0]['message']); + } + + public function testFailureWithCustomExternalIdDoesNotAnnotateUnrelatedOrder() { + spiff_test_queue_response(array('errors' => array(array('message' => 'Unauthorised'))), 401); + $this->addOrder(123, 'VEG-1042', array($this->spiffItem('transaction-1'))); + $this->addOrder(5000, 'VEG-2000', array()); + add_filter('spiff_order_external_id', function () { + return 5000; + }, 10, 2); + + spiff_create_order(123); + + $this->assertCount(1, $GLOBALS['spiff_test_logs']); + $this->assertSame(array(), $GLOBALS['spiff_test_orders'][5000]->notes); + $this->assertSame(array(), $GLOBALS['spiff_test_orders'][123]->notes); + } + + public function testSuccessfulRequestIsNotLoggedOrFlagged() { + $this->addOrder(123, 'VEG-1042', array($this->spiffItem('transaction-1'))); + + spiff_create_order(123); + + $this->assertSame(array(), $GLOBALS['spiff_test_logs']); + $this->assertSame(array(), $GLOBALS['spiff_test_orders'][123]->meta); + } + public function testSendsPaidFlagAndApplicationKey() { $this->addOrder(123, 'VEG-1042', array($this->spiffItem('transaction-1')), false); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index b62d531..040b4fe 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -16,6 +16,7 @@ function spiff_test_reset() { $GLOBALS['spiff_test_requests'] = array(); $GLOBALS['spiff_test_responses'] = array(); $GLOBALS['spiff_test_orders'] = array(); + $GLOBALS['spiff_test_logs'] = array(); $GLOBALS['spiff_test_products'] = array(); $GLOBALS['spiff_test_users'] = array(); $GLOBALS['spiff_test_user_meta'] = array(); @@ -190,16 +191,42 @@ function wp_remote_post($url, $args) { ); } +function is_wp_error($thing) { + return $thing instanceof WP_Error; +} + +class WP_Error { + private $message; + + public function __construct($code = '', $message = '') { + $this->message = $message; + } + + public function get_error_message() { + return $this->message; + } +} + function wp_remote_retrieve_response_code($response) { - return $response['response']['code']; + return is_wp_error($response) ? '' : $response['response']['code']; } function wp_remote_retrieve_body($response) { - return $response['body']; + return is_wp_error($response) ? '' : $response['body']; } // WooCommerce. +class Spiff_Test_Logger { + public function error($message, $context = array()) { + $GLOBALS['spiff_test_logs'][] = array('message' => $message, 'context' => $context); + } +} + +function wc_get_logger() { + return new Spiff_Test_Logger(); +} + function wc_get_order($order_id) { return $GLOBALS['spiff_test_orders'][$order_id] ?? false; } @@ -321,6 +348,8 @@ class Spiff_Test_Order { private $items; private $paid; private $fields; + public $notes = array(); + public $meta = array(); public function __construct($id, $order_number, $items, $paid = true, $fields = array()) { $this->id = $id; @@ -350,6 +379,16 @@ public function get_coupon_codes() { return $this->fields['coupon_codes'] ?? array(); } + public function add_order_note($note) { + $this->notes[] = $note; + } + + public function update_meta_data($key, $value) { + $this->meta[$key] = $value; + } + + public function save() {} + // Remaining getters (addresses, email, phone, note) read from $fields, e.g. get_billing_city => 'billing_city'. public function __call($name, $arguments) { return $this->fields[substr($name, strlen('get_'))] ?? '';