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/API/V1/Checkout_Controller.php +30 -1 1.10.21.10.18 View file →
@@ -14,8 +14,9 @@
14 14 use WC_REST_Controller;
15 15 use WCPOS\WooCommercePOS\Payments\Checkout_State_Repository;
16 16 use WCPOS\WooCommercePOS\Payments\Gateway_Contract;
17 17 use WCPOS\WooCommercePOS\Payments\Idempotency_Repository;
18 +use WCPOS\WooCommercePOS\Services\Stock_Validator;
18 19 use WP_Error;
19 20 use WP_REST_Request;
20 21 use WP_REST_Response;
21 22 use WP_REST_Server;
@@ -115,8 +116,9 @@
115 116 /**
116 117 * Create a checkout state mutation.
117 118 *
118 119 * @param WP_REST_Request $request Request object.
120 + * @throws \Throwable When gateway processing fails.
119 121 */
120 122 public function create_item( $request ) {
121 123 $order = $this->get_order( (int) $request['id'] );
122 124 if ( is_wp_error( $order ) ) {
@@ -183,15 +185,42 @@
183 185
184 186 try {
185 187 $action = isset( $params['action'] ) ? (string) $params['action'] : 'start';
186 188 $payment_data = isset( $params['payment_data'] ) && is_array( $params['payment_data'] ) ? $params['payment_data'] : array();
187 - $state = $this->dispatch_checkout_action( $gateway, $order->get_id(), $action, $payment_data, $order, $request );
189 + // Validate on EVERY action, not just `start`. The action string is
190 + // free-form and dispatched to a gateway filter, and the shipped surface
191 + // already carries `update` alongside `start`, so a gateway completing
192 + // payment on a later action would otherwise take money for stock that
193 + // was never checked. validate_checkout() short-circuits when the order
194 + // already holds a sufficient reservation, so this costs a lookup rather
195 + // than a second hold.
196 + $validation = Stock_Validator::instance()->validate_checkout( $order );
197 + if ( is_wp_error( $validation ) ) {
198 + Stock_Validator::instance()->release_checkout_stock( $order );
188 199
200 + return $validation;
201 + }
202 + try {
203 + $state = $this->dispatch_checkout_action( $gateway, $order->get_id(), $action, $payment_data, $order, $request );
204 + } catch ( \Throwable $exception ) {
205 + // Every action can now be holding stock, so every action gives it
206 + // back when dispatch fails; the normalized cancelled/failed branch
207 + // below is never reached on these paths.
208 + Stock_Validator::instance()->release_checkout_stock( $order );
209 +
210 + throw $exception;
211 + }
212 +
189 213 if ( is_wp_error( $state ) ) {
214 + Stock_Validator::instance()->release_checkout_stock( $order );
215 +
190 216 return $state;
191 217 }
192 218
193 219 $state = $this->normalize_state( $order->get_id(), $gateway_id, $state );
220 + if ( \in_array( $state['status'], array( 'cancelled', 'failed' ), true ) ) {
221 + Stock_Validator::instance()->release_checkout_stock( $order );
222 + }
194 223 $this->state_repository->upsert( $order->get_id(), $state );
195 224
196 225 if ( 'completed' === $state['status'] ) {
197 226 $order->update_meta_data( '_pos_checkout_gateway_id', $gateway_id );