Validate available payment methods before attempting payment or updating order statuses (https://github.com/woocommerce/woocommerce-blocks/pull/5440)

* Make payment method required

* removed unused imports

* Remove PUT method

* Validate available method when updating order

* Enable bacs for tests
This commit is contained in:
Mike Jolley 2022-01-04 18:04:08 +00:00 committed by GitHub
parent 65835d3689
commit 7f2af8c1a6
5 changed files with 147 additions and 179 deletions

View File

@ -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 ];
}
/**

View File

@ -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' ),

View File

@ -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"
}
}
```

View File

@ -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();
}
}

View File

@ -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(