| @@ -9,12 +9,12 @@ | ||
| 9 | 9 | |
| 10 | 10 | namespace WCPOS\WooCommercePOS\API\V2\Writers; |
| 11 | 11 | |
| 12 | 12 | use WCPOS\WooCommercePOS\Services\Order_Notes; |
| 13 | +use WCPOS\WooCommercePOS\Services\Order_Write_Intent; | |
| 13 | 14 | use WCPOS\WooCommercePOS\Services\Pos_Order_Audit; |
| 14 | 15 | use WCPOS\WooCommercePOS\Services\Settings as SettingsService; |
| 15 | 16 | use WCPOS\WooCommercePOS\Services\Stock_Validator; |
| 16 | -use WCPOS\WooCommercePOS\Services\Tax_Id_Writer; | |
| 17 | 17 | use WCPOS\WooCommercePOS\Sync\Meta_Entry; |
| 18 | 18 | use WCPOS\WooCommercePOS\Sync\Order_Serializer; |
| 19 | 19 | use WCPOS\WooCommercePOS\Sync\Order_Write_Payload; |
| 20 | 20 | use WCPOS\WooCommercePOS\Sync\Pos_Uuid; |
| @@ -45,9 +45,9 @@ | ||
| 45 | 45 | } |
| 46 | 46 | |
| 47 | 47 | /** Prepare an order create and its create-only hook policy. */ |
| 48 | 48 | public function prepare_create( array $meta, array $payload, callable $validate_tax_ids ) { |
| 49 | - $created_gmt = $this->validate_client_created_gmt( $payload ); | |
| 49 | + $created_gmt = $this->order_payload->validate_client_created_gmt( $payload ); | |
| 50 | 50 | if ( is_wp_error( $created_gmt ) ) { |
| 51 | 51 | return $created_gmt; |
| 52 | 52 | } |
| 53 | 53 | $error = $validate_tax_ids( $payload ); |
| @@ -119,9 +119,21 @@ | ||
| 119 | 119 | } |
| 120 | 120 | |
| 121 | 121 | /** Forward within the named order hook lifecycle. */ |
| 122 | 122 | public function forward( array $prepared, callable $forward ) { |
| 123 | - return $this->forward_with_reserved_stock( $prepared, $forward ); | |
| 123 | + $declared = $prepared['context']; | |
| 124 | + if ( ! in_array( $declared['operation'] ?? '', array( 'create', 'update' ), true ) ) { | |
| 125 | + return $this->forward_with_reserved_stock( $prepared, $forward ); | |
| 126 | + } | |
| 127 | + $payload = $prepared['payload']; | |
| 128 | + $declared['requested_status'] = isset( $payload['status'] ) ? (string) $payload['status'] : ''; | |
| 129 | + $declared['set_paid'] = isset( $payload['set_paid'] ) && rest_sanitize_boolean( $payload['set_paid'] ); | |
| 130 | + return Order_Write_Intent::open( | |
| 131 | + $declared, | |
| 132 | + function () use ( $prepared, $forward ) { | |
| 133 | + return $this->forward_with_reserved_stock( $prepared, $forward ); | |
| 134 | + } | |
| 135 | + ); | |
| 124 | 136 | } |
| 125 | 137 | |
| 126 | 138 | /** |
| 127 | 139 | * Wrap the create forward in the shared create-pending -> reserve -> complete |
| @@ -179,9 +191,9 @@ | ||
| 179 | 191 | |
| 180 | 192 | /** Persist the order behavior assigned to a controller-owned protocol phase. */ |
| 181 | 193 | public function persist( string $phase, int $id, array $payload, array $current = array(), array $response_data = array(), array $context = array() ): void { |
| 182 | 194 | if ( 'create_before_identity' === $phase ) { |
| 183 | - $this->persist_tax_ids( $id, $payload, true ); | |
| 195 | + $this->order_payload->persist_tax_ids( $id, $payload, true ); | |
| 184 | 196 | } elseif ( 'create_after_identity' === $phase ) { |
| 185 | 197 | $this->stamp_order_audit( $id, $payload, true ); |
| 186 | 198 | $order = wc_get_order( $id ); |
| 187 | 199 | if ( $order ) { |
| @@ -188,12 +200,12 @@ | ||
| 188 | 200 | Order_Notes::add_creation_note( $order, get_current_user_id(), $order->get_meta( '_pos_store' ) ); |
| 189 | 201 | } |
| 190 | 202 | } elseif ( 'create_recovery' === $phase ) { |
| 191 | 203 | $this->stamp_order_audit( $id, $payload, false ); |
| 192 | - $this->persist_tax_ids( $id, $payload, true ); | |
| 204 | + $this->order_payload->persist_tax_ids( $id, $payload, true ); | |
| 193 | 205 | } elseif ( 'update' === $phase ) { |
| 194 | 206 | $this->stamp_order_till_meta( $id, $payload ); |
| 195 | - $this->persist_tax_ids( $id, $payload, false ); | |
| 207 | + $this->order_payload->persist_tax_ids( $id, $payload, false ); | |
| 196 | 208 | $this->persist_cashier_store_reassignment( $id, $current, $response_data, $context ); |
| 197 | 209 | if ( ! empty( $context['clear_email'] ) ) { |
| 198 | 210 | $order = wc_get_order( $id ); |
| 199 | 211 | if ( $order ) { |
| @@ -237,35 +249,15 @@ | ||
| 237 | 249 | } |
| 238 | 250 | |
| 239 | 251 | /** Apply create/update hook policies around one exact forwarded order. */ |
| 240 | 252 | private function forward_with_order_lifecycle( array $prepared, callable $forward ) { |
| 241 | - $context = $prepared['context']; | |
| 242 | - $forwarded_order = null; | |
| 243 | - $pre_insert = static function ( $order, $request, $creating ) use ( $context, &$forwarded_order ) { | |
| 244 | - $is_create = 'create' === $context['operation']; | |
| 245 | - if ( $is_create && $creating && $order instanceof \WC_Order && null === $forwarded_order ) { | |
| 246 | - $forwarded_order = $order; | |
| 247 | - } | |
| 248 | - $target = $is_create ? ( $creating && $order === $forwarded_order ) : ( $order instanceof \WC_Order && $context['id'] === $order->get_id() ); | |
| 249 | - if ( $target ) { | |
| 250 | - foreach ( $context['fill_meta'] as $key => $value ) { | |
| 251 | - $order->update_meta_data( $key, $value ); | |
| 252 | - } | |
| 253 | - } | |
| 254 | - if ( $is_create && $creating && null !== $context['created_gmt'] && $order instanceof \WC_Order ) { | |
| 255 | - $order->set_date_created( $context['created_gmt'] ); | |
| 256 | - } | |
| 257 | - return $order; | |
| 258 | - }; | |
| 259 | - $created_via = static function ( $order ) use ( &$forwarded_order ) { | |
| 260 | - if ( $order instanceof \WC_Order && $order === $forwarded_order && 'woocommerce-pos' !== $order->get_created_via() ) { | |
| 253 | + $context = $prepared['context']; | |
| 254 | + $created_via = static function ( $order ) { | |
| 255 | + $intent = Order_Write_Intent::current(); | |
| 256 | + if ( $order instanceof \WC_Order && null !== $intent && $intent->is_subject( $order ) && 'woocommerce-pos' !== $order->get_created_via() ) { | |
| 261 | 257 | $order->set_created_via( 'woocommerce-pos' ); |
| 262 | 258 | } |
| 263 | 259 | }; |
| 264 | - $use_filter = 'create' === $context['operation'] || array() !== $context['fill_meta']; | |
| 265 | - if ( $use_filter ) { | |
| 266 | - add_filter( 'woocommerce_rest_pre_insert_shop_order_object', $pre_insert, 10, 3 ); | |
| 267 | - } | |
| 268 | 260 | if ( 'create' === $context['operation'] ) { |
| 269 | 261 | add_action( 'woocommerce_before_order_object_save', $created_via ); |
| 270 | 262 | } |
| 271 | 263 | try { |
| @@ -270,11 +262,8 @@ | ||
| 270 | 262 | } |
| 271 | 263 | try { |
| 272 | 264 | return $forward( $prepared['method'], $prepared['route'], $prepared['payload'] ); |
| 273 | 265 | } finally { |
| 274 | - if ( $use_filter ) { | |
| 275 | - remove_filter( 'woocommerce_rest_pre_insert_shop_order_object', $pre_insert, 10 ); | |
| 276 | - } | |
| 277 | 266 | if ( 'create' === $context['operation'] ) { |
| 278 | 267 | remove_action( 'woocommerce_before_order_object_save', $created_via ); |
| 279 | 268 | } |
| 280 | 269 | } |
| @@ -403,21 +392,8 @@ | ||
| 403 | 392 | Order_Notes::add_pos_customer_change_note( $order, $current['customer_id'], $data['customer_id'] ); |
| 404 | 393 | } |
| 405 | 394 | } |
| 406 | 395 | |
| 407 | - /** Persist order tax IDs or the create-time customer snapshot. */ | |
| 408 | - private function persist_tax_ids( int $id, array $payload, bool $is_create ): void { | |
| 409 | - $order = wc_get_order( $id ); | |
| 410 | - if ( ! $order ) { | |
| 411 | - return; | |
| 412 | - } | |
| 413 | - if ( is_array( $payload['tax_ids'] ?? null ) ) { | |
| 414 | - ( new Tax_Id_Writer() )->write_for_order( $order, $payload['tax_ids'] ); | |
| 415 | - } elseif ( $is_create && $order->get_customer_id() > 0 ) { | |
| 416 | - ( new Tax_Id_Writer() )->snapshot_from_user_to_order( $order, $order->get_customer_id() ); | |
| 417 | - } | |
| 418 | - } | |
| 419 | - | |
| 420 | 396 | /** Persist server-owned order audit metadata. */ |
| 421 | 397 | private function stamp_order_audit( int $id, array $payload, bool $stamp_version ): void { |
| 422 | 398 | $meta = array( '_pos_user' => (string) get_current_user_id() ); |
| 423 | 399 | // Create-shaped phases only (create_after_identity, create_recovery, the |
| @@ -454,35 +430,8 @@ | ||
| 454 | 430 | private function without_pos_audit_meta( array $payload, int $id = 0 ): array { |
| 455 | 431 | $meta = is_array( $payload['meta_data'] ?? null ) ? $payload['meta_data'] : array(); |
| 456 | 432 | $protected = $id > 0 ? Pos_Order_Audit::audit_meta_ids( wc_get_order( $id ) ) : array(); |
| 457 | 433 | return Pos_Order_Audit::strip_audit_meta( $meta, $protected ); |
| 458 | - } | |
| 459 | - | |
| 460 | - /** Validate and normalize the optional client create timestamp. */ | |
| 461 | - private function validate_client_created_gmt( array $payload ) { | |
| 462 | - if ( ! isset( $payload['date_created_gmt'] ) ) { | |
| 463 | - return null; | |
| 464 | - } | |
| 465 | - if ( ! is_scalar( $payload['date_created_gmt'] ) ) { | |
| 466 | - return $this->invalid_created_gmt(); | |
| 467 | - } | |
| 468 | - $value = wc_clean( wp_unslash( (string) $payload['date_created_gmt'] ) ); | |
| 469 | - if ( '' === $value ) { | |
| 470 | - return null; | |
| 471 | - } | |
| 472 | - $timestamp = 1 === preg_match( '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?$/i', $value ) | |
| 473 | - ? rest_parse_date( 'Z' === strtoupper( substr( $value, -1 ) ) ? $value : $value . 'Z', true ) : false; | |
| 474 | - if ( false === $timestamp ) { | |
| 475 | - return $this->invalid_created_gmt(); | |
| 476 | - } | |
| 477 | - return $timestamp > time() + DAY_IN_SECONDS | |
| 478 | - ? new WP_Error( 'woocommerce_pos_rest_future_date_created_gmt', __( 'date_created_gmt cannot be more than 24 hours in the future.', 'woocommerce-pos' ), array( 'status' => 400 ) ) | |
| 479 | - : $timestamp; | |
| 480 | - } | |
| 481 | - | |
| 482 | - /** Build the stable invalid create timestamp error. */ | |
| 483 | - private function invalid_created_gmt(): WP_Error { | |
| 484 | - return new WP_Error( 'woocommerce_pos_rest_invalid_date_created_gmt', __( 'date_created_gmt must be a valid ISO 8601 UTC date.', 'woocommerce-pos' ), array( 'status' => 400 ) ); | |
| 485 | 434 | } |
| 486 | 435 | |
| 487 | 436 | /** Whether order stock was actually reduced before delete. */ |
| 488 | 437 | private function order_stock_reduced( int $id ): bool { |