Skip to content

Commit

Permalink
Prevent order create duplication
Browse files Browse the repository at this point in the history
  • Loading branch information
kilbot committed Jun 7, 2024
1 parent 314384a commit e7bea13
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 1 deletion.
35 changes: 34 additions & 1 deletion includes/API/Orders_Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,12 +193,46 @@ public function get_item_schema() {

/**
* Create a single order.
* - Validate billing email.
* - Do a sanity check on the UUID, if the internet connection is bad, several requests can be made with the same UUID.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_Error|WP_REST_Response
*/
public function create_item( $request ) {
// check if the UUID is already in use.
if ( isset( $request['meta_data'] ) && is_array( $request['meta_data'] ) ) {
foreach ( $request['meta_data'] as $meta ) {
if ( $meta['key'] === '_woocommerce_pos_uuid' ) {
$uuid = $meta['value'];
$ids = $this->get_order_ids_by_uuid( $uuid );

/**
* If the UUID is already in use, and there is only one order with that UUID, return the existing order.
* This can happen if the internet connection is bad and the request is made several times.
*
* @NOTE: This means that $request data is lost, but we can't update existing order because it has resource ids now.
* The alternative would be to update the existing order, but that would require a lot of extra work.
* Or return an error, which would be a bad user experience.
*/
if ( count( $ids ) === 1 ) {
Logger::log( 'UUID already in use, return existing order.', $ids[0] );

// Create a new WP_REST_Request object for the GET request.
$get_request = new WP_REST_Request( 'GET', '/wc/v3/orders/' . $ids[0] );
$get_request->set_param( 'id', $ids[0] );

return $this->get_item( $get_request );
}
if ( count( $ids ) > 1 ) {
Logger::log( 'UUID already in use for multiple orders. This should not happen.', $ids );
return new WP_Error( 'woocommerce_rest_order_invalid_id', __( 'UUID already in use.', 'woocommerce' ), array( 'status' => 400 ) );
}
}
}
}

$valid_email = $this->wcpos_validate_billing_email( $request );
if ( is_wp_error( $valid_email ) ) {
return $valid_email;
Expand Down Expand Up @@ -320,7 +354,6 @@ public function get_product_id( $posted, $action = 'create' ) {
* @return bool|WP_Error
*/
public function wcpos_validate_billing_email( WP_REST_Request $request ) {
// Your custom validation logic for the request data
$billing = $request['billing'] ?? null;
$email = \is_array( $billing ) ? ( $billing['email'] ?? null ) : null;

Expand Down
27 changes: 27 additions & 0 deletions includes/API/Traits/Uuid_Handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,33 @@ private function uuid_postmeta_exists( string $uuid, WC_Data $object ): bool {
return (bool) $result;
}

/**
*
*/
private function get_order_ids_by_uuid( string $uuid ) {
global $wpdb;

if ( class_exists( OrderUtil::class ) && OrderUtil::custom_orders_table_usage_is_enabled() ) {
// Check the orders meta table.
$result = $wpdb->get_col(
$wpdb->prepare(
"SELECT order_id FROM {$wpdb->prefix}wc_orders_meta WHERE meta_key = '_woocommerce_pos_uuid' AND meta_value = %s",
$uuid
)
);
} else {
// Check the postmeta table.
$result = $wpdb->get_col(
$wpdb->prepare(
"SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = '_woocommerce_pos_uuid' AND meta_value = %s",
$uuid
)
);
}

return $result;
}

/**
* Check if the given UUID already exists for any user.
*
Expand Down

0 comments on commit e7bea13

Please sign in to comment.