PluginProbe
WooCommerce / 11.0.1
WooCommerce v11.0.1
11.1.0 11.1.0-rc.2 11.1.0-rc.1 11.1.0-beta.2 11.1.0-beta.1 11.0.1 11.0.0 11.0.0-rc.3 11.0.0-rc.2 11.0.0-rc.1 11.0.0-beta.2 11.0.0-beta.1 10.9.4 10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 All 648 releases
woocommerce / src / StoreApi / Routes / V1 / CheckoutOrder.php
CheckoutOrder.php
288 lines 8.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Automattic\WooCommerce\StoreApi\Routes\V1;
3
4 use Automattic\WooCommerce\StoreApi\Payments\PaymentResult;
5 use Automattic\WooCommerce\StoreApi\Exceptions\InvalidStockLevelsInCartException;
6 use Automattic\WooCommerce\StoreApi\Exceptions\RouteException;
7 use Automattic\WooCommerce\StoreApi\Utilities\OrderAuthorizationTrait;
8 use Automattic\WooCommerce\StoreApi\Utilities\CheckoutTrait;
9
10 /**
11 * CheckoutOrder class.
12 */
13 class CheckoutOrder extends AbstractCartRoute {
14 use OrderAuthorizationTrait;
15 use CheckoutTrait;
16
17 /**
18 * The route identifier.
19 *
20 * @var string
21 */
22 const IDENTIFIER = 'checkout-order';
23
24 /**
25 * The routes schema.
26 *
27 * @var string
28 */
29 const SCHEMA_TYPE = 'checkout-order';
30
31 /**
32 * Holds the current order being processed.
33 *
34 * @var \WC_Order
35 */
36 private $order = null;
37
38 /**
39 * Get the path of this REST route.
40 *
41 * @return string
42 */
43 public function get_path() {
44 return self::get_path_regex();
45 }
46
47 /**
48 * Get the path of this rest route.
49 *
50 * @return string
51 */
52 public static function get_path_regex() {
53 return '/checkout/(?P<id>[\d]+)';
54 }
55
56 /**
57 * Get method arguments for this REST route.
58 *
59 * @return array An array of endpoints.
60 */
61 public function get_args() {
62 return [
63 [
64 'methods' => \WP_REST_Server::CREATABLE,
65 'callback' => [ $this, 'get_response' ],
66 'permission_callback' => [ $this, 'is_authorized' ],
67 'args' => array_merge(
68 [
69 'payment_data' => [
70 'description' => __( 'Data to pass through to the payment method when processing payment.', 'woocommerce' ),
71 'type' => 'array',
72 'items' => [
73 'type' => 'object',
74 'properties' => [
75 'key' => [
76 'type' => 'string',
77 ],
78 'value' => [
79 'type' => [ 'string', 'boolean' ],
80 ],
81 ],
82 ],
83 ],
84 ],
85 $this->schema->get_endpoint_args_for_item_schema( \WP_REST_Server::CREATABLE )
86 ),
87 ],
88 'schema' => [ $this->schema, 'get_public_item_schema' ],
89 'allow_batch' => [ 'v1' => true ],
90 ];
91 }
92
93 /**
94 * Process an order.
95 *
96 * 1. Process Request
97 * 2. Process Customer
98 * 3. Validate Order
99 * 4. Process Payment
100 *
101 * @throws RouteException On error.
102 * @throws InvalidStockLevelsInCartException On error.
103 *
104 * @param \WP_REST_Request $request Request object.
105 *
106 * @return \WP_REST_Response
107 */
108 protected function get_route_post_response( \WP_REST_Request $request ) {
109 $order_id = absint( $request['id'] );
110 $this->order = wc_get_order( $order_id );
111
112 if ( ! $this->order instanceof \WC_Order || ! $this->order->needs_payment() ) {
113 return new \WP_Error(
114 'invalid_order_update_status',
115 __( 'This order cannot be paid for.', 'woocommerce' )
116 );
117 }
118
119 /**
120 * Process request data.
121 *
122 * The order address is validated before anything is persisted, so a rejected request
123 * cannot mutate the existing order or the customer record.
124 */
125 $this->update_billing_address( $request );
126 $this->update_order_from_request( $request );
127
128 /**
129 * Process customer data.
130 *
131 * Update order with customer details, and sign up a user account as necessary.
132 */
133 $this->process_customer( $request );
134
135 /**
136 * Validate order.
137 *
138 * This logic ensures the order is valid before payment is attempted.
139 */
140 $this->order_controller->validate_existing_order_before_payment( $this->order );
141
142 /**
143 * Fires before an order is processed by the Checkout Block/Store API.
144 *
145 * This hook informs extensions that $order has completed processing and is ready for payment.
146 *
147 * This is similar to existing core hook woocommerce_checkout_order_processed. We're using a new action:
148 * - To keep the interface focused (only pass $order, not passing request data).
149 * - This also explicitly indicates these orders are from checkout block/StoreAPI.
150 *
151 * @since 7.2.0
152 *
153 * @see https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3238
154 * @example See docs/examples/checkout-order-processed.md
155
156 * @param \WC_Order $order Order object.
157 */
158 do_action( 'woocommerce_store_api_checkout_order_processed', $this->order );
159
160 /**
161 * Process the payment and return the results.
162 */
163 $payment_result = new PaymentResult();
164
165 if ( $this->order->needs_payment() ) {
166 $this->process_payment( $request, $payment_result );
167 } else {
168 $this->process_without_payment( $request, $payment_result );
169 }
170
171 return $this->prepare_item_for_response(
172 (object) [
173 'order' => wc_get_order( $this->order ),
174 'payment_result' => $payment_result,
175 ],
176 $request
177 );
178 }
179
180 /**
181 * Since this endpoint only operates on existing orders, we don't need to do updates based on
182 * the cart data.
183 *
184 * @param \WP_REST_Request $request Request object.
185 */
186 protected function cart_updated( \WP_REST_Request $request ) {}
187
188 /**
189 * Applies the billing and shipping address from the request to the order and customer.
190 *
191 * The address is set on the order and validated before anything is persisted, so a rejected
192 * request cannot mutate the order or the customer. wc()->customer is saved on shutdown (see
193 * WooCommerce::initialize_cart()), so its address fields are only set once validation passes.
194 *
195 * @throws RouteException When the order address fails validation.
196 *
197 * @param \WP_REST_Request $request Full details about the request.
198 */
199 private function update_billing_address( \WP_REST_Request $request ) {
200 $customer = wc()->customer;
201
202 // Billing address is a required field.
203 $billing = $request['billing_address'];
204
205 // If shipping address (optional field) was not provided, set it to the given billing address (required field).
206 $shipping = $request['shipping_address'] ?? $billing;
207
208 $this->order->set_billing_address( $billing );
209 $this->order->set_shipping_address( $shipping );
210 $this->order_controller->validate_existing_order_before_update( $this->order );
211
212 // Update customer object with validated order addresses.
213 foreach ( $billing as $key => $value ) {
214 if ( is_callable( [ $customer, "set_billing_$key" ] ) ) {
215 $customer->{"set_billing_$key"}( $value );
216 }
217 }
218
219 foreach ( $shipping as $key => $value ) {
220 if ( is_callable( [ $customer, "set_shipping_$key" ] ) ) {
221 $customer->{"set_shipping_$key"}( $value );
222 }
223 }
224
225 /**
226 * Fires when the Checkout Block/Store API updates a customer from the API request data.
227 *
228 * @since 8.2.0
229 *
230 * @param \WC_Customer $customer Customer object.
231 * @param \WP_REST_Request $request Full details about the request.
232 */
233 do_action( 'woocommerce_store_api_checkout_update_customer_from_request', $customer, $request );
234
235 $customer->save();
236 $this->order->save();
237 $this->order->calculate_totals();
238 }
239
240 /**
241 * Gets the chosen payment method from the request.
242 *
243 * @throws RouteException On error.
244 * @param \WP_REST_Request $request Request object.
245 * @return \WC_Payment_Gateway|null
246 */
247 private function get_request_payment_method( \WP_REST_Request $request ) {
248 $request_payment_method = wc_clean( wp_unslash( $request['payment_method'] ?? '' ) );
249
250 if ( empty( $request_payment_method ) ) {
251 if ( $this->order->needs_payment() ) {
252 throw new RouteException(
253 'woocommerce_rest_checkout_missing_payment_method',
254 __( 'No payment method provided.', 'woocommerce' ),
255 400
256 );
257 }
258 return null;
259 }
260
261 $available_gateways = WC()->payment_gateways->get_available_payment_gateways();
262
263 if ( ! isset( $available_gateways[ $request_payment_method ] ) ) {
264 throw new RouteException(
265 'woocommerce_rest_checkout_payment_method_disabled',
266 sprintf(
267 // Translators: %s Payment method ID.
268 __( 'The %s payment gateway is not available.', 'woocommerce' ),
269 esc_html( $request_payment_method )
270 ),
271 400
272 );
273 }
274
275 return $available_gateways[ $request_payment_method ];
276 }
277
278 /**
279 * Updates the order with user details (e.g. address).
280 *
281 * @throws RouteException API error object with error details.
282 * @param \WP_REST_Request $request Request object.
283 */
284 private function process_customer( \WP_REST_Request $request ) {
285 $this->order_controller->sync_customer_data_with_order( $this->order );
286 }
287 }
288