* Correct docblock

* Implement coupons endpoint with add/delete/get functionality

* Add totals

* Use money formatting

* Added tests for new endpoints

* Add coupons to main cart endpoint and update docs to match

* Fix indenting in readme

* is_a to instanceof

* Update coupon_exists logic

* Documentation/tidy class

* Update currency responses for coupon totals w/ tests
This commit is contained in:
Mike Jolley 2019-12-19 12:41:34 +00:00 committed by GitHub
parent ca506d0a48
commit 15dbc627c1
8 changed files with 871 additions and 20 deletions

View File

@ -94,6 +94,7 @@ class RestApi {
'product-reviews' => __NAMESPACE__ . '\RestApi\Controllers\ProductReviews',
'store-cart' => __NAMESPACE__ . '\RestApi\StoreApi\Controllers\Cart',
'store-cart-items' => __NAMESPACE__ . '\RestApi\StoreApi\Controllers\CartItems',
'store-cart-coupons' => __NAMESPACE__ . '\RestApi\StoreApi\Controllers\CartCoupons',
'store-products' => __NAMESPACE__ . '\RestApi\StoreApi\Controllers\Products',
'store-product-collection-data' => __NAMESPACE__ . '\RestApi\StoreApi\Controllers\ProductCollectionData',
'store-product-attributes' => __NAMESPACE__ . '\RestApi\StoreApi\Controllers\ProductAttributes',

View File

@ -0,0 +1,252 @@
<?php
/**
* Cart Coupons controller.
*
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
* @package WooCommerce/Blocks
*/
namespace Automattic\WooCommerce\Blocks\RestApi\StoreApi\Controllers;
defined( 'ABSPATH' ) || exit;
use \WP_Error as RestError;
use \WP_REST_Server as RestServer;
use \WP_REST_Controller as RestController;
use \WP_REST_Response as RestResponse;
use \WP_REST_Request as RestRequest;
use \WC_REST_Exception as RestException;
use Automattic\WooCommerce\Blocks\RestApi\StoreApi\Schemas\CartCouponSchema;
use Automattic\WooCommerce\Blocks\RestApi\StoreApi\Utilities\CartController;
/**
* Cart Coupons API.
*/
class CartCoupons extends RestController {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/store';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'cart/coupons';
/**
* Schema class instance.
*
* @var object
*/
protected $schema;
/**
* Setup API class.
*/
public function __construct() {
$this->schema = new CartCouponSchema();
}
/**
* Register routes.
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
[
[
'methods' => RestServer::READABLE,
'callback' => [ $this, 'get_items' ],
'args' => [
'context' => $this->get_context_param( [ 'default' => 'view' ] ),
],
],
[
'methods' => RestServer::CREATABLE,
'callback' => array( $this, 'create_item' ),
'args' => $this->get_endpoint_args_for_item_schema( RestServer::CREATABLE ),
],
[
'methods' => RestServer::DELETABLE,
'callback' => [ $this, 'delete_items' ],
],
'schema' => [ $this, 'get_public_item_schema' ],
]
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<code>[\w-]+)',
[
'args' => [
'code' => [
'description' => __( 'Unique identifier for the coupon within the cart.', 'woo-gutenberg-products-block' ),
'type' => 'string',
],
],
[
'methods' => RestServer::READABLE,
'callback' => [ $this, 'get_item' ],
'args' => [
'context' => $this->get_context_param( [ 'default' => 'view' ] ),
],
],
[
'methods' => RestServer::DELETABLE,
'callback' => [ $this, 'delete_item' ],
],
'schema' => [ $this, 'get_public_item_schema' ],
]
);
}
/**
* Get a collection of cart coupons.
*
* @param RestRequest $request Full details about the request.
* @return RestError|RestResponse
*/
public function get_items( $request ) {
$controller = new CartController();
$cart_coupons = $controller->get_cart_coupons();
$items = [];
foreach ( $cart_coupons as $coupon_code ) {
$response = $this->prepare_item_for_response( $coupon_code, $request );
$response->add_links( $this->prepare_links( $coupon_code ) );
$response = $this->prepare_response_for_collection( $response );
$items[] = $response;
}
$response = rest_ensure_response( $items );
return $response;
}
/**
* Get a single cart coupon.
*
* @param RestRequest $request Full details about the request.
* @return RestError|RestResponse
*/
public function get_item( $request ) {
$controller = new CartController();
if ( ! $controller->has_coupon( $request['code'] ) ) {
return new RestError( 'woocommerce_rest_cart_coupon_invalid_code', __( 'Coupon does not exist in the cart.', 'woo-gutenberg-products-block' ), array( 'status' => 404 ) );
}
$data = $this->prepare_item_for_response( $request['code'], $request );
$response = rest_ensure_response( $data );
return $response;
}
/**
* Add a coupon to the cart and return the result.
*
* @param RestRequest $request Full data about the request.
* @return RestError|RestResponse Response object on success, or WP_Error object on failure.
*/
public function create_item( $request ) {
if ( ! wc_coupons_enabled() ) {
return new RestError( 'woocommerce_rest_cart_coupon_disabled', __( 'Coupons are disabled.', 'woo-gutenberg-products-block' ), array( 'status' => 404 ) );
}
$controller = new CartController();
try {
$controller->apply_coupon( $request['code'] );
} catch ( RestException $e ) {
return new RestError( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
}
$response = $this->get_item( $request );
if ( $response instanceof RestError ) {
return $response;
}
$response = rest_ensure_response( $response );
$response->set_status( 201 );
return $response;
}
/**
* Delete a single cart coupon.
*
* @param RestRequest $request Full data about the request.
* @return RestError|RestResponse Response object on success, or WP_Error object on failure.
*/
public function delete_item( $request ) {
$controller = new CartController();
if ( ! $controller->has_coupon( $request['code'] ) ) {
return new RestError( 'woocommerce_rest_cart_coupon_invalid_code', __( 'Coupon does not exist in the cart.', 'woo-gutenberg-products-block' ), array( 'status' => 404 ) );
}
$cart = $controller->get_cart_instance();
$cart->remove_coupon( $request['code'] );
return new RestResponse( null, 204 );
}
/**
* Deletes all coupons in the cart.
*
* @param RestRequest $request Full data about the request.
* @return RestError|RestResponse Response object on success, or WP_Error object on failure.
*/
public function delete_items( $request ) {
$controller = new CartController();
$cart = $controller->get_cart_instance();
$cart->remove_coupons();
return new RestResponse( [], 200 );
}
/**
* Cart item schema.
*
* @return array
*/
public function get_item_schema() {
return $this->schema->get_item_schema();
}
/**
* Prepares a single item output for response.
*
* @param string $coupon_code Coupon code.
* @param RestRequest $request Request object.
* @return RestResponse Response object.
*/
public function prepare_item_for_response( $coupon_code, $request ) {
return rest_ensure_response( $this->schema->get_item_response( $coupon_code ) );
}
/**
* Prepare links for the request.
*
* @param string $coupon_code Coupon code.
* @return array
*/
protected function prepare_links( $coupon_code ) {
$base = $this->namespace . '/' . $this->rest_base;
$links = array(
'self' => array(
'href' => rest_url( trailingslashit( $base ) . $coupon_code ),
),
'collection' => array(
'href' => rest_url( $base ),
),
);
return $links;
}
}

View File

@ -90,6 +90,7 @@ Available resources in the Store API include:
| [`Products`](#products-api) | `/wc/store/products` |
| [`Cart`](#cart-api) | `/wc/store/cart` |
| [`Cart Items`](#cart-items-api) | `/wc/store/cart/items` |
| [`Cart Coupons`](#cart-coupons-api) | `/wc/store/cart/coupons` |
| [`Cart Shipping Rates`](#cart-shipping-rates-api) | `/wc/store/cart/shipping-rates` |
| [`Product Attributes`](#product-attributes-api) | `/wc/store/products/attributes` |
| [`Product Attribute Terms`](#product-attribute-terms-api) | `/wc/store/products/attributes/1/terms` |
@ -321,6 +322,7 @@ Example response:
```json
{
"coupons": [],
"items": [
{
"key": "6512bd43d9caa6e02c990b0a82652dca",
@ -800,6 +802,159 @@ Example response:
[]
```
## Cart coupons API
### List cart coupons
```http
GET /cart/coupons
```
There are no parameters required for this endpoint.
```http
curl "https://example-store.com/wp-json/wc/store/cart/items"
```
Example response:
```json
[
{
"code": "20off",
"totals": {
"currency_code": "GBP",
"currency_symbol": "£",
"currency_minor_unit": 2,
"currency_decimal_separator": ".",
"currency_thousand_separator": ",",
"currency_prefix": "£",
"currency_suffix": "",
"total_discount": "1667",
"total_discount_tax": "333"
},
"_links": {
"self": [
{
"href": "http:\/\/local.wordpress.test\/wp-json\/wc\/store\/cart\/coupons\/20off"
}
],
"collection": [
{
"href": "http:\/\/local.wordpress.test\/wp-json\/wc\/store\/cart\/coupons"
}
]
}
}
]
```
### Single cart coupon
Get a single cart coupon.
```http
GET /cart/coupons/:code
```
| Attribute | Type | Required | Description |
| :-------- | :----- | :------: | :---------------------------------------------- |
| `code` | string | Yes | The coupon code of the cart coupon to retrieve. |
```http
curl "https://example-store.com/wp-json/wc/store/cart/coupons/20off"
```
Example response:
```json
{
"code": "20off",
"totals": {
"currency_code": "GBP",
"currency_symbol": "£",
"currency_minor_unit": 2,
"currency_decimal_separator": ".",
"currency_thousand_separator": ",",
"currency_prefix": "£",
"currency_suffix": "",
"total_discount": "1667",
"total_discount_tax": "333"
}
}
```
### New cart coupon
Apply a coupon to the cart.
```http
POST /cart/coupons/
```
| Attribute | Type | Required | Description |
| :-------- | :----- | :------: | :--------------------------------------------- |
| `code` | string | Yes | The coupon code you wish to apply to the cart. |
```http
curl --request POST https://example-store.com/wp-json/wc/store/cart/coupons?code=20off
```
Example response:
```json
{
"code": "20off",
"totals": {
"currency_code": "GBP",
"currency_symbol": "£",
"currency_minor_unit": 2,
"currency_decimal_separator": ".",
"currency_thousand_separator": ",",
"currency_prefix": "£",
"currency_suffix": "",
"total_discount": "1667",
"total_discount_tax": "333"
}
}
```
### Delete single cart coupon
Delete/remove a coupon from the cart.
```http
DELETE /cart/coupons/:code
```
| Attribute | Type | Required | Description |
| :-------- | :----- | :------: | :------------------------------------------------ |
| `code` | string | Yes | The coupon code you wish to remove from the cart. |
```http
curl --request DELETE https://example-store.com/wp-json/wc/store/cart/coupons/20off
```
### Delete all cart coupons
Delete/remove all coupons from the cart.
```http
DELETE /cart/coupons/
```
There are no parameters required for this endpoint.
```http
curl --request DELETE https://example-store.com/wp-json/wc/store/cart/coupons
```
Example response:
```json
[]
```
## Cart shipping rates API
### Get shipping rates for current cart

View File

@ -0,0 +1,102 @@
<?php
/**
* Cart Coupon Schema.
*
* @package WooCommerce/Blocks
*/
namespace Automattic\WooCommerce\Blocks\RestApi\StoreApi\Schemas;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Blocks\RestApi\StoreApi\Utilities\CartController;
/**
* CartCouponSchema class.
*
* @since 2.5.0
*/
class CartCouponSchema extends AbstractSchema {
/**
* The schema item name.
*
* @var string
*/
protected $title = 'cart_coupon';
/**
* Cart schema properties.
*
* @return array
*/
protected function get_properties() {
return [
'code' => [
'description' => __( 'The coupons unique code.', 'woo-gutenberg-products-block' ),
'type' => 'string',
'context' => [ 'view', 'edit' ],
'arg_options' => [
'sanitize_callback' => 'wc_format_coupon_code',
'validate_callback' => [ $this, 'coupon_exists' ],
],
],
'totals' => [
'description' => __( 'Total amounts provided using the smallest unit of the currency.', 'woo-gutenberg-products-block' ),
'type' => 'object',
'context' => [ 'view', 'edit' ],
'readonly' => true,
'properties' => array_merge(
$this->get_store_currency_properties(),
[
'total_discount' => [
'description' => __( 'Total discount applied by this coupon.', 'woo-gutenberg-products-block' ),
'type' => 'string',
'context' => [ 'view', 'edit' ],
'readonly' => true,
],
'total_discount_tax' => [
'description' => __( 'Total tax removed due to discount applied by this coupon.', 'woo-gutenberg-products-block' ),
'type' => 'string',
'context' => [ 'view', 'edit' ],
'readonly' => true,
],
]
),
],
];
}
/**
* Check given coupon exists.
*
* @param string $coupon_code Coupon code.
* @return bool
*/
public function coupon_exists( $coupon_code ) {
$coupon = new \WC_Coupon( $coupon_code );
return (bool) $coupon->get_id() || $coupon->get_virtual();
}
/**
* Convert a WooCommerce cart item to an object suitable for the response.
*
* @param string $coupon_code Coupon code from the cart.
* @return array
*/
public function get_item_response( $coupon_code ) {
$controller = new CartController();
$cart = $controller->get_cart_instance();
$total_discounts = $cart->get_coupon_discount_totals();
$total_discount_taxes = $cart->get_coupon_discount_tax_totals();
return [
'code' => $coupon_code,
'totals' => array_merge(
$this->get_store_currency_response(),
[
'total_discount' => $this->prepare_money_response( isset( $total_discounts[ $coupon_code ] ) ? $total_discounts[ $coupon_code ] : 0, wc_get_price_decimals() ),
'total_discount_tax' => $this->prepare_money_response( isset( $total_discount_taxes[ $coupon_code ] ) ? $total_discount_taxes[ $coupon_code ] : 0, wc_get_price_decimals(), PHP_ROUND_HALF_DOWN ),
]
),
];
}
}

View File

@ -1,8 +1,6 @@
<?php
/**
* Abstract Schema.
*
* Rest API schema class.
* Cart Item Schema.
*
* @package WooCommerce/Blocks
*/
@ -14,7 +12,7 @@ defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Blocks\RestApi\Utilities\ProductImages;
/**
* AbstractBlock class.
* CartItemSchema class.
*
* @since 2.5.0
*/

View File

@ -29,6 +29,16 @@ class CartSchema extends AbstractSchema {
*/
protected function get_properties() {
return [
'coupons' => [
'description' => __( 'List of applied cart coupons.', 'woo-gutenberg-products-block' ),
'type' => 'array',
'context' => [ 'view', 'edit' ],
'readonly' => true,
'items' => [
'type' => 'object',
'properties' => $this->force_schema_readonly( ( new CartCouponSchema() )->get_properties() ),
],
],
'items' => [
'description' => __( 'List of cart items.', 'woo-gutenberg-products-block' ),
'type' => 'array',
@ -161,8 +171,10 @@ class CartSchema extends AbstractSchema {
* @return array
*/
public function get_item_response( $cart ) {
$cart_item_schema = new CartItemSchema();
$cart_coupon_schema = new CartCouponSchema();
$cart_item_schema = new CartItemSchema();
return [
'coupons' => array_values( array_map( [ $cart_coupon_schema, 'get_item_response' ], array_filter( $cart->get_applied_coupons() ) ) ),
'items' => array_values( array_map( [ $cart_item_schema, 'get_item_response' ], array_filter( $cart->get_cart() ) ) ),
'items_count' => $cart->get_cart_contents_count(),
'items_weight' => wc_get_weight( $cart->get_cart_contents_weight(), 'g' ),

View File

@ -2,8 +2,6 @@
/**
* Helper class to bridge the gap between the cart API and Woo core.
*
* Overrides some of the woo core cart methods to make them work with the API and generally increase flexibility. Some of this logic should move to core.
*
* @package WooCommerce/Blocks
*/
@ -24,6 +22,10 @@ class CartController {
/**
* Based on the core cart class but returns errors rather than rendering notices directly.
*
* @todo Overriding the core add_to_cart method was necessary because core outputs notices when an item is added to
* the cart. For us this would cause notices to build up and output on the store, out of context. Core would need
* refactoring to split notices out from other cart actions.
*
* @throws RestException Exception if invalid data is detected.
*
* @param array $request Add to cart request params.
@ -52,21 +54,44 @@ class CartController {
$variation_id = 0;
}
$cart_id = wc()->cart->generate_cart_id( $product_id, $variation_id, $request['variation'], $request['cart_item_data'] );
$cart_id = wc()->cart->generate_cart_id(
$product_id,
$variation_id,
$request['variation'],
$request['cart_item_data']
);
$existing_cart_id = wc()->cart->find_product_in_cart( $cart_id );
if ( ! $product->is_purchasable() ) {
throw new RestException( 'woocommerce_rest_cart_product_is_not_purchasable', __( 'This product cannot be purchased.', 'woo-gutenberg-products-block' ), 403 );
throw new RestException(
'woocommerce_rest_cart_product_is_not_purchasable',
__( 'This product cannot be purchased.', 'woo-gutenberg-products-block' ),
403
);
}
if ( $product->is_sold_individually() && $existing_cart_id ) {
/* translators: %s: product name */
throw new RestException( 'woocommerce_rest_cart_product_sold_individually', sprintf( __( '"%s" is already inside your cart.', 'woo-gutenberg-products-block' ), $product->get_name() ), 403 );
throw new RestException(
'woocommerce_rest_cart_product_sold_individually',
sprintf(
/* translators: %s: product name */
__( '"%s" is already inside your cart.', 'woo-gutenberg-products-block' ),
$product->get_name()
),
403
);
}
if ( ! $product->is_in_stock() ) {
/* translators: %s: product name */
throw new RestException( 'woocommerce_rest_cart_product_no_stock', sprintf( __( 'You cannot add &quot;%s&quot; to the cart because the product is out of stock.', 'woo-gutenberg-products-block' ), $product->get_name() ), 403 );
throw new RestException(
'woocommerce_rest_cart_product_no_stock',
sprintf(
/* translators: %s: product name */
__( 'You cannot add &quot;%s&quot; to the cart because the product is out of stock.', 'woo-gutenberg-products-block' ),
$product->get_name()
),
403
);
}
if ( $product->managing_stock() ) {
@ -112,7 +137,15 @@ class CartController {
wc()->cart->cart_contents = apply_filters( 'woocommerce_cart_contents_changed', wc()->cart->cart_contents );
do_action( 'woocommerce_add_to_cart', $cart_id, $product_id, $request['quantity'], $variation_id, $request['variation'], $request['cart_item_data'] );
do_action(
'woocommerce_add_to_cart',
$cart_id,
$product_id,
$request['quantity'],
$variation_id,
$request['variation'],
$request['cart_item_data']
);
return $cart_id;
} catch ( RestException $e ) {
@ -156,6 +189,104 @@ class CartController {
wc()->cart->empty_cart();
}
/**
* See if cart has applied coupon by code.
*
* @param string $coupon_code Cart coupon code.
* @return bool
*/
public function has_coupon( $coupon_code ) {
return wc()->cart->has_discount( $coupon_code );
}
/**
* Returns all applied coupons.
*
* @param callable $callback Optional callback to apply to the array filter.
* @return array
*/
public function get_cart_coupons( $callback = null ) {
return $callback ? array_filter( wc()->cart->get_applied_coupons(), $callback ) : array_filter( wc()->cart->get_applied_coupons() );
}
/**
* Based on the core cart class but returns errors rather than rendering notices directly.
*
* @todo Overriding the core apply_coupon method was necessary because core outputs notices when a coupon gets
* applied. For us this would cause notices to build up and output on the store, out of context. Core would need
* refactoring to split notices out from other cart actions.
*
* @throws RestException Exception if invalid data is detected.
*
* @param string $coupon_code Coupon code.
*/
public function apply_coupon( $coupon_code ) {
$cart = $this->get_cart_instance();
$applied_coupons = $this->get_cart_coupons();
$coupon = new \WC_Coupon( $coupon_code );
if ( $coupon->get_code() !== $coupon_code ) {
throw new RestException(
'woocommerce_rest_cart_coupon_error',
__( 'Invalid coupon code.', 'woo-gutenberg-products-block' ),
403
);
}
if ( $this->has_coupon( $coupon_code ) ) {
throw new RestException(
'woocommerce_rest_cart_coupon_error',
__( 'Coupon has already been applied.', 'woo-gutenberg-products-block' ),
403
);
}
if ( ! $coupon->is_valid() ) {
throw new RestException(
'woocommerce_rest_cart_coupon_error',
$coupon->get_error_message(),
403
);
}
// Prevents new coupons being added if individual use coupons are already in the cart.
$individual_use_coupons = $this->get_cart_coupons(
function( $code ) {
$coupon = new \WC_Coupon( $code );
return $coupon->get_individual_use();
}
);
foreach ( $individual_use_coupons as $code ) {
$individual_use_coupon = new \WC_Coupon( $code );
if ( false === apply_filters( 'woocommerce_apply_with_individual_use_coupon', false, $coupon, $individual_use_coupon, $applied_coupons ) ) {
throw new RestException(
'woocommerce_rest_cart_coupon_error',
sprintf(
/* translators: %s: coupon code */
__( '"%s" has already been applied and cannot be used in conjunction with other coupons.', 'woo-gutenberg-products-block' ),
$code
),
403
);
}
}
if ( $coupon->get_individual_use() ) {
$coupons_to_remove = array_diff( $applied_coupons, apply_filters( 'woocommerce_apply_individual_use_coupon', array(), $coupon, $applied_coupons ) );
foreach ( $coupons_to_remove as $code ) {
$cart->remove_coupon( $code );
}
}
$applied_coupons[] = $coupon_code;
$cart->set_applied_coupons( $applied_coupons );
do_action( 'woocommerce_applied_coupon', $coupon_code );
}
/**
* Get a product object to be added to the cart.
*
@ -168,7 +299,11 @@ class CartController {
$product = wc_get_product( $request['id'] );
if ( ! $product || 'trash' === $product->get_status() ) {
throw new RestException( 'woocommerce_rest_cart_invalid_product', __( 'This product cannot be added to the cart.', 'woo-gutenberg-products-block' ), 403 );
throw new RestException(
'woocommerce_rest_cart_invalid_product',
__( 'This product cannot be added to the cart.', 'woo-gutenberg-products-block' ),
403
);
}
return $product;
@ -297,7 +432,11 @@ class CartController {
$variation_id = $data_store->find_matching_product_variation( $product, $match_attributes );
if ( empty( $variation_id ) ) {
throw new RestException( 'woocommerce_rest_variation_id_from_variation_data', __( 'No matching variation found.', 'woo-gutenberg-products-block' ), 400 );
throw new RestException(
'woocommerce_rest_variation_id_from_variation_data',
__( 'No matching variation found.', 'woo-gutenberg-products-block' ),
400
);
}
return $variation_id;
@ -321,17 +460,36 @@ class CartController {
if ( ! $attribute['is_variation'] ) {
continue;
}
$attribute_label = wc_attribute_label( $attribute['name'] );
$attribute_label = wc_attribute_label( $attribute['name'] );
$variation_attribute_name = wc_variation_attribute_name( $attribute['name'] );
// Attribute labels e.g. Size.
if ( isset( $variation_data[ $attribute_label ] ) ) {
$return[ wc_variation_attribute_name( $attribute['name'] ) ] = $attribute['is_taxonomy'] ? sanitize_title( $variation_data[ $attribute_label ] ) : html_entity_decode( wc_clean( $variation_data[ $attribute_label ] ), ENT_QUOTES, get_bloginfo( 'charset' ) );
$return[ $variation_attribute_name ] =
$attribute['is_taxonomy']
?
sanitize_title( $variation_data[ $attribute_label ] )
:
html_entity_decode(
wc_clean( $variation_data[ $attribute_label ] ),
ENT_QUOTES,
get_bloginfo( 'charset' )
);
continue;
}
// Attribute slugs e.g. pa_size.
if ( isset( $variation_data[ $attribute['name'] ] ) ) {
$return[ wc_variation_attribute_name( $attribute['name'] ) ] = $attribute['is_taxonomy'] ? sanitize_title( $variation_data[ $attribute['name'] ] ) : html_entity_decode( wc_clean( $variation_data[ $attribute['name'] ] ), ENT_QUOTES, get_bloginfo( 'charset' ) );
$return[ $variation_attribute_name ] =
$attribute['is_taxonomy']
?
sanitize_title( $variation_data[ $attribute['name'] ] )
:
html_entity_decode(
wc_clean( $variation_data[ $attribute['name'] ] ),
ENT_QUOTES,
get_bloginfo( 'charset' )
);
}
}
return $return;
@ -351,7 +509,11 @@ class CartController {
}
if ( ! $product || 'trash' === $product->get_status() ) {
throw new RestException( 'woocommerce_rest_cart_invalid_parent_product', __( 'This product cannot be added to the cart.', 'woo-gutenberg-products-block' ), 403 );
throw new RestException(
'woocommerce_rest_cart_invalid_parent_product',
__( 'This product cannot be added to the cart.', 'woo-gutenberg-products-block' ),
403
);
}
return $product->get_attributes();

View File

@ -0,0 +1,169 @@
<?php
/**
* Controller Tests.
*
* @package WooCommerce\Blocks\Tests
*/
namespace Automattic\WooCommerce\Blocks\Tests\RestApi\StoreApi\Controllers;
use \WP_REST_Request;
use \WC_REST_Unit_Test_Case as TestCase;
use \WC_Helper_Product as ProductHelper;
use \WC_Helper_Coupon as CouponHelper;
/**
* Cart Coupons Controller Tests.
*/
class CartCoupons extends TestCase {
/**
* Setup test products data. Called before every test.
*/
public function setUp() {
parent::setUp();
wp_set_current_user( 0 );
$this->product = ProductHelper::create_simple_product( false );
$this->coupon = CouponHelper::create_coupon();
wc_empty_cart();
wc()->cart->add_to_cart( $this->product->get_id(), 2 );
wc()->cart->apply_coupon( $this->coupon->get_code() );
}
/**
* Test route registration.
*/
public function test_register_routes() {
$routes = $this->server->get_routes();
$this->assertArrayHasKey( '/wc/store/cart/coupons', $routes );
$this->assertArrayHasKey( '/wc/store/cart/coupons/(?P<code>[\w-]+)', $routes );
}
/**
* Test getting cart.
*/
public function test_get_items() {
$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/store/cart/coupons' ) );
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 1, count( $data ) );
}
/**
* Test getting cart item by key.
*/
public function test_get_item() {
$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/store/cart/coupons/' . $this->coupon->get_code() ) );
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( $this->coupon->get_code(), $data['code'] );
$this->assertEquals( '0', $data['totals']['total_discount'] );
$this->assertEquals( '0', $data['totals']['total_discount_tax'] );
}
/**
* Test add to cart.
*/
public function test_create_item() {
wc()->cart->remove_coupons();
$request = new WP_REST_Request( 'POST', '/wc/store/cart/coupons' );
$request->set_body_params(
array(
'code' => $this->coupon->get_code(),
)
);
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 201, $response->get_status() );
$this->assertEquals( $this->coupon->get_code(), $data['code'] );
}
/**
* Test add to cart does not allow invalid items.
*/
public function test_invalid_create_item() {
wc()->cart->remove_coupons();
$request = new WP_REST_Request( 'POST', '/wc/store/cart/coupons' );
$request->set_body_params(
array(
'code' => 'IDONOTEXIST',
)
);
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 400, $response->get_status() );
}
/**
* Test delete item.
*/
public function test_delete_item() {
$request = new WP_REST_Request( 'DELETE', '/wc/store/cart/coupons/' . $this->coupon->get_code() );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 204, $response->get_status() );
$this->assertEmpty( $data );
$request = new WP_REST_Request( 'DELETE', '/wc/store/cart/coupons/' . $this->coupon->get_code() );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 404, $response->get_status() );
$request = new WP_REST_Request( 'DELETE', '/wc/store/cart/coupons/i-do-not-exist' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 404, $response->get_status() );
}
/**
* Test delete all items.
*/
public function test_delete_items() {
$request = new WP_REST_Request( 'DELETE', '/wc/store/cart/coupons' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( [], $data );
$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/store/cart/coupons' ) );
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 0, count( $data ) );
}
/**
* Test schema retrieval.
*/
public function test_get_item_schema() {
$controller = new \Automattic\WooCommerce\Blocks\RestApi\StoreApi\Controllers\CartCoupons();
$schema = $controller->get_item_schema();
$this->assertArrayHasKey( 'code', $schema['properties'] );
$this->assertArrayHasKey( 'totals', $schema['properties'] );
}
/**
* Test conversion of cart item to rest response.
*/
public function test_prepare_item_for_response() {
$controller = new \Automattic\WooCommerce\Blocks\RestApi\StoreApi\Controllers\CartCoupons();
$response = $controller->prepare_item_for_response( $this->coupon->get_code(), [] );
$this->assertArrayHasKey( 'code', $response->get_data() );
$this->assertArrayHasKey( 'totals', $response->get_data() );
}
}