PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.18
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.18
1.10.19 1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 1.9.17 1.9.15 1.9.16 1.9.14 All 163 releases
← All changes | includes/Sync/Order_Write_Payload.php +77 -1 1.10.131.10.18 View file →
@@ -7,8 +7,11 @@
7 7
8 8 namespace WCPOS\WooCommercePOS\Sync;
9 9
10 10 use WC_Order_Item_Product;
11 +use WCPOS\WooCommercePOS\Services\Tax_Id_Reader;
12 +use WCPOS\WooCommercePOS\Services\Tax_Id_Writer;
13 +use WP_Error;
11 14
12 15 /**
13 16 * Shapes a POS order document into the body forwarded to the STOCK wc/v3 orders
14 17 * controller.
@@ -17,9 +20,27 @@
17 20 * replay, CAS, checkpoint) and the order-specific payload shaping that makes a
18 21 * POS order document survive wc/v3's strict schema and its remove-and-reapply
19 22 * line semantics. This class is the second half, extracted verbatim from
20 23 * API\V2\Write_Controller so the protocol half stays legible; the shaping rules
21 - * themselves are unchanged.
24 + * themselves are unchanged. Client-date validation and tax-ID persistence are
25 + * shared with the v1 lane; the remaining shaping rules are not.
26 + *
27 + * Lane differences still to be reconciled (V1 = API\V1\Orders_Controller; V2 = API\V2\Writers\Order_Writer):
28 + * - Coupons: V1::calculate_coupons vs reconcile_order_coupon_lines: v1 skips empty codes; v2 forwards malformed lines for rejection; v2 reconciles updates only.
29 + * - Product identity: V1::get_product_id vs normalize_line_item_product_identity: v1 uses loose zero comparison; v2 requires numeric zero and supplies a misc SKU sentinel.
30 + * - Misc SKU: V1::maybe_set_item_meta_data vs normalize_line_item_product_identity: v1 uses isset and the stored product ID; v2 requires posted zero, a string SKU, and trims it.
31 + * - Any attributes: V1::maybe_set_item_meta_data vs recover_any_variation_attributes: v1 uses stored identity and updates by meta ID (default ''); v2 uses posted IDs (product default 0) and appends only missing keys.
32 + * - Variation dedupe: V1::prepare_line_items vs drop_unchanged_variation_line_identity: v1 prunes duplicate rows after preparation; v2 drops unchanged binding IDs before forwarding.
33 + * - Tombstones/omissions: V1 uses WC item deletion; v2 preserves explicit product_id null before identity dedupe and adds deletion markers for omitted items; v1 has no omission pass.
34 + * - Item UUIDs: V1 uses WC posted item IDs; reconcile_order_item_ids restores missing IDs from unique UUID matches on v2.
35 + * - Billing email: V1::wcpos_validate_billing_email/get_item_schema allow empty values; without_empty_billing_email drops ''/null on v2, whose writer explicitly clears '' on update.
36 + * - Display fields: V1::get_item_schema relaxes parent_name; sanitize_order_wc_payload drops null parent_name, image, and display meta fields on v2.
37 + * - Client date: V1 create filter reads raw JSON; V2::prepare_create reads the document; both now use validate_client_created_gmt (absent/null/empty means no override).
38 + * - Tax IDs: v1 coerces, v2 rejects incomplete entries; V1 refreshes its response after persist_tax_ids; V2::persist uses the same snapshot (absent snapshots on create only; [] clears).
39 + * - Audit: V1::wcpos_before_order_object_save/create_item/update_item vs V2 audit phases: v2 also handles reassignment, offline payment assertions, and unpaid provenance updates.
40 + * - Reserved stock: V1::save_object uses request params (absent values null); V2::forward_with_reserved_stock uses payload defaults (status/transaction '', paid false); both use around_paid_create.
41 + * - Write intent: both lanes declare through Services\Order_Write_Intent (v1 at create_item, v2 at Order_Writer::forward); v1 update and direct wc/v3 rely on the ad-hoc intent from the request.
42 + * - HPOS caps: V1 permission overrides retry broad edit/delete order caps; V2 Write_Controller::wcpos_check_permissions remaps read/create and ownership-sensitive edit/delete caps; no payload rule.
22 43 */
23 44 final class Order_Write_Payload {
24 45 /**
25 46 * Shape a CREATE payload for the wc/v3 forward.
@@ -56,8 +77,63 @@
56 77 $payload = $this->sanitize_order_wc_payload( $payload );
57 78 // Runs last: it reads the FORWARDED line shape, after normalize_line_item_product_identity
58 79 // has already resolved the posted sku (which outranks the ids in wc/v3's get_product_id).
59 80 return $this->drop_unchanged_variation_line_identity( $order, $payload );
81 + }
82 +
83 + /**
84 + * Validate the client creation time; bare GMT values are UTC, not store time.
85 + *
86 + * @param array $payload Original order document (raw JSON on v1).
87 + * @return int|null|WP_Error UTC timestamp, null when absent/empty, or a 400 error.
88 + */
89 + public function validate_client_created_gmt( array $payload ) {
90 + if ( ! isset( $payload['date_created_gmt'] ) ) {
91 + return null;
92 + }
93 + if ( ! is_scalar( $payload['date_created_gmt'] ) ) {
94 + return $this->invalid_created_gmt();
95 + }
96 + $value = wc_clean( wp_unslash( (string) $payload['date_created_gmt'] ) );
97 + if ( '' === $value ) {
98 + return null;
99 + }
100 + $timestamp = 1 === preg_match( '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?$/i', $value )
101 + ? rest_parse_date( 'Z' === strtoupper( substr( $value, -1 ) ) ? $value : $value . 'Z', true ) : false;
102 + if ( false === $timestamp ) {
103 + return $this->invalid_created_gmt();
104 + }
105 + return $timestamp > time() + DAY_IN_SECONDS
106 + ? 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 ) )
107 + : $timestamp;
108 + }
109 +
110 + /** Build the stable invalid create timestamp error. */
111 + private function invalid_created_gmt(): WP_Error {
112 + 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 ) );
113 + }
114 +
115 + /**
116 + * Persist explicit tax IDs, or snapshot the customer only on create.
117 + *
118 + * The v1 controller uses the read-back to refresh its already-built response.
119 + *
120 + * @param int $id Saved order ID.
121 + * @param array $payload Original order document; an empty tax_ids array clears IDs.
122 + * @param bool $is_create Whether to snapshot when tax_ids is absent.
123 + * @return array|null Stored tax IDs, or null when the order does not exist.
124 + */
125 + public function persist_tax_ids( int $id, array $payload, bool $is_create ): ?array {
126 + $order = wc_get_order( $id );
127 + if ( ! $order ) {
128 + return null;
129 + }
130 + if ( is_array( $payload['tax_ids'] ?? null ) ) {
131 + ( new Tax_Id_Writer() )->write_for_order( $order, $payload['tax_ids'] );
132 + } elseif ( $is_create && $order->get_customer_id() > 0 ) {
133 + ( new Tax_Id_Writer() )->snapshot_from_user_to_order( $order, $order->get_customer_id() );
134 + }
135 + return ( new Tax_Id_Reader() )->read_for_order( $order );
60 136 }
61 137
62 138 /**
63 139 * WC-strict-schema tolerance for POS order payloads.