diff --git a/plugins/woocommerce-blocks/src/StoreApi/Routes/Checkout.php b/plugins/woocommerce-blocks/src/StoreApi/Routes/Checkout.php index 8f0d08b6dbd..9d0998098a4 100644 --- a/plugins/woocommerce-blocks/src/StoreApi/Routes/Checkout.php +++ b/plugins/woocommerce-blocks/src/StoreApi/Routes/Checkout.php @@ -5,12 +5,8 @@ use Automattic\WooCommerce\Blocks\Domain\Services\CreateAccount; use Automattic\WooCommerce\Blocks\Package; use Automattic\WooCommerce\Blocks\Payments\PaymentContext; use Automattic\WooCommerce\Blocks\Payments\PaymentResult; -use Automattic\WooCommerce\Blocks\StoreApi\Schemas\AbstractSchema; -use Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartSchema; -use Automattic\WooCommerce\Blocks\StoreApi\Utilities\CartController; use Automattic\WooCommerce\Blocks\StoreApi\Utilities\DraftOrderTrait; use Automattic\WooCommerce\Blocks\StoreApi\Utilities\InvalidStockLevelsInCartException; -use Automattic\WooCommerce\Blocks\StoreApi\Utilities\OrderController; use Automattic\WooCommerce\Checkout\Helpers\ReserveStock; use Automattic\WooCommerce\Checkout\Helpers\ReserveStockException; /** @@ -87,12 +83,6 @@ class Checkout extends AbstractCartRoute { $this->schema->get_endpoint_args_for_item_schema( \WP_REST_Server::CREATABLE ) ), ], - [ - 'methods' => \WP_REST_Server::EDITABLE, - 'callback' => array( $this, 'get_response' ), - 'permission_callback' => '__return_true', - 'args' => $this->schema->get_endpoint_args_for_item_schema( \WP_REST_Server::EDITABLE ), - ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; @@ -141,30 +131,7 @@ class Checkout extends AbstractCartRoute { } /** - * Update the current order. - * - * @internal Customer data is updated first so OrderController::update_addresses_from_cart uses up to date data. - * - * @throws RouteException On error. - * @param \WP_REST_Request $request Request object. - * @return \WP_REST_Response - */ - protected function get_route_update_response( \WP_REST_Request $request ) { - $this->update_customer_from_request( $request ); - $this->create_or_update_draft_order(); - $this->update_order_from_request( $request ); - - return $this->prepare_item_for_response( - (object) [ - 'order' => $this->order, - 'payment_result' => new PaymentResult(), - ], - $request - ); - } - - /** - * Update and process an order. + * Process an order. * * 1. Obtain Draft Order * 2. Process Request @@ -448,7 +415,7 @@ class Checkout extends AbstractCartRoute { */ private function update_order_from_request( \WP_REST_Request $request ) { $this->order->set_customer_note( $request['customer_note'] ?? '' ); - $this->order->set_payment_method( $request['payment_method'] ?? '' ); + $this->order->set_payment_method( $this->get_request_payment_method_id( $request ) ); /** * Fires when the Checkout Block/Store API updates an order's from the API request data. @@ -551,17 +518,7 @@ class Checkout extends AbstractCartRoute { * @return string */ private function get_request_payment_method_id( \WP_REST_Request $request ) { - $payment_method_id = wc_clean( wp_unslash( $request['payment_method'] ?? '' ) ); - - if ( empty( $payment_method_id ) ) { - throw new RouteException( - 'woocommerce_rest_checkout_missing_payment_method', - __( 'No payment method provided.', 'woo-gutenberg-products-block' ), - 400 - ); - } - - return $payment_method_id; + return $this->get_request_payment_method( $request )->id; } /** @@ -572,18 +529,30 @@ class Checkout extends AbstractCartRoute { * @return \WC_Payment_Gateway */ private function get_request_payment_method( \WP_REST_Request $request ) { - $payment_method_id = $this->get_request_payment_method_id( $request ); - $available_gateways = WC()->payment_gateways->get_available_payment_gateways(); + $available_gateways = WC()->payment_gateways->get_available_payment_gateways(); + $request_payment_method = wc_clean( wp_unslash( $request['payment_method'] ?? '' ) ); - if ( ! isset( $available_gateways[ $payment_method_id ] ) ) { + if ( empty( $request_payment_method ) ) { throw new RouteException( - 'woocommerce_rest_checkout_payment_method_disabled', - __( 'This payment gateway is not available.', 'woo-gutenberg-products-block' ), + 'woocommerce_rest_checkout_missing_payment_method', + __( 'No payment method provided.', 'woo-gutenberg-products-block' ), 400 ); } - return $available_gateways[ $payment_method_id ]; + if ( ! isset( $available_gateways[ $request_payment_method ] ) ) { + throw new RouteException( + 'woocommerce_rest_checkout_payment_method_disabled', + sprintf( + // Translators: %s Payment method ID. + __( 'The %s payment gateway is not available.', 'woo-gutenberg-products-block' ), + esc_html( $request_payment_method ) + ), + 400 + ); + } + + return $available_gateways[ $request_payment_method ]; } /** diff --git a/plugins/woocommerce-blocks/src/StoreApi/Schemas/CheckoutSchema.php b/plugins/woocommerce-blocks/src/StoreApi/Schemas/CheckoutSchema.php index 989cf677842..5c3ce4a2ca4 100644 --- a/plugins/woocommerce-blocks/src/StoreApi/Schemas/CheckoutSchema.php +++ b/plugins/woocommerce-blocks/src/StoreApi/Schemas/CheckoutSchema.php @@ -115,6 +115,7 @@ class CheckoutSchema extends AbstractSchema { 'type' => 'string', 'context' => [ 'view', 'edit' ], 'enum' => wc()->payment_gateways->get_payment_gateway_ids(), + 'required' => true, ], 'create_account' => [ 'description' => __( 'Whether to create a new user account as part of order processing.', 'woo-gutenberg-products-block' ), diff --git a/plugins/woocommerce-blocks/src/StoreApi/docs/checkout.md b/plugins/woocommerce-blocks/src/StoreApi/docs/checkout.md index 865aea83ee4..527817e85cd 100644 --- a/plugins/woocommerce-blocks/src/StoreApi/docs/checkout.md +++ b/plugins/woocommerce-blocks/src/StoreApi/docs/checkout.md @@ -5,7 +5,6 @@ The checkout API facilitates the creation of orders (from the current cart) and All checkout endpoints require [Nonce Tokens](nonce-tokens.md). - [Get Checkout Data](#get-checkout-data) -- [Update Checkout Data](#update-checkout-data) - [Process Order and Payment](#process-order-and-payment) ## Get Checkout Data @@ -28,70 +27,48 @@ curl --header "X-WC-Store-API-Nonce: 12345" --request GET https://example-store. ```json { - "order_id": 146, - "status": "checkout-draft", - "order_key": "wc_order_VPffqyvgWVqWL", - "customer_note": "", - "customer_id": 1, - "billing_address": { - "first_name": "Peter", - "last_name": "Venkman", - "company": "", - "address_1": "550 Central Park West", - "address_2": "Corner Penthouse Spook Central", - "city": "New York", - "state": "NY", - "postcode": "10023", - "country": "US", - "email": "admin@example.com", - "phone": "555-2368" - }, - "shipping_address": { - "first_name": "Peter", - "last_name": "Venkman", - "company": "", - "address_1": "550 Central Park West", - "address_2": "Corner Penthouse Spook Central", - "city": "New York", - "state": "NY", - "postcode": "10023", - "country": "US" - }, - "payment_method": "", - "payment_result": { - "payment_status": "", - "payment_details": [], - "redirect_url": "" - } + "order_id": 146, + "status": "checkout-draft", + "order_key": "wc_order_VPffqyvgWVqWL", + "customer_note": "", + "customer_id": 1, + "billing_address": { + "first_name": "Peter", + "last_name": "Venkman", + "company": "", + "address_1": "550 Central Park West", + "address_2": "Corner Penthouse Spook Central", + "city": "New York", + "state": "NY", + "postcode": "10023", + "country": "US", + "email": "admin@example.com", + "phone": "555-2368" + }, + "shipping_address": { + "first_name": "Peter", + "last_name": "Venkman", + "company": "", + "address_1": "550 Central Park West", + "address_2": "Corner Penthouse Spook Central", + "city": "New York", + "state": "NY", + "postcode": "10023", + "country": "US" + }, + "payment_method": "", + "payment_result": { + "payment_status": "", + "payment_details": [], + "redirect_url": "" + } } ``` -## Update Checkout Data - -Allows the client to update checkout data, and returns an updated response. - -This endpoint will return an error unless a valid [Nonce Token](nonce-tokens.md) is provided. - -```http -PUT /wc/store/checkout -``` - -| Attribute | Type | Required | Description | -| :----------------- | :------ | :------: | :-------------------------------------------------------------- | -| `billing_address` | array | No | Array of updated billing address data for the customer. | -| `shipping_address` | integer | No | Array of updated shipping address data for the customer. | -| `customer_note` | string | No | Note added to the order by the customer during checkout. | -| `payment_method` | string | No | The ID of the payment method being used to process the payment. | - -```http -curl --header "X-WC-Store-API-Nonce: 12345" --request PUT https://example-store.com/wp-json/wc/store/checkout?payment_method=paypal -``` - -Returns either updated checkout data (See [Get Checkout Data](#get-checkout-data)), or an error response. - ## Process Order and Payment -Posts final checkout data, including data from payment methods, and attempts payment. +Accepts the final customer addresses and chosen payment method, and any additional payment data, then attempts payment and +returns the result. This endpoint will return an error unless a valid [Nonce Token](nonce-tokens.md) is provided. @@ -101,10 +78,10 @@ POST /wc/store/checkout | Attribute | Type | Required | Description | | :----------------- | :------ | :------: | :------------------------------------------------------------------ | -| `billing_address` | array | No | Array of updated billing address data for the customer. | -| `shipping_address` | integer | No | Array of updated shipping address data for the customer. | +| `billing_address` | array | Yes | Array of updated billing address data for the customer. | +| `shipping_address` | integer | Yes | Array of updated shipping address data for the customer. | | `customer_note` | string | No | Note added to the order by the customer during checkout. | -| `payment_method` | string | No | The ID of the payment method being used to process the payment. | +| `payment_method` | string | Yes | The ID of the payment method being used to process the payment. | | `payment_data` | array | No | Data to pass through to the payment method when processing payment. | ```http @@ -115,40 +92,40 @@ curl --header "X-WC-Store-API-Nonce: 12345" --request POST https://example-store ```json { - "order_id": 146, - "status": "on-hold", - "order_key": "wc_order_VPffqyvgWVqWL", - "customer_note": "", - "customer_id": 1, - "billing_address": { - "first_name": "Peter", - "last_name": "Venkman", - "company": "", - "address_1": "550 Central Park West", - "address_2": "Corner Penthouse Spook Central", - "city": "New York", - "state": "NY", - "postcode": "10023", - "country": "US", - "email": "admin@example.com", - "phone": "555-2368" - }, - "shipping_address": { - "first_name": "Peter", - "last_name": "Venkman", - "company": "", - "address_1": "550 Central Park West", - "address_2": "Corner Penthouse Spook Central", - "city": "New York", - "state": "NY", - "postcode": "10023", - "country": "US" - }, - "payment_method": "cheque", - "payment_result": { - "payment_status": "success", - "payment_details": [], - "redirect_url": "https:\/\/local.wordpress.test\/block-checkout\/order-received\/146\/?key=wc_order_VPffqyvgWVqWL" - } + "order_id": 146, + "status": "on-hold", + "order_key": "wc_order_VPffqyvgWVqWL", + "customer_note": "", + "customer_id": 1, + "billing_address": { + "first_name": "Peter", + "last_name": "Venkman", + "company": "", + "address_1": "550 Central Park West", + "address_2": "Corner Penthouse Spook Central", + "city": "New York", + "state": "NY", + "postcode": "10023", + "country": "US", + "email": "admin@example.com", + "phone": "555-2368" + }, + "shipping_address": { + "first_name": "Peter", + "last_name": "Venkman", + "company": "", + "address_1": "550 Central Park West", + "address_2": "Corner Penthouse Spook Central", + "city": "New York", + "state": "NY", + "postcode": "10023", + "country": "US" + }, + "payment_method": "cheque", + "payment_result": { + "payment_status": "success", + "payment_details": [], + "redirect_url": "https://local.wordpress.test/block-checkout/order-received/146/?key=wc_order_VPffqyvgWVqWL" + } } ``` diff --git a/plugins/woocommerce-blocks/tests/php/Helpers/FixtureData.php b/plugins/woocommerce-blocks/tests/php/Helpers/FixtureData.php index 08f433ad74c..9976e04fc2e 100644 --- a/plugins/woocommerce-blocks/tests/php/Helpers/FixtureData.php +++ b/plugins/woocommerce-blocks/tests/php/Helpers/FixtureData.php @@ -20,9 +20,9 @@ class FixtureData { $product->set_props( wp_parse_args( $props, - [ + array( 'name' => 'Simple Product', - ] + ) ) ); $product->save(); @@ -37,20 +37,20 @@ class FixtureData { * @param array $attributes Product attributes from which to create variations. * @return \WC_Product */ - public function get_variable_product( $props, $attributes = [] ) { + public function get_variable_product( $props, $attributes = array() ) { $product = new \WC_Product_Variable(); $product->set_props( wp_parse_args( $props, - [ + array( 'name' => 'Variable Product', - ] + ) ) ); $product->save(); if ( $attributes ) { - $product_attributes = []; + $product_attributes = array(); foreach ( $attributes as $attribute ) { $product_attribute = new \WC_Product_Attribute(); @@ -74,23 +74,23 @@ class FixtureData { * Create and return a variation of a product. * * @param integer $parent_id Parent product ID. - * @param array $attributes Variation attributes. - * @param array $props Product props. + * @param array $attributes Variation attributes. + * @param array $props Product props. * @return \WC_Product_Variation */ - public function get_variation_product( $parent_id, $attributes = [], $props = [] ) { + public function get_variation_product( $parent_id, $attributes = array(), $props = array() ) { $variation = new \WC_Product_Variation(); $variation->set_props( array_merge( wp_parse_args( $props, - [ - 'name' => 'Variation of ' . $parent_id, + array( + 'name' => 'Variation of ' . $parent_id, 'regular_price' => '10', - ] + ) ), array( - 'parent_id' => $parent_id, + 'parent_id' => $parent_id, ) ) ); @@ -103,7 +103,7 @@ class FixtureData { * Create a product attribute. * * @param string $raw_name Name of attribute to create. - * @param array $terms Terms to create for the attribute. + * @param array $terms Terms to create for the attribute. * @return array Attribute data and created terms. */ public static function get_product_attribute( $raw_name = 'size', $terms = array( 'small' ) ) { @@ -180,10 +180,10 @@ class FixtureData { $result = wp_insert_term( $term, $attribute->slug, - [ - 'slug' => $term . '-slug', + array( + 'slug' => $term . '-slug', 'description' => 'Description of ' . $term, - ] + ) ); $return['term_ids'][] = $result['term_id']; } else { @@ -225,30 +225,30 @@ class FixtureData { * * @param integer $product_id Product ID. * @param integer $rating Review rating. - * @param string $content Review content. - * @param array $props Review props. + * @param string $content Review content. + * @param array $props Review props. * @return void */ - public function add_product_review( $product_id, $rating = 5, $content = 'Product review.', $props = [] ) { + public function add_product_review( $product_id, $rating = 5, $content = 'Product review.', $props = array() ) { wp_insert_comment( array_merge( wp_parse_args( $props, - [ + array( 'comment_author' => 'admin', 'comment_author_email' => 'woo@woo.local', 'comment_author_url' => '', 'comment_approved' => 1, 'comment_type' => 'review', - ] + ) ), - [ - 'comment_post_ID' => $product_id, - 'comment_content' => $content, - 'comment_meta' => [ + array( + 'comment_post_ID' => $product_id, + 'comment_content' => $content, + 'comment_meta' => array( 'rating' => $rating, - ], - ] + ), + ) ) ); \WC_Comments::clear_transients( $product_id ); @@ -273,4 +273,25 @@ class FixtureData { \WC_Cache_Helper::get_transient_version( 'shipping', true ); WC()->shipping()->load_shipping_methods(); } + + /** + * Enable bacs payment method. + */ + public function payments_enable_bacs() { + $bacs_settings = array( + 'enabled' => 'yes', + 'title' => 'Direct bank transfer', + 'description' => 'Make your payment directly into our bank account. Please use your Order ID as the payment reference. Your order will not be shipped until the funds have cleared in our account.', + 'instructions' => '', + 'account_details' => '', + 'account_name' => '', + 'account_number' => '', + 'sort_code' => '', + 'bank_name' => '', + 'iban' => '', + 'bic' => '', + ); + update_option( 'woocommerce_bacs_settings', $bacs_settings ); + WC()->payment_gateways()->init(); + } } diff --git a/plugins/woocommerce-blocks/tests/php/StoreApi/Routes/Checkout.php b/plugins/woocommerce-blocks/tests/php/StoreApi/Routes/Checkout.php index d57de75e08d..6a114911ae7 100644 --- a/plugins/woocommerce-blocks/tests/php/StoreApi/Routes/Checkout.php +++ b/plugins/woocommerce-blocks/tests/php/StoreApi/Routes/Checkout.php @@ -13,7 +13,6 @@ use Automattic\WooCommerce\Blocks\StoreApi\Formatters\HtmlFormatter; use Automattic\WooCommerce\Blocks\StoreApi\Formatters\CurrencyFormatter; use Automattic\WooCommerce\Blocks\Domain\Services\FeatureGating; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\CheckoutSchema; -use Automattic\WooCommerce\Blocks\Tests\StoreApi\Routes\ControllerTestCase; use Automattic\WooCommerce\Blocks\Tests\Helpers\FixtureData; use Automattic\WooCommerce\Blocks\StoreApi\Routes\Checkout as CheckoutRoute; use Automattic\WooCommerce\Blocks\StoreApi\Utilities\CartController; @@ -62,6 +61,7 @@ class Checkout extends MockeryTestCase { $fixtures = new FixtureData(); $fixtures->shipping_add_flat_rate(); + $fixtures->payments_enable_bacs(); $this->products = array( $fixtures->get_simple_product( array(