REST API - Cart Order API (https://github.com/woocommerce/woocommerce-blocks/pull/1425)
* Checkout/order WIP schema * Add _address suffix for billing/shipping * Rename schema, update endpoints, create tests * Fix POST in test * Fix test response checks * Stock reservation and draft order status * Add todo for shipping lines * Readme * Rename address fields in readme * 10 min timeout of stock * Fix broken test * Update src/RestApi/StoreApi/Controllers/CartOrder.php Co-Authored-By: Darren Ethier <darren@roughsmootheng.in> * Add typehinting where possible * Remove explicit pass by reference * Further typehinting * Clarify todo comment * Validate product instances * Specific phpcs exclusion rule * Exclusion rule * Move ReserveStock code to class * Correct shipping-rates schema to shipping_rates * Save shipping rates and lines if included with request * Insert todo for shipping rate code * Calculate shipping and selected shipping from order properties, not global cart properties * Prevent error when shipping is not needed * Update API readme * Added tests for stock reserve class * Fixes conflicts with draft statuses Co-authored-by: Darren Ethier <darren@roughsmootheng.in>
This commit is contained in:
parent
f339e28310
commit
0150681c4b
|
@ -19,6 +19,63 @@ class Library {
|
|||
*/
|
||||
public static function init() {
|
||||
add_action( 'init', array( __CLASS__, 'register_blocks' ) );
|
||||
add_action( 'init', array( __CLASS__, 'define_tables' ) );
|
||||
add_action( 'init', array( __CLASS__, 'maybe_create_tables' ) );
|
||||
add_filter( 'wc_order_statuses', array( __CLASS__, 'register_draft_order_status' ) );
|
||||
add_filter( 'woocommerce_register_shop_order_post_statuses', array( __CLASS__, 'register_draft_order_post_status' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register custom tables within $wpdb object.
|
||||
*/
|
||||
public static function define_tables() {
|
||||
global $wpdb;
|
||||
|
||||
// List of tables without prefixes.
|
||||
$tables = array(
|
||||
'wc_reserved_stock' => 'wc_reserved_stock',
|
||||
);
|
||||
|
||||
foreach ( $tables as $name => $table ) {
|
||||
$wpdb->$name = $wpdb->prefix . $table;
|
||||
$wpdb->tables[] = $table;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the database tables which the plugin needs to function.
|
||||
*/
|
||||
public static function maybe_create_tables() {
|
||||
$db_version = get_option( 'wc_blocks_db_version', 0 );
|
||||
|
||||
if ( version_compare( $db_version, \Automattic\WooCommerce\Blocks\Package::get_version(), '>=' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||
|
||||
$wpdb->hide_errors();
|
||||
$collate = '';
|
||||
|
||||
if ( $wpdb->has_cap( 'collation' ) ) {
|
||||
$collate = $wpdb->get_charset_collate();
|
||||
}
|
||||
|
||||
dbDelta(
|
||||
"
|
||||
CREATE TABLE {$wpdb->prefix}wc_reserved_stock (
|
||||
`order_id` bigint(20) NOT NULL,
|
||||
`product_id` bigint(20) NOT NULL,
|
||||
`stock_quantity` double NOT NULL DEFAULT 0,
|
||||
`timestamp` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`order_id`, `product_id`)
|
||||
) $collate;
|
||||
"
|
||||
);
|
||||
|
||||
update_option( 'wc_blocks_db_version', \Automattic\WooCommerce\Blocks\Package::get_version() );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,4 +116,36 @@ class Library {
|
|||
$instance->register_block_type();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register custom order status for orders created via the API during checkout.
|
||||
*
|
||||
* Draft order status is used before payment is attempted, during checkout, when a cart is converted to an order.
|
||||
*
|
||||
* @param array $statuses Array of statuses.
|
||||
* @return array
|
||||
*/
|
||||
public static function register_draft_order_status( array $statuses ) {
|
||||
$statuses['wc-checkout-draft'] = _x( 'Draft', 'Order status', 'woo-gutenberg-products-block' );
|
||||
return $statuses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register custom order post status for orders created via the API during checkout.
|
||||
*
|
||||
* @param array $statuses Array of statuses.
|
||||
* @return array
|
||||
*/
|
||||
public static function register_draft_order_post_status( array $statuses ) {
|
||||
$statuses['wc-checkout-draft'] = [
|
||||
'label' => _x( 'Draft', 'Order status', 'woo-gutenberg-products-block' ),
|
||||
'public' => false,
|
||||
'exclude_from_search' => false,
|
||||
'show_in_admin_all_list' => false,
|
||||
'show_in_admin_status_list' => true,
|
||||
/* translators: %s: number of orders */
|
||||
'label_count' => _n_noop( 'Drafts <span class="count">(%s)</span>', 'Drafts <span class="count">(%s)</span>', 'woo-gutenberg-products-block' ),
|
||||
];
|
||||
return $statuses;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ class RestApi {
|
|||
}
|
||||
|
||||
/**
|
||||
* If we're making a cart request, we may need to load some additonal classes from WC Core so we're ready to deal with requests.
|
||||
* If we're making a cart request, we may need to load some additional classes from WC Core so we're ready to deal with requests.
|
||||
*
|
||||
* Note: We load the session here early so guest nonces are in place.
|
||||
*
|
||||
|
@ -96,6 +96,7 @@ class RestApi {
|
|||
'store-cart-items' => __NAMESPACE__ . '\RestApi\StoreApi\Controllers\CartItems',
|
||||
'store-cart-coupons' => __NAMESPACE__ . '\RestApi\StoreApi\Controllers\CartCoupons',
|
||||
'store-cart-shipping-rates' => __NAMESPACE__ . '\RestApi\StoreApi\Controllers\CartShippingRates',
|
||||
'store-cart-order' => __NAMESPACE__ . '\RestApi\StoreApi\Controllers\CartOrder',
|
||||
'store-customer' => __NAMESPACE__ . '\RestApi\StoreApi\Controllers\Customer',
|
||||
'store-products' => __NAMESPACE__ . '\RestApi\StoreApi\Controllers\Products',
|
||||
'store-product-collection-data' => __NAMESPACE__ . '\RestApi\StoreApi\Controllers\ProductCollectionData',
|
||||
|
|
|
@ -0,0 +1,456 @@
|
|||
<?php
|
||||
/**
|
||||
* Cart Order 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;
|
||||
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\OrderSchema;
|
||||
use Automattic\WooCommerce\Blocks\RestApi\StoreApi\Utilities\ReserveStock;
|
||||
|
||||
/**
|
||||
* Cart Order API.
|
||||
*
|
||||
* Creates orders based on cart contents.
|
||||
*/
|
||||
class CartOrder extends RestController {
|
||||
/**
|
||||
* Endpoint namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'wc/store';
|
||||
|
||||
/**
|
||||
* Route base.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base = 'cart/order';
|
||||
|
||||
/**
|
||||
* Schema class instance.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected $schema;
|
||||
|
||||
/**
|
||||
* Draft order details, if applicable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $draft_order;
|
||||
|
||||
/**
|
||||
* Setup API class.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->schema = new OrderSchema();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
[
|
||||
[
|
||||
'methods' => RestServer::CREATABLE,
|
||||
'callback' => array( $this, 'create_item' ),
|
||||
'args' => array_merge(
|
||||
$this->get_endpoint_args_for_item_schema( RestServer::CREATABLE ),
|
||||
array(
|
||||
'shipping_rates' => array(
|
||||
'description' => __( 'Selected shipping rates to apply to the order.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'array',
|
||||
'required' => false,
|
||||
'items' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'rate_id' => [
|
||||
'description' => __( 'ID of the shipping rate.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
],
|
||||
],
|
||||
],
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
'schema' => [ $this, 'get_public_item_schema' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the global cart to an order object.
|
||||
*
|
||||
* @todo Since this relies on the cart global so much, why doesn't the core cart class do this?
|
||||
*
|
||||
* Based on WC_Checkout::create_order.
|
||||
*
|
||||
* @param RestRequest $request Full details about the request.
|
||||
* @return WP_Error|RestResponse
|
||||
*/
|
||||
public function create_item( $request ) {
|
||||
try {
|
||||
$this->draft_order = WC()->session->get(
|
||||
'store_api_draft_order',
|
||||
[
|
||||
'id' => 0,
|
||||
'hashes' => [
|
||||
'line_items' => false,
|
||||
'fees' => false,
|
||||
'coupons' => false,
|
||||
'taxes' => false,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
// Update session based on posted data.
|
||||
$this->update_session( $request );
|
||||
|
||||
// Create or retrieve the draft order for the current cart.
|
||||
$order_object = $this->create_order_from_cart( $request );
|
||||
|
||||
// Try to reserve stock, if available.
|
||||
$this->reserve_stock( $order_object );
|
||||
|
||||
$response = $this->prepare_item_for_response( $order_object, $request );
|
||||
$response->set_status( 201 );
|
||||
return $response;
|
||||
} catch ( RestException $e ) {
|
||||
return new WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getCode() );
|
||||
} catch ( Exception $e ) {
|
||||
return new WP_Error( 'create-order-error', $e->getMessage(), [ 'status' => 500 ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Before creating anything, this method ensures the cart session is up to date and matches the data we're going
|
||||
* to be adding to the order.
|
||||
*
|
||||
* @param RestRequest $request Full details about the request.
|
||||
* @return void
|
||||
*/
|
||||
protected function update_session( RestRequest $request ) {
|
||||
$schema = $this->get_item_schema();
|
||||
|
||||
if ( isset( $request['billing_address'] ) ) {
|
||||
$allowed_billing_values = array_intersect_key( $request['billing_address'], $schema['properties']['billing_address']['properties'] );
|
||||
foreach ( $allowed_billing_values as $key => $value ) {
|
||||
WC()->customer->{"set_billing_$key"}( $value );
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $request['shipping_address'] ) ) {
|
||||
$allowed_shipping_values = array_intersect_key( $request['shipping_address'], $schema['properties']['shipping_address']['properties'] );
|
||||
foreach ( $allowed_shipping_values as $key => $value ) {
|
||||
WC()->customer->{"set_shipping_$key"}( $value );
|
||||
}
|
||||
}
|
||||
|
||||
WC()->customer->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a temporary hold on stock for this order.
|
||||
*
|
||||
* @throws RestException Exception when stock cannot be reserved.
|
||||
* @param \WC_Order $order Order object.
|
||||
*/
|
||||
protected function reserve_stock( \WC_Order $order ) {
|
||||
$reserve_stock_helper = new ReserveStock();
|
||||
$result = $reserve_stock_helper->reserve_stock_for_order( $order );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
throw new RestException( $result->get_error_code(), $result->get_error_message(), $result->get_error_data( 'status' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create order and set props based on global settings.
|
||||
*
|
||||
* @param RestRequest $request Full details about the request.
|
||||
* @return \WC_Order A new order object.
|
||||
*/
|
||||
protected function create_order_from_cart( RestRequest $request ) {
|
||||
add_filter( 'woocommerce_default_order_status', array( $this, 'default_order_status' ) );
|
||||
|
||||
$order = $this->get_order_object();
|
||||
$order->set_status( 'checkout-draft' );
|
||||
$order->set_created_via( 'store-api' );
|
||||
$order->set_currency( get_woocommerce_currency() );
|
||||
$order->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
|
||||
$order->set_customer_id( get_current_user_id() );
|
||||
$order->set_customer_ip_address( \WC_Geolocation::get_ip_address() );
|
||||
$order->set_customer_user_agent( wc_get_user_agent() );
|
||||
$order->set_cart_hash( WC()->cart->get_cart_hash() );
|
||||
$order->update_meta_data( 'is_vat_exempt', WC()->cart->get_customer()->get_is_vat_exempt() ? 'yes' : 'no' );
|
||||
|
||||
$this->set_props_from_request( $order, $request );
|
||||
$this->create_line_items_from_cart( $order, $request );
|
||||
$this->select_shipping_rates( $order, $request );
|
||||
|
||||
// Calc totals, taxes, and save.
|
||||
$order->calculate_totals();
|
||||
|
||||
remove_filter( 'woocommerce_default_order_status', array( $this, 'default_order_status' ) );
|
||||
|
||||
// Store Order details in session so we can look it up later.
|
||||
WC()->session->set(
|
||||
'store_api_draft_order',
|
||||
[
|
||||
'id' => $order->get_id(),
|
||||
'hashes' => $this->get_cart_hashes( $order, $request ),
|
||||
]
|
||||
);
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hashes for items in the current cart. Useful for tracking changes.
|
||||
*
|
||||
* @param \WC_Order $order Object to prepare for the response.
|
||||
* @param RestRequest $request Full details about the request.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_cart_hashes( \WC_Order $order, RestRequest $request ) {
|
||||
return [
|
||||
'line_items' => md5( wp_json_encode( WC()->cart->get_cart() ) ),
|
||||
'fees' => md5( wp_json_encode( WC()->cart->get_fees() ) ),
|
||||
'coupons' => md5( wp_json_encode( WC()->cart->get_applied_coupons() ) ),
|
||||
'taxes' => md5( wp_json_encode( WC()->cart->get_taxes() ) ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an order object, either using a current draft order, or returning a new one.
|
||||
*
|
||||
* @return \WC_Order A new order object.
|
||||
*/
|
||||
protected function get_order_object() {
|
||||
$draft_order = $this->draft_order['id'] ? wc_get_order( $this->draft_order['id'] ) : false;
|
||||
|
||||
if ( $draft_order && $draft_order->has_status( 'draft' ) && 'store-api' === $draft_order->get_created_via() ) {
|
||||
return $draft_order;
|
||||
}
|
||||
|
||||
return new \WC_Order();
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes default order status to draft for orders created via this API.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function default_order_status() {
|
||||
return 'checkout-draft';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create order line items.
|
||||
*
|
||||
* @todo Knowing if items changed between the order and cart can be complex. Line items are ok because there is a
|
||||
* hash, but no hash exists for other line item types. Having a normalised set of data between cart and order, or
|
||||
* additional hashes, would be useful in the future and to help refactor this code. In the meantime, we're relying
|
||||
* on custom hashes in $this->draft_order to track if things changed.
|
||||
*
|
||||
* @param \WC_Order $order Object to prepare for the response.
|
||||
* @param RestRequest $request Full details about the request.
|
||||
*/
|
||||
protected function create_line_items_from_cart( \WC_Order $order, RestRequest $request ) {
|
||||
$new_hashes = $this->get_cart_hashes( $order, $request );
|
||||
$old_hashes = $this->draft_order['hashes'];
|
||||
|
||||
if ( $new_hashes['line_items'] !== $old_hashes['line_items'] ) {
|
||||
$order->remove_order_items( 'line_item' );
|
||||
WC()->checkout->create_order_line_items( $order, WC()->cart );
|
||||
}
|
||||
|
||||
if ( $new_hashes['coupons'] !== $old_hashes['coupons'] ) {
|
||||
$order->remove_order_items( 'coupon' );
|
||||
WC()->checkout->create_order_coupon_lines( $order, WC()->cart );
|
||||
}
|
||||
|
||||
if ( $new_hashes['fees'] !== $old_hashes['fees'] ) {
|
||||
$order->remove_order_items( 'fee' );
|
||||
WC()->checkout->create_order_fee_lines( $order, WC()->cart );
|
||||
}
|
||||
|
||||
if ( $new_hashes['taxes'] !== $old_hashes['taxes'] ) {
|
||||
$order->remove_order_items( 'tax' );
|
||||
WC()->checkout->create_order_tax_lines( $order, WC()->cart );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select shipping rates and store to order as line items.
|
||||
*
|
||||
* @throws RestException Exception when shipping is invalid.
|
||||
* @param \WC_Order $order Object to prepare for the response.
|
||||
* @param RestRequest $request Full details about the request.
|
||||
*/
|
||||
protected function select_shipping_rates( \WC_Order $order, RestRequest $request ) {
|
||||
$packages = $this->get_shipping_packages( $order, $request );
|
||||
$selected_rates = isset( $request['shipping_rates'] ) ? wp_list_pluck( $request['shipping_rates'], 'rate_id' ) : [];
|
||||
$shipping_hash = md5( wp_json_encode( $packages ) . wp_json_encode( $selected_rates ) );
|
||||
$stored_hash = WC()->session->get( 'store_api_shipping_hash' );
|
||||
|
||||
if ( $shipping_hash === $stored_hash ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$order->remove_order_items( 'shipping' );
|
||||
|
||||
foreach ( $packages as $package_key => $package ) {
|
||||
$rates = $package['rates'];
|
||||
$fallback_rate_id = current( array_keys( $rates ) );
|
||||
$selected_rate_id = isset( $selected_rates[ $package_key ] ) ? $selected_rates[ $package_key ] : $fallback_rate_id;
|
||||
$selected_rate = isset( $rates[ $selected_rate_id ] ) ? $rates[ $selected_rate_id ] : false;
|
||||
|
||||
if ( ! $rates ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! $selected_rate ) {
|
||||
throw new RestException(
|
||||
'invalid-shipping-rate-id',
|
||||
sprintf(
|
||||
/* translators: 1: Rate ID, 2: list of valid ids */
|
||||
__( '%1$s is not a valid shipping rate ID. Select one of the following: %2$s', 'woo-gutenberg-products-block' ),
|
||||
$selected_rate_id,
|
||||
implode( ', ', array_keys( $rates ) )
|
||||
),
|
||||
403
|
||||
);
|
||||
}
|
||||
|
||||
$item = new \WC_Order_Item_Shipping();
|
||||
$item->set_props(
|
||||
array(
|
||||
'method_title' => $selected_rate->label,
|
||||
'method_id' => $selected_rate->method_id,
|
||||
'instance_id' => $selected_rate->instance_id,
|
||||
'total' => wc_format_decimal( $selected_rate->cost ),
|
||||
'taxes' => array(
|
||||
'total' => $selected_rate->taxes,
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
foreach ( $selected_rate->get_meta_data() as $key => $value ) {
|
||||
$item->add_meta_data( $key, $value, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Action hook to adjust item before save.
|
||||
*/
|
||||
do_action( 'woocommerce_checkout_create_order_shipping_item', $item, $package_key, $package, $order );
|
||||
|
||||
// Add item to order and save.
|
||||
$order->add_item( $item );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get packages with calculated shipping.
|
||||
*
|
||||
* Based on WC_Cart::get_shipping_packages but allows the destination to be
|
||||
* customised based on the address in the order.
|
||||
*
|
||||
* @param \WC_Order $order Object to prepare for the response.
|
||||
* @param RestRequest $request Full details about the request.
|
||||
* @return array of packages and shipping rates.
|
||||
*/
|
||||
protected function get_shipping_packages( \WC_Order $order, RestRequest $request ) {
|
||||
if ( ! WC()->cart->needs_shipping() ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$packages = WC()->cart->get_shipping_packages();
|
||||
|
||||
foreach ( $packages as $key => $package ) {
|
||||
$packages[ $key ]['destination'] = [
|
||||
'address_1' => $order->get_shipping_address_1(),
|
||||
'address_2' => $order->get_shipping_address_2(),
|
||||
'city' => $order->get_shipping_city(),
|
||||
'state' => $order->get_shipping_state(),
|
||||
'postcode' => $order->get_shipping_postcode(),
|
||||
'country' => $order->get_shipping_country(),
|
||||
];
|
||||
}
|
||||
|
||||
$packages = WC()->shipping()->calculate_shipping( $packages );
|
||||
|
||||
return $packages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set props from API request.
|
||||
*
|
||||
* @param \WC_Order $order Object to prepare for the response.
|
||||
* @param RestRequest $request Full details about the request.
|
||||
*/
|
||||
protected function set_props_from_request( \WC_Order $order, RestRequest $request ) {
|
||||
$schema = $this->get_item_schema();
|
||||
|
||||
if ( isset( $request['billing_address'] ) ) {
|
||||
$allowed_billing_values = array_intersect_key( $request['billing_address'], $schema['properties']['billing_address']['properties'] );
|
||||
foreach ( $allowed_billing_values as $key => $value ) {
|
||||
$order->{"set_billing_$key"}( $value );
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $request['shipping_address'] ) ) {
|
||||
$allowed_shipping_values = array_intersect_key( $request['shipping_address'], $schema['properties']['shipping_address']['properties'] );
|
||||
foreach ( $allowed_shipping_values as $key => $value ) {
|
||||
$order->{"set_shipping_$key"}( $value );
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $request['customer_note'] ) ) {
|
||||
$order->set_customer_note( $request['customer_note'] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cart item schema.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
return $this->schema->get_item_schema();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a single item output for response.
|
||||
*
|
||||
* @param \WC_Order $object Object to prepare for the response.
|
||||
* @param RestRequest $request Request object.
|
||||
* @return RestResponse Response object.
|
||||
*/
|
||||
public function prepare_item_for_response( $object, $request ) {
|
||||
$response = [];
|
||||
|
||||
if ( $object instanceof \WC_Order ) {
|
||||
$response = $this->schema->get_item_response( $object );
|
||||
}
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
}
|
|
@ -114,15 +114,15 @@ class Customer extends RestController {
|
|||
}
|
||||
|
||||
try {
|
||||
if ( isset( $request['billing'] ) ) {
|
||||
$allowed_billing_values = array_intersect_key( $request['billing'], $schema['properties']['billing']['properties'] );
|
||||
if ( isset( $request['billing_address'] ) ) {
|
||||
$allowed_billing_values = array_intersect_key( $request['billing_address'], $schema['properties']['billing_address']['properties'] );
|
||||
foreach ( $allowed_billing_values as $key => $value ) {
|
||||
$customer->{"set_billing_$key"}( $value );
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $request['shipping'] ) ) {
|
||||
$allowed_shipping_values = array_intersect_key( $request['shipping'], $schema['properties']['shipping']['properties'] );
|
||||
if ( isset( $request['shipping_address'] ) ) {
|
||||
$allowed_shipping_values = array_intersect_key( $request['shipping_address'], $schema['properties']['shipping_address']['properties'] );
|
||||
foreach ( $allowed_shipping_values as $key => $value ) {
|
||||
$customer->{"set_shipping_$key"}( $value );
|
||||
}
|
||||
|
|
|
@ -820,32 +820,32 @@ 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
|
@ -869,18 +869,18 @@ 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"
|
||||
}
|
||||
"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"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -904,18 +904,18 @@ 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"
|
||||
}
|
||||
"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"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -990,7 +990,7 @@ Example response:
|
|||
"country": "US"
|
||||
},
|
||||
"items": [ "6512bd43d9caa6e02c990b0a82652dca" ],
|
||||
"shipping-rates": [
|
||||
"shipping_rates": [
|
||||
{
|
||||
"name": "International",
|
||||
"description": "",
|
||||
|
@ -1011,6 +1011,137 @@ Example response:
|
|||
]
|
||||
```
|
||||
|
||||
## Cart Order API
|
||||
|
||||
Create a new order from the items in the cart.
|
||||
|
||||
```http
|
||||
POST /cart/order/
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| :----------------- | :----- | :------: | :-------------------------------------------------------------------------------------- |
|
||||
| `billing_address` | array | No | Billing address data to store to the new order. |
|
||||
| `shipping_address` | array | No | Shipping address data to store to the new order. |
|
||||
| `customer_note` | string | No | Customer note to store to the new order. |
|
||||
| `shipping_rates` | array | No | Array of objects containing `rate_id` of selected shipping methods to add to the order. |
|
||||
|
||||
```http
|
||||
curl --request POST https://example-store.com/wp-json/wc/store/cart/order
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 149,
|
||||
"number": "149",
|
||||
"status": "draft",
|
||||
"order_key": "wc_order_9falc306dOkWb",
|
||||
"created_via": "store-api",
|
||||
"prices_include_tax": true,
|
||||
"events": {
|
||||
"date_created": "2020-01-07T12:33:23",
|
||||
"date_created_gmt": "2020-01-07T12:33:23",
|
||||
"date_modified": "2020-01-07T12:33:23",
|
||||
"date_modified_gmt": "2020-01-07T12:33:23",
|
||||
"date_completed": null,
|
||||
"date_completed_gmt": null,
|
||||
"date_paid": null,
|
||||
"date_paid_gmt": null
|
||||
},
|
||||
"customer": {
|
||||
"customer_id": 1,
|
||||
"customer_ip_address": "192.168.50.1",
|
||||
"customer_user_agent": "insomnia/7.0.5"
|
||||
},
|
||||
"customer_note": "This is a customer note.",
|
||||
"billing_address": {
|
||||
"first_name": "Margaret",
|
||||
"last_name": "Thatchcroft",
|
||||
"company": "",
|
||||
"address_1": "123 South Street",
|
||||
"address_2": "Apt 1",
|
||||
"city": "Philadelphia",
|
||||
"state": "PA",
|
||||
"postcode": "19123",
|
||||
"country": "US",
|
||||
"email": "test@test.com",
|
||||
"phone": ""
|
||||
},
|
||||
"shipping_address": {
|
||||
"first_name": "Margaret",
|
||||
"last_name": "Thatchcroft",
|
||||
"company": "",
|
||||
"address_1": "123 South Street",
|
||||
"address_2": "Apt 1",
|
||||
"city": "Philadelphia",
|
||||
"state": "PA",
|
||||
"postcode": "19123",
|
||||
"country": "US"
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"id": 12,
|
||||
"quantity": 1,
|
||||
"name": "Belt",
|
||||
"sku": "woo-belt",
|
||||
"permalink": "http://local.wordpress.test/product/belt/",
|
||||
"images": [
|
||||
{
|
||||
"id": "41",
|
||||
"src": "http://local.wordpress.test/wp-content/uploads/2019/12/belt-2.jpg",
|
||||
"thumbnail": "http://local.wordpress.test/wp-content/uploads/2019/12/belt-2-300x300.jpg",
|
||||
"srcset": "http://local.wordpress.test/wp-content/uploads/2019/12/belt-2.jpg 801w, http://local.wordpress.test/wp-content/uploads/2019/12/belt-2-300x300.jpg 300w, http://local.wordpress.test/wp-content/uploads/2019/12/belt-2-100x100.jpg 100w, http://local.wordpress.test/wp-content/uploads/2019/12/belt-2-450x450.jpg 450w, http://local.wordpress.test/wp-content/uploads/2019/12/belt-2-150x150.jpg 150w, http://local.wordpress.test/wp-content/uploads/2019/12/belt-2-768x768.jpg 768w",
|
||||
"sizes": "(max-width: 801px) 100vw, 801px",
|
||||
"name": "belt-2.jpg",
|
||||
"alt": ""
|
||||
}
|
||||
],
|
||||
"variation": [],
|
||||
"totals": {
|
||||
"currency_code": "GBP",
|
||||
"currency_symbol": "£",
|
||||
"currency_minor_unit": 2,
|
||||
"currency_decimal_separator": ".",
|
||||
"currency_thousand_separator": ",",
|
||||
"currency_prefix": "£",
|
||||
"currency_suffix": "",
|
||||
"line_subtotal": "4583",
|
||||
"line_subtotal_tax": "917",
|
||||
"line_total": "4583",
|
||||
"line_total_tax": "917"
|
||||
}
|
||||
}
|
||||
],
|
||||
"totals": {
|
||||
"currency_code": "GBP",
|
||||
"currency_symbol": "£",
|
||||
"currency_minor_unit": 2,
|
||||
"currency_decimal_separator": ".",
|
||||
"currency_thousand_separator": ",",
|
||||
"currency_prefix": "£",
|
||||
"currency_suffix": "",
|
||||
"total_items": "4583",
|
||||
"total_items_tax": "917",
|
||||
"total_fees": "0",
|
||||
"total_fees_tax": "0",
|
||||
"total_discount": "0",
|
||||
"total_discount_tax": "0",
|
||||
"total_shipping": "499",
|
||||
"total_shipping_tax": "100",
|
||||
"total_price": "6099",
|
||||
"total_tax": "1017",
|
||||
"tax_lines": [
|
||||
{
|
||||
"name": "Tax",
|
||||
"price": "1017"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Customer API
|
||||
|
||||
### Get data for the current customer
|
||||
|
@ -1029,31 +1160,31 @@ Example response:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": 0,
|
||||
"billing": {
|
||||
"first_name": "Margaret",
|
||||
"last_name": "Thatchcroft",
|
||||
"company": "",
|
||||
"address_1": "123 South Street",
|
||||
"address_2": "Apt 1",
|
||||
"city": "Philadelphia",
|
||||
"state": "PA",
|
||||
"postcode": "19123",
|
||||
"country": "US",
|
||||
"email": "test@test.com",
|
||||
"phone": ""
|
||||
},
|
||||
"shipping": {
|
||||
"first_name": "Margaret",
|
||||
"last_name": "Thatchcroft",
|
||||
"company": "",
|
||||
"address_1": "123 South Street",
|
||||
"address_2": "Apt 1",
|
||||
"city": "Philadelphia",
|
||||
"state": "PA",
|
||||
"postcode": "19123",
|
||||
"country": "US"
|
||||
}
|
||||
"id": 0,
|
||||
"billing_address": {
|
||||
"first_name": "Margaret",
|
||||
"last_name": "Thatchcroft",
|
||||
"company": "",
|
||||
"address_1": "123 South Street",
|
||||
"address_2": "Apt 1",
|
||||
"city": "Philadelphia",
|
||||
"state": "PA",
|
||||
"postcode": "19123",
|
||||
"country": "US",
|
||||
"email": "test@test.com",
|
||||
"phone": ""
|
||||
},
|
||||
"shipping_address": {
|
||||
"first_name": "Margaret",
|
||||
"last_name": "Thatchcroft",
|
||||
"company": "",
|
||||
"address_1": "123 South Street",
|
||||
"address_2": "Apt 1",
|
||||
"city": "Philadelphia",
|
||||
"state": "PA",
|
||||
"postcode": "19123",
|
||||
"country": "US"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -1065,10 +1196,10 @@ Edit current customer data, such as billing and shipping addresses.
|
|||
PUT /cart/customer
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| :--------- | :------ | :------: | :--------------------------------- |
|
||||
| `billing` | object | No | Billing address properties. |
|
||||
| `shipping` | object | No | Shipping address properties. |
|
||||
| Attribute | Type | Required | Description |
|
||||
| :--------- | :----- | :------: | :--------------------------- |
|
||||
| `billing` | object | No | Billing address properties. |
|
||||
| `shipping` | object | No | Shipping address properties. |
|
||||
|
||||
```http
|
||||
curl --request PUT https://example-store.com/wp-json/wc/store/cart/customer?billing[company]=Test
|
||||
|
@ -1078,31 +1209,31 @@ Example response:
|
|||
|
||||
```json
|
||||
{
|
||||
"id": 0,
|
||||
"billing": {
|
||||
"first_name": "Margaret",
|
||||
"last_name": "Thatchcroft",
|
||||
"company": "Test",
|
||||
"address_1": "123 South Street",
|
||||
"address_2": "Apt 1",
|
||||
"city": "Philadelphia",
|
||||
"state": "PA",
|
||||
"postcode": "19123",
|
||||
"country": "US",
|
||||
"email": "test@test.com",
|
||||
"phone": ""
|
||||
},
|
||||
"shipping": {
|
||||
"first_name": "Margaret",
|
||||
"last_name": "Thatchcroft",
|
||||
"company": "",
|
||||
"address_1": "123 South Street",
|
||||
"address_2": "Apt 1",
|
||||
"city": "Philadelphia",
|
||||
"state": "PA",
|
||||
"postcode": "19123",
|
||||
"country": "US"
|
||||
}
|
||||
"id": 0,
|
||||
"billing_address": {
|
||||
"first_name": "Margaret",
|
||||
"last_name": "Thatchcroft",
|
||||
"company": "Test",
|
||||
"address_1": "123 South Street",
|
||||
"address_2": "Apt 1",
|
||||
"city": "Philadelphia",
|
||||
"state": "PA",
|
||||
"postcode": "19123",
|
||||
"country": "US",
|
||||
"email": "test@test.com",
|
||||
"phone": ""
|
||||
},
|
||||
"shipping_address": {
|
||||
"first_name": "Margaret",
|
||||
"last_name": "Thatchcroft",
|
||||
"company": "",
|
||||
"address_1": "123 South Street",
|
||||
"address_2": "Apt 1",
|
||||
"city": "Philadelphia",
|
||||
"state": "PA",
|
||||
"postcode": "19123",
|
||||
"country": "US"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -123,14 +123,14 @@ class CartSchema extends AbstractSchema {
|
|||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'total_tax' => [
|
||||
'description' => __( 'Total tax applied to items and shipping.', 'woo-gutenberg-products-block' ),
|
||||
'total_price' => [
|
||||
'description' => __( 'Total price the customer will pay.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'total_price' => [
|
||||
'description' => __( 'Total price the customer will pay.', 'woo-gutenberg-products-block' ),
|
||||
'total_tax' => [
|
||||
'description' => __( 'Total tax applied to items and shipping.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
|
@ -190,8 +190,8 @@ class CartSchema extends AbstractSchema {
|
|||
'total_discount_tax' => $this->prepare_money_response( $cart->get_discount_tax(), wc_get_price_decimals() ),
|
||||
'total_shipping' => $this->prepare_money_response( $cart->get_shipping_total(), wc_get_price_decimals() ),
|
||||
'total_shipping_tax' => $this->prepare_money_response( $cart->get_shipping_tax(), wc_get_price_decimals() ),
|
||||
'total_tax' => $this->prepare_money_response( $cart->get_total_tax(), wc_get_price_decimals() ),
|
||||
'total_price' => $this->prepare_money_response( $cart->get_total(), wc_get_price_decimals() ),
|
||||
'total_tax' => $this->prepare_money_response( $cart->get_total_tax(), wc_get_price_decimals() ),
|
||||
'tax_lines' => $this->get_tax_lines( $cart ),
|
||||
]
|
||||
),
|
||||
|
|
|
@ -82,7 +82,7 @@ class CartShippingRateSchema extends AbstractSchema {
|
|||
'type' => 'string',
|
||||
],
|
||||
],
|
||||
'shipping-rates' => [
|
||||
'shipping_rates' => [
|
||||
'description' => __( 'List of shipping rates.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'array',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
|
@ -186,7 +186,7 @@ class CartShippingRateSchema extends AbstractSchema {
|
|||
'country' => $package['destination']['country'],
|
||||
],
|
||||
'items' => array_values( wp_list_pluck( $package['contents'], 'key' ) ),
|
||||
'shipping-rates' => array_values( array_map( [ $this, 'get_rate_response' ], $package['rates'] ) ),
|
||||
'shipping_rates' => array_values( array_map( [ $this, 'get_rate_response' ], $package['rates'] ) ),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -29,13 +29,13 @@ class CustomerSchema extends AbstractSchema {
|
|||
*/
|
||||
protected function get_properties() {
|
||||
return [
|
||||
'id' => [
|
||||
'id' => [
|
||||
'description' => __( 'Customer ID. Will return 0 if the customer is logged out.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'integer',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'billing' => [
|
||||
'billing_address' => [
|
||||
'description' => __( 'List of billing address data.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'object',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
|
@ -102,7 +102,7 @@ class CustomerSchema extends AbstractSchema {
|
|||
],
|
||||
],
|
||||
],
|
||||
'shipping' => [
|
||||
'shipping_address' => [
|
||||
'description' => __( 'List of shipping address data.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'object',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
|
@ -165,8 +165,8 @@ class CustomerSchema extends AbstractSchema {
|
|||
*/
|
||||
public function get_item_response( $object ) {
|
||||
return [
|
||||
'id' => $object->get_id(),
|
||||
'billing' => [
|
||||
'id' => $object->get_id(),
|
||||
'billing_address' => [
|
||||
'first_name' => $object->get_billing_first_name(),
|
||||
'last_name' => $object->get_billing_last_name(),
|
||||
'company' => $object->get_billing_company(),
|
||||
|
@ -179,7 +179,7 @@ class CustomerSchema extends AbstractSchema {
|
|||
'email' => $object->get_billing_email(),
|
||||
'phone' => $object->get_billing_phone(),
|
||||
],
|
||||
'shipping' => [
|
||||
'shipping_address' => [
|
||||
'first_name' => $object->get_shipping_first_name(),
|
||||
'last_name' => $object->get_shipping_last_name(),
|
||||
'company' => $object->get_shipping_company(),
|
||||
|
|
|
@ -0,0 +1,247 @@
|
|||
<?php
|
||||
/**
|
||||
* Order Item Schema.
|
||||
*
|
||||
* @package WooCommerce/Blocks
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\RestApi\StoreApi\Schemas;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\RestApi\Utilities\ProductImages;
|
||||
|
||||
/**
|
||||
* OrderItemSchema class.
|
||||
*/
|
||||
class OrderItemSchema extends AbstractSchema {
|
||||
/**
|
||||
* The schema item name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $title = 'order_item';
|
||||
|
||||
/**
|
||||
* Cart schema properties.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_properties() {
|
||||
return [
|
||||
'id' => [
|
||||
'description' => __( 'The item product or variation ID.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'integer',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'required' => true,
|
||||
'arg_options' => [
|
||||
'sanitize_callback' => 'absint',
|
||||
'validate_callback' => [ $this, 'product_id_exists' ],
|
||||
],
|
||||
],
|
||||
'quantity' => [
|
||||
'description' => __( 'Quantity of this item in the cart.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'integer',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'required' => true,
|
||||
'arg_options' => [
|
||||
'sanitize_callback' => 'wc_stock_amount',
|
||||
],
|
||||
],
|
||||
'name' => [
|
||||
'description' => __( 'Product name.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'sku' => [
|
||||
'description' => __( 'Stock keeping unit, if applicable.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'permalink' => [
|
||||
'description' => __( 'Product URL.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'format' => 'uri',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'images' => [
|
||||
'description' => __( 'List of images.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'array',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
'items' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'id' => [
|
||||
'description' => __( 'Image ID.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'integer',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'src' => [
|
||||
'description' => __( 'Image URL.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'format' => 'uri',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'name' => [
|
||||
'description' => __( 'Image name.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'alt' => [
|
||||
'description' => __( 'Image alternative text.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'variation' => [
|
||||
'description' => __( 'Chosen attributes (for variations).', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'array',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'items' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'attribute' => [
|
||||
'description' => __( 'Variation attribute name.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'value' => [
|
||||
'description' => __( 'Variation attribute value.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'totals' => [
|
||||
'description' => __( 'Item 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(),
|
||||
[
|
||||
'line_subtotal' => [
|
||||
'description' => __( 'Line price subtotal (excluding coupons and discounts).', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'line_subtotal_tax' => [
|
||||
'description' => __( 'Line price subtotal tax.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'line_total' => [
|
||||
'description' => __( 'Line price total (including coupons and discounts).', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'line_total_tax' => [
|
||||
'description' => __( 'Line price total tax.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
]
|
||||
),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check given ID exists,
|
||||
*
|
||||
* @param integer $product_id Product ID.
|
||||
* @return bool
|
||||
*/
|
||||
public function product_id_exists( $product_id ) {
|
||||
$post = get_post( (int) $product_id );
|
||||
return $post && in_array( $post->post_type, [ 'product', 'product_variation' ], true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a WooCommerce cart item to an object suitable for the response.
|
||||
*
|
||||
* @todo Variation is stored to meta - how can we gather for response?
|
||||
*
|
||||
* @param \WC_Order_Item_Product $line_item Order line item array.
|
||||
* @return array
|
||||
*/
|
||||
public function get_item_response( \WC_Order_Item_Product $line_item ) {
|
||||
$product = $line_item->get_product();
|
||||
$has_product = $product instanceof \WC_Product;
|
||||
|
||||
return [
|
||||
'id' => $line_item->get_variation_id() ? $line_item->get_variation_id() : $line_item->get_product_id(),
|
||||
'quantity' => $line_item->get_quantity(),
|
||||
'name' => $has_product ? $product->get_title() : null,
|
||||
'sku' => $has_product ? $product->get_sku() : null,
|
||||
'permalink' => $has_product ? $product->get_permalink() : null,
|
||||
'images' => $has_product ? ( new ProductImages() )->images_to_array( $product ) : null,
|
||||
'variation' => $has_product ? $this->format_variation_data( $line_item, $product ) : [],
|
||||
'totals' => array_merge(
|
||||
$this->get_store_currency_response(),
|
||||
[
|
||||
'line_subtotal' => $this->prepare_money_response( $line_item->get_subtotal(), wc_get_price_decimals() ),
|
||||
'line_subtotal_tax' => $this->prepare_money_response( $line_item->get_subtotal_tax(), wc_get_price_decimals() ),
|
||||
'line_total' => $this->prepare_money_response( $line_item->get_total(), wc_get_price_decimals() ),
|
||||
'line_total_tax' => $this->prepare_money_response( $line_item->get_total_tax(), wc_get_price_decimals() ),
|
||||
]
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Format variation data. For line items we get meta data and format it.
|
||||
*
|
||||
* @param \WC_Order_Item_Product $line_item Line item from the order.
|
||||
* @param \WC_Product $product Product data.
|
||||
* @return array
|
||||
*/
|
||||
protected function format_variation_data( \WC_Order_Item_Product $line_item, \WC_Product $product ) {
|
||||
$return = [];
|
||||
$line_item_meta = $line_item->get_meta_data();
|
||||
$attribute_keys = array_keys( $product->get_attributes() );
|
||||
|
||||
foreach ( $line_item_meta as $meta ) {
|
||||
$key = $meta->key;
|
||||
$value = $meta->value;
|
||||
|
||||
if ( ! in_array( $key, $attribute_keys, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$taxonomy = wc_attribute_taxonomy_name( str_replace( 'pa_', '', urldecode( $key ) ) );
|
||||
|
||||
if ( taxonomy_exists( $taxonomy ) ) {
|
||||
// If this is a term slug, get the term's nice name.
|
||||
$term = get_term_by( 'slug', $value, $taxonomy );
|
||||
if ( ! is_wp_error( $term ) && $term && $term->name ) {
|
||||
$value = $term->name;
|
||||
}
|
||||
$label = wc_attribute_label( $taxonomy );
|
||||
} else {
|
||||
// If this is a custom option slug, get the options name.
|
||||
$value = apply_filters( 'woocommerce_variation_option_name', $value, null, $taxonomy, $product );
|
||||
$label = wc_attribute_label( $name, $product );
|
||||
}
|
||||
|
||||
$return[ $label ] = $value;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,625 @@
|
|||
<?php
|
||||
/**
|
||||
* Order schema for the Store API.
|
||||
*
|
||||
* Note, only fields customers can edit via checkout are editable. Everything else is either readonly or hidden.
|
||||
*
|
||||
* @package WooCommerce/Blocks
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\RestApi\StoreApi\Schemas;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* OrderSchema class.
|
||||
*/
|
||||
class OrderSchema extends AbstractSchema {
|
||||
/**
|
||||
* The schema item name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $title = 'order';
|
||||
|
||||
/**
|
||||
* Order schema properties.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_properties() {
|
||||
return [
|
||||
'id' => [
|
||||
'description' => __( 'Unique identifier for the resource.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'integer',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'number' => [
|
||||
'description' => __( 'Generated order number which may differ from the Order ID.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'status' => [
|
||||
'description' => __( 'Order status. Payment providers will update this value after payment.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'order_key' => [
|
||||
'description' => __( 'Order key used to check validity or protect access to certain order data.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'created_via' => [
|
||||
'description' => __( 'Shows where the order was created.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'prices_include_tax' => [
|
||||
'description' => __( 'True if the prices included tax when the order was created.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'boolean',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'events' => [
|
||||
'description' => __( 'List of events and dates such as creation date.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'object',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
'properties' => [
|
||||
'date_created' => [
|
||||
'description' => __( "The date the order was created, in the site's timezone.", 'woo-gutenberg-products-block' ),
|
||||
'type' => 'date-time',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'date_created_gmt' => [
|
||||
'description' => __( 'The date the order was created, as GMT.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'date-time',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'date_modified' => [
|
||||
'description' => __( "The date the order was last modified, in the site's timezone.", 'woo-gutenberg-products-block' ),
|
||||
'type' => 'date-time',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'date_modified_gmt' => [
|
||||
'description' => __( 'The date the order was last modified, as GMT.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'date-time',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'date_paid' => [
|
||||
'description' => __( "The date the order was paid, in the site's timezone.", 'woo-gutenberg-products-block' ),
|
||||
'type' => 'date-time',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'date_paid_gmt' => [
|
||||
'description' => __( 'The date the order was paid, as GMT.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'date-time',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'date_completed' => [
|
||||
'description' => __( "The date the order was completed, in the site's timezone.", 'woo-gutenberg-products-block' ),
|
||||
'type' => 'date-time',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'date_completed_gmt' => [
|
||||
'description' => __( 'The date the order was completed, as GMT.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'date-time',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
'customer' => [
|
||||
'description' => __( 'Information about the customer that placed the order.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'object',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
'properties' => [
|
||||
'customer_id' => [
|
||||
'description' => __( 'Customer ID if registered. Will return 0 for guest orders.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'integer',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'customer_ip_address' => [
|
||||
'description' => __( 'Customer IP address.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'customer_user_agent' => [
|
||||
'description' => __( 'Customer web browser identifier.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
'customer_note' => [
|
||||
'description' => __( 'Note added to the order by the customer during checkout.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'billing_address' => [
|
||||
'description' => __( 'Billing address.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'object',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'properties' => [
|
||||
'first_name' => [
|
||||
'description' => __( 'First name.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'last_name' => [
|
||||
'description' => __( 'Last name.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'company' => [
|
||||
'description' => __( 'Company name.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'address_1' => [
|
||||
'description' => __( 'Address line 1', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'address_2' => [
|
||||
'description' => __( 'Address line 2', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'city' => [
|
||||
'description' => __( 'City name.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'state' => [
|
||||
'description' => __( 'ISO code or name of the state, province or district.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'postcode' => [
|
||||
'description' => __( 'Postal code.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'country' => [
|
||||
'description' => __( 'Country code in ISO 3166-1 alpha-2 format.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'email' => [
|
||||
'description' => __( 'Email address.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'format' => 'email',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'phone' => [
|
||||
'description' => __( 'Phone number.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
],
|
||||
],
|
||||
'shipping_address' => [
|
||||
'description' => __( 'Shipping address.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'object',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'properties' => [
|
||||
'first_name' => [
|
||||
'description' => __( 'First name.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'last_name' => [
|
||||
'description' => __( 'Last name.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'company' => [
|
||||
'description' => __( 'Company name.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'address_1' => [
|
||||
'description' => __( 'Address line 1', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'address_2' => [
|
||||
'description' => __( 'Address line 2', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'city' => [
|
||||
'description' => __( 'City name.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'state' => [
|
||||
'description' => __( 'ISO code or name of the state, province or district.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'postcode' => [
|
||||
'description' => __( 'Postal code.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'country' => [
|
||||
'description' => __( 'Country code in ISO 3166-1 alpha-2 format.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
],
|
||||
],
|
||||
'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',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
'items' => [
|
||||
'type' => 'object',
|
||||
'properties' => $this->force_schema_readonly( ( new OrderItemSchema() )->get_properties() ),
|
||||
],
|
||||
],
|
||||
'shipping_lines' => [
|
||||
'description' => __( 'Shipping lines data.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'array',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
'items' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'id' => [
|
||||
'description' => __( 'Item ID.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'integer',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'method_title' => [
|
||||
'description' => __( 'Shipping method name.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'mixed',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'method_id' => [
|
||||
'description' => __( 'Shipping method ID.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'mixed',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'instance_id' => [
|
||||
'description' => __( 'Shipping instance ID.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'total' => [
|
||||
'description' => __( 'Line total (after discounts).', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'total_tax' => [
|
||||
'description' => __( 'Line total tax (after discounts).', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'taxes' => [
|
||||
'description' => __( 'Line taxes.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'array',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
'items' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'id' => [
|
||||
'description' => __( 'Tax rate ID.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'integer',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'total' => [
|
||||
'description' => __( 'Tax total.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'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_items' => [
|
||||
'description' => __( 'Total price of items in the order.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'total_items_tax' => [
|
||||
'description' => __( 'Total tax on items in the order.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'total_fees' => [
|
||||
'description' => __( 'Total price of any applied fees.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'total_fees_tax' => [
|
||||
'description' => __( 'Total tax on fees.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'total_discount' => [
|
||||
'description' => __( 'Total discount from applied coupons.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'total_discount_tax' => [
|
||||
'description' => __( 'Total tax removed due to discount from applied coupons.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'total_shipping' => [
|
||||
'description' => __( 'Total price of shipping.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'total_shipping_tax' => [
|
||||
'description' => __( 'Total tax on shipping.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'total_price' => [
|
||||
'description' => __( 'Total price the customer will pay.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'total_tax' => [
|
||||
'description' => __( 'Total tax applied to items and shipping.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'tax_lines' => [
|
||||
'description' => __( 'Lines of taxes applied to items and shipping.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'array',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
'items' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'name' => [
|
||||
'description' => __( 'The name of the tax.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'price' => [
|
||||
'description' => __( 'The amount of tax charged.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a woo order into an object suitable for the response.
|
||||
*
|
||||
* @param \WC_Order $order Order class instance.
|
||||
* @return array
|
||||
*/
|
||||
public function get_item_response( \WC_Order $order ) {
|
||||
$order_item_schema = new OrderItemSchema();
|
||||
|
||||
return [
|
||||
'id' => $order->get_id(),
|
||||
'number' => $order->get_order_number(),
|
||||
'status' => $order->get_status(),
|
||||
'order_key' => $order->get_order_key(),
|
||||
'created_via' => $order->get_created_via(),
|
||||
'prices_include_tax' => $order->get_prices_include_tax(),
|
||||
'events' => $this->get_events( $order ),
|
||||
'customer' => [
|
||||
'customer_id' => $order->get_customer_id(),
|
||||
'customer_ip_address' => $order->get_customer_ip_address(),
|
||||
'customer_user_agent' => $order->get_customer_user_agent(),
|
||||
],
|
||||
'customer_note' => $order->get_customer_note(),
|
||||
'billing_address' => [
|
||||
'first_name' => $order->get_billing_first_name(),
|
||||
'last_name' => $order->get_billing_last_name(),
|
||||
'company' => $order->get_billing_company(),
|
||||
'address_1' => $order->get_billing_address_1(),
|
||||
'address_2' => $order->get_billing_address_2(),
|
||||
'city' => $order->get_billing_city(),
|
||||
'state' => $order->get_billing_state(),
|
||||
'postcode' => $order->get_billing_postcode(),
|
||||
'country' => $order->get_billing_country(),
|
||||
'email' => $order->get_billing_email(),
|
||||
'phone' => $order->get_billing_phone(),
|
||||
],
|
||||
'shipping_address' => [
|
||||
'first_name' => $order->get_shipping_first_name(),
|
||||
'last_name' => $order->get_shipping_last_name(),
|
||||
'company' => $order->get_shipping_company(),
|
||||
'address_1' => $order->get_shipping_address_1(),
|
||||
'address_2' => $order->get_shipping_address_2(),
|
||||
'city' => $order->get_shipping_city(),
|
||||
'state' => $order->get_shipping_state(),
|
||||
'postcode' => $order->get_shipping_postcode(),
|
||||
'country' => $order->get_shipping_country(),
|
||||
],
|
||||
'items' => array_values( array_map( [ $order_item_schema, 'get_item_response' ], $order->get_items( 'line_item' ) ) ),
|
||||
'totals' => array_merge(
|
||||
$this->get_store_currency_response(),
|
||||
[
|
||||
'total_items' => $this->prepare_money_response( $order->get_subtotal(), wc_get_price_decimals() ),
|
||||
'total_items_tax' => $this->prepare_money_response( $this->get_subtotal_tax( $order ), wc_get_price_decimals() ),
|
||||
'total_fees' => $this->prepare_money_response( $this->get_fee_total( $order ), wc_get_price_decimals() ),
|
||||
'total_fees_tax' => $this->prepare_money_response( $this->get_fee_tax( $order ), wc_get_price_decimals() ),
|
||||
'total_discount' => $this->prepare_money_response( $order->get_discount_total(), wc_get_price_decimals() ),
|
||||
'total_discount_tax' => $this->prepare_money_response( $order->get_discount_tax(), wc_get_price_decimals() ),
|
||||
'total_shipping' => $this->prepare_money_response( $order->get_shipping_total(), wc_get_price_decimals() ),
|
||||
'total_shipping_tax' => $this->prepare_money_response( $order->get_shipping_tax(), wc_get_price_decimals() ),
|
||||
'total_price' => $this->prepare_money_response( $order->get_total(), wc_get_price_decimals() ),
|
||||
'total_tax' => $this->prepare_money_response( $order->get_total_tax(), wc_get_price_decimals() ),
|
||||
'tax_lines' => $this->get_tax_lines( $order ),
|
||||
]
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the wc- prefix from order statuses.
|
||||
*
|
||||
* @param string $status Status from the order.
|
||||
* @return string
|
||||
*/
|
||||
protected function remove_status_prefix( $status ) {
|
||||
return 'wc-' === substr( $status, 0, 3 ) ? substr( $status, 3 ) : $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event dates from an order, formatting both local and GMT values.
|
||||
*
|
||||
* @param \WC_Order $order Order class instance.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_events( \WC_Order $order ) {
|
||||
$events = [];
|
||||
$props = [ 'date_created', 'date_modified', 'date_completed', 'date_paid' ];
|
||||
|
||||
foreach ( $props as $prop ) {
|
||||
$datetime = $order->{"get_$prop"}();
|
||||
$events[ $prop ] = wc_rest_prepare_date_response( $datetime, false );
|
||||
$events[ $prop . '_gmt' ] = wc_rest_prepare_date_response( $datetime );
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tax lines from the order and format to match schema.
|
||||
*
|
||||
* @param \WC_Order $order Order class instance.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_tax_lines( \WC_Order $order ) {
|
||||
$tax_totals = $order->get_tax_totals();
|
||||
$tax_lines = [];
|
||||
|
||||
foreach ( $tax_totals as $tax_total ) {
|
||||
$tax_lines[] = array(
|
||||
'name' => $tax_total->label,
|
||||
'price' => $this->prepare_money_response( $tax_total->amount, wc_get_price_decimals() ),
|
||||
);
|
||||
}
|
||||
|
||||
return $tax_lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total amount of tax for line items.
|
||||
*
|
||||
* Needed because orders do not hold this total like carts.
|
||||
*
|
||||
* @todo In the future this could be added to the core WC_Order class to better match the WC_Cart class.
|
||||
*
|
||||
* @param \WC_Order $order Order class instance.
|
||||
* @return float
|
||||
*/
|
||||
protected function get_subtotal_tax( \WC_Order $order ) {
|
||||
$total = 0;
|
||||
foreach ( $order->get_items() as $item ) {
|
||||
$total += $item->get_subtotal_tax();
|
||||
}
|
||||
return $total;
|
||||
}
|
||||
/**
|
||||
* Get the total amount of fees.
|
||||
*
|
||||
* Needed because orders do not hold this total like carts.
|
||||
*
|
||||
* @todo In the future this could be added to the core WC_Order class to better match the WC_Cart class.
|
||||
*
|
||||
* @param \WC_Order $order Order class instance.
|
||||
* @return float
|
||||
*/
|
||||
protected function get_fee_total( \WC_Order $order ) {
|
||||
$total = 0;
|
||||
foreach ( $order->get_fees() as $item ) {
|
||||
$total += $item->get_total();
|
||||
}
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total tax of fees.
|
||||
*
|
||||
* Needed because orders do not hold this total like carts.
|
||||
*
|
||||
* @todo In the future this could be added to the core WC_Order class to better match the WC_Cart class.
|
||||
*
|
||||
* @param \WC_Order $order Order class instance.
|
||||
* @return float
|
||||
*/
|
||||
protected function get_fee_tax( \WC_Order $order ) {
|
||||
$total = 0;
|
||||
foreach ( $order->get_fees() as $item ) {
|
||||
$total += $item->get_total_tax();
|
||||
}
|
||||
return $total;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
/**
|
||||
* Helper class to handle product stock reservation.
|
||||
*
|
||||
* @package WooCommerce/Blocks
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\RestApi\StoreApi\Utilities;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use \WP_Error;
|
||||
|
||||
/**
|
||||
* Stock Reservation class.
|
||||
*/
|
||||
class ReserveStock {
|
||||
/**
|
||||
* Put a temporary hold on stock for an order if enough is available.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public function reserve_stock_for_order( \WC_Order $order ) {
|
||||
$stock_to_reserve = [];
|
||||
$items = array_filter(
|
||||
$order->get_items(),
|
||||
function( $item ) {
|
||||
return $item->is_type( 'line_item' ) && $item->get_product() instanceof \WC_Product;
|
||||
}
|
||||
);
|
||||
|
||||
foreach ( $items as $item ) {
|
||||
$product = $item->get_product();
|
||||
|
||||
if ( ! $product->is_in_stock() ) {
|
||||
return new WP_Error(
|
||||
'product_out_of_stock',
|
||||
sprintf(
|
||||
/* translators: %s: product name */
|
||||
__( '%s is out of stock and cannot be purchased.', 'woo-gutenberg-products-block' ),
|
||||
$product->get_name()
|
||||
),
|
||||
[ 'status' => 403 ]
|
||||
);
|
||||
}
|
||||
|
||||
// If stock management is off, no need to reserve any stock here.
|
||||
if ( ! $product->managing_stock() || $product->backorders_allowed() ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$product_id = $product->get_stock_managed_by_id();
|
||||
$stock_to_reserve[ $product_id ] = isset( $stock_to_reserve[ $product_id ] ) ? $stock_to_reserve[ $product_id ] : 0;
|
||||
$reserved_stock = $this->get_reserved_stock( $product, $order->get_id() );
|
||||
|
||||
if ( ( $product->get_stock_quantity() - $reserved_stock - $stock_to_reserve[ $product_id ] ) < $item->get_quantity() ) {
|
||||
return new WP_Error(
|
||||
'product_not_enough_stock',
|
||||
sprintf(
|
||||
/* translators: %s: product name */
|
||||
__( 'Not enough units of %s are available in stock to fulfil this order.', 'woo-gutenberg-products-block' ),
|
||||
$product->get_name()
|
||||
),
|
||||
[ 'status' => 403 ]
|
||||
);
|
||||
}
|
||||
|
||||
// Queue for later DB insertion.
|
||||
$stock_to_reserve[ $product_id ] += $item->get_quantity();
|
||||
}
|
||||
|
||||
$this->reserve_stock( $stock_to_reserve, $order->get_id() );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reserve stock by inserting rows into the DB.
|
||||
*
|
||||
* @param array $stock_to_reserve Array of Product ID => Qty pairs.
|
||||
* @param integer $order_id Order ID for which to reserve stock.
|
||||
*/
|
||||
protected function reserve_stock( $stock_to_reserve, $order_id ) {
|
||||
global $wpdb;
|
||||
|
||||
$stock_to_reserve = array_filter( $stock_to_reserve );
|
||||
|
||||
if ( ! $stock_to_reserve ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$stock_to_reserve_rows = [];
|
||||
|
||||
foreach ( $stock_to_reserve as $product_id => $stock_quantity ) {
|
||||
$stock_to_reserve_rows[] = '(' . esc_sql( $order_id ) . ',"' . esc_sql( $product_id ) . '","' . esc_sql( $stock_quantity ) . '")';
|
||||
}
|
||||
|
||||
$values = implode( ',', $stock_to_reserve_rows );
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
|
||||
$wpdb->query( "REPLACE INTO {$wpdb->wc_reserved_stock} ( order_id, product_id, stock_quantity ) VALUES {$values};" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Query for any existing holds on stock for this item.
|
||||
*
|
||||
* - Can ignore reserved stock for a specific order.
|
||||
* - Ignores stock for orders which are no longer drafts (assuming real stock reduction was performed).
|
||||
* - Ignores stock reserved over 10 mins ago.
|
||||
*
|
||||
* @param \WC_Product $product Product to get reserved stock for.
|
||||
* @param integer $exclude_order_id Optional order to exclude from the results.
|
||||
* @return integer Amount of stock already reserved.
|
||||
*/
|
||||
public function get_reserved_stock( \WC_Product $product, $exclude_order_id = 0 ) {
|
||||
global $wpdb;
|
||||
|
||||
$reserved_stock = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"
|
||||
SELECT SUM( stock_table.`stock_quantity` ) FROM $wpdb->wc_reserved_stock stock_table
|
||||
LEFT JOIN $wpdb->posts posts ON stock_table.`order_id` = posts.ID
|
||||
WHERE stock_table.`product_id` = %d
|
||||
AND posts.post_status = 'wc-checkout-draft'
|
||||
AND stock_table.`order_id` != %d
|
||||
AND stock_table.`timestamp` > ( NOW() - INTERVAL 10 MINUTE )
|
||||
",
|
||||
$product->get_stock_managed_by_id(),
|
||||
$exclude_order_id
|
||||
)
|
||||
);
|
||||
|
||||
// Deals with legacy stock reservation which the core Woo checkout performs.
|
||||
$hold_stock_minutes = (int) get_option( 'woocommerce_hold_stock_minutes', 0 );
|
||||
$reserved_stock += ( $hold_stock_minutes > 0 ) ? wc_get_held_stock_quantity( $product ) : 0;
|
||||
|
||||
return $reserved_stock;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
<?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_Order as OrderHelper;
|
||||
|
||||
/**
|
||||
* Cart Order Controller Tests.
|
||||
*/
|
||||
class CartOrder extends TestCase {
|
||||
/**
|
||||
* Setup test data. Called before every test.
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
wp_set_current_user( 0 );
|
||||
|
||||
$this->products = [];
|
||||
|
||||
// Create some test products.
|
||||
$this->products[0] = ProductHelper::create_simple_product( false );
|
||||
$this->products[0]->set_weight( 10 );
|
||||
$this->products[0]->set_regular_price( 10 );
|
||||
$this->products[0]->save();
|
||||
|
||||
$this->products[1] = ProductHelper::create_simple_product( false );
|
||||
$this->products[1]->set_weight( 10 );
|
||||
$this->products[1]->set_regular_price( 10 );
|
||||
$this->products[1]->save();
|
||||
|
||||
wc_empty_cart();
|
||||
|
||||
$this->keys = [];
|
||||
$this->keys[] = wc()->cart->add_to_cart( $this->products[0]->get_id(), 2 );
|
||||
$this->keys[] = wc()->cart->add_to_cart( $this->products[1]->get_id(), 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test route registration.
|
||||
*/
|
||||
public function test_register_routes() {
|
||||
$routes = $this->server->get_routes();
|
||||
$this->assertArrayHasKey( '/wc/store/cart/order', $routes );
|
||||
|
||||
$request = new WP_REST_Request( 'GET', '/wc/store/cart/order' );
|
||||
$response = $this->server->dispatch( $request );
|
||||
$this->assertEquals( 404, $response->get_status() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test order creation from cart data.
|
||||
*/
|
||||
public function test_create_item() {
|
||||
$request = new WP_REST_Request( 'POST', '/wc/store/cart/order' );
|
||||
$request->set_param(
|
||||
'billing_address',
|
||||
[
|
||||
'first_name' => 'Margaret',
|
||||
'last_name' => 'Thatchcroft',
|
||||
'address_1' => '123 South Street',
|
||||
'address_2' => 'Apt 1',
|
||||
'city' => 'Philadelphia',
|
||||
'state' => 'PA',
|
||||
'postcode' => '19123',
|
||||
'country' => 'US',
|
||||
'email' => 'test@test.com',
|
||||
'phone' => '',
|
||||
]
|
||||
);
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
$this->assertEquals( 201, $response->get_status() );
|
||||
|
||||
$this->assertArrayHasKey( 'id', $data );
|
||||
$this->assertArrayHasKey( 'number', $data );
|
||||
$this->assertArrayHasKey( 'status', $data );
|
||||
$this->assertArrayHasKey( 'order_key', $data );
|
||||
$this->assertArrayHasKey( 'created_via', $data );
|
||||
$this->assertArrayHasKey( 'prices_include_tax', $data );
|
||||
$this->assertArrayHasKey( 'events', $data );
|
||||
$this->assertArrayHasKey( 'customer', $data );
|
||||
$this->assertArrayHasKey( 'billing_address', $data );
|
||||
$this->assertArrayHasKey( 'shipping_address', $data );
|
||||
$this->assertArrayHasKey( 'customer_note', $data );
|
||||
$this->assertArrayHasKey( 'items', $data );
|
||||
$this->assertArrayHasKey( 'totals', $data );
|
||||
|
||||
$this->assertEquals( 'Margaret', $data['billing_address']['first_name'] );
|
||||
$this->assertEquals( 'Thatchcroft', $data['billing_address']['last_name'] );
|
||||
$this->assertEquals( '123 South Street', $data['billing_address']['address_1'] );
|
||||
$this->assertEquals( 'Apt 1', $data['billing_address']['address_2'] );
|
||||
$this->assertEquals( 'Philadelphia', $data['billing_address']['city'] );
|
||||
$this->assertEquals( 'PA', $data['billing_address']['state'] );
|
||||
$this->assertEquals( '19123', $data['billing_address']['postcode'] );
|
||||
$this->assertEquals( 'US', $data['billing_address']['country'] );
|
||||
$this->assertEquals( 'test@test.com', $data['billing_address']['email'] );
|
||||
$this->assertEquals( '', $data['billing_address']['phone'] );
|
||||
|
||||
$this->assertEquals( 'checkout-draft', $data['status'] );
|
||||
$this->assertEquals( 2, count( $data['items'] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test schema retrieval.
|
||||
*/
|
||||
public function test_get_item_schema() {
|
||||
$controller = new \Automattic\WooCommerce\Blocks\RestApi\StoreApi\Controllers\CartOrder();
|
||||
$schema = $controller->get_item_schema();
|
||||
|
||||
$this->assertArrayHasKey( 'id', $schema['properties'] );
|
||||
$this->assertArrayHasKey( 'number', $schema['properties'] );
|
||||
$this->assertArrayHasKey( 'status', $schema['properties'] );
|
||||
$this->assertArrayHasKey( 'order_key', $schema['properties'] );
|
||||
$this->assertArrayHasKey( 'created_via', $schema['properties'] );
|
||||
$this->assertArrayHasKey( 'prices_include_tax', $schema['properties'] );
|
||||
$this->assertArrayHasKey( 'events', $schema['properties'] );
|
||||
$this->assertArrayHasKey( 'customer', $schema['properties'] );
|
||||
$this->assertArrayHasKey( 'billing_address', $schema['properties'] );
|
||||
$this->assertArrayHasKey( 'shipping_address', $schema['properties'] );
|
||||
$this->assertArrayHasKey( 'customer_note', $schema['properties'] );
|
||||
$this->assertArrayHasKey( 'items', $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\CartOrder();
|
||||
$order = OrderHelper::create_order();
|
||||
$response = $controller->prepare_item_for_response( $order, [] );
|
||||
|
||||
$this->assertArrayHasKey( 'id', $response->get_data() );
|
||||
$this->assertArrayHasKey( 'number', $response->get_data() );
|
||||
$this->assertArrayHasKey( 'status', $response->get_data() );
|
||||
$this->assertArrayHasKey( 'order_key', $response->get_data() );
|
||||
$this->assertArrayHasKey( 'created_via', $response->get_data() );
|
||||
$this->assertArrayHasKey( 'prices_include_tax', $response->get_data() );
|
||||
$this->assertArrayHasKey( 'events', $response->get_data() );
|
||||
$this->assertArrayHasKey( 'customer', $response->get_data() );
|
||||
$this->assertArrayHasKey( 'billing_address', $response->get_data() );
|
||||
$this->assertArrayHasKey( 'shipping_address', $response->get_data() );
|
||||
$this->assertArrayHasKey( 'customer_note', $response->get_data() );
|
||||
$this->assertArrayHasKey( 'items', $response->get_data() );
|
||||
$this->assertArrayHasKey( 'totals', $response->get_data() );
|
||||
}
|
||||
}
|
|
@ -59,7 +59,7 @@ class CartShippingRates extends TestCase {
|
|||
|
||||
$this->assertArrayHasKey( 'destination', $data[0] );
|
||||
$this->assertArrayHasKey( 'items', $data[0] );
|
||||
$this->assertArrayHasKey( 'shipping-rates', $data[0] );
|
||||
$this->assertArrayHasKey( 'shipping_rates', $data[0] );
|
||||
|
||||
$this->assertEquals( null, $data[0]['destination']['address_1'] );
|
||||
$this->assertEquals( null, $data[0]['destination']['address_2'] );
|
||||
|
@ -73,7 +73,7 @@ class CartShippingRates extends TestCase {
|
|||
* Test getting shipping.
|
||||
*/
|
||||
public function test_get_items_missing_address() {
|
||||
$request = new WP_REST_Request( 'GET', '/wc/store/cart/shipping-rates' );
|
||||
$request = new WP_REST_Request( 'GET', '/wc/store/cart/shipping-rates' );
|
||||
$response = $this->server->dispatch( $request );
|
||||
$this->assertEquals( 400, $response->get_status() );
|
||||
}
|
||||
|
@ -131,15 +131,15 @@ class CartShippingRates extends TestCase {
|
|||
|
||||
$this->assertArrayHasKey( 'destination', $schema['properties'] );
|
||||
$this->assertArrayHasKey( 'items', $schema['properties'] );
|
||||
$this->assertArrayHasKey( 'shipping-rates', $schema['properties'] );
|
||||
$this->assertArrayHasKey( 'name', $schema['properties']['shipping-rates']['items']['properties'] );
|
||||
$this->assertArrayHasKey( 'description', $schema['properties']['shipping-rates']['items']['properties'] );
|
||||
$this->assertArrayHasKey( 'delivery_time', $schema['properties']['shipping-rates']['items']['properties'] );
|
||||
$this->assertArrayHasKey( 'price', $schema['properties']['shipping-rates']['items']['properties'] );
|
||||
$this->assertArrayHasKey( 'rate_id', $schema['properties']['shipping-rates']['items']['properties'] );
|
||||
$this->assertArrayHasKey( 'instance_id', $schema['properties']['shipping-rates']['items']['properties'] );
|
||||
$this->assertArrayHasKey( 'method_id', $schema['properties']['shipping-rates']['items']['properties'] );
|
||||
$this->assertArrayHasKey( 'meta_data', $schema['properties']['shipping-rates']['items']['properties'] );
|
||||
$this->assertArrayHasKey( 'shipping_rates', $schema['properties'] );
|
||||
$this->assertArrayHasKey( 'name', $schema['properties']['shipping_rates']['items']['properties'] );
|
||||
$this->assertArrayHasKey( 'description', $schema['properties']['shipping_rates']['items']['properties'] );
|
||||
$this->assertArrayHasKey( 'delivery_time', $schema['properties']['shipping_rates']['items']['properties'] );
|
||||
$this->assertArrayHasKey( 'price', $schema['properties']['shipping_rates']['items']['properties'] );
|
||||
$this->assertArrayHasKey( 'rate_id', $schema['properties']['shipping_rates']['items']['properties'] );
|
||||
$this->assertArrayHasKey( 'instance_id', $schema['properties']['shipping_rates']['items']['properties'] );
|
||||
$this->assertArrayHasKey( 'method_id', $schema['properties']['shipping_rates']['items']['properties'] );
|
||||
$this->assertArrayHasKey( 'meta_data', $schema['properties']['shipping_rates']['items']['properties'] );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -152,7 +152,7 @@ class CartShippingRates extends TestCase {
|
|||
|
||||
$this->assertArrayHasKey( 'destination', $response->get_data() );
|
||||
$this->assertArrayHasKey( 'items', $response->get_data() );
|
||||
$this->assertArrayHasKey( 'shipping-rates', $response->get_data() );
|
||||
$this->assertArrayHasKey( 'shipping_rates', $response->get_data() );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -32,8 +32,8 @@ class Customer extends TestCase {
|
|||
|
||||
$this->assertEquals( 200, $response->get_status() );
|
||||
$this->assertArrayHasKey( 'id', $data );
|
||||
$this->assertArrayHasKey( 'billing', $data );
|
||||
$this->assertArrayHasKey( 'shipping', $data );
|
||||
$this->assertArrayHasKey( 'billing_address', $data );
|
||||
$this->assertArrayHasKey( 'shipping_address', $data );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -43,7 +43,7 @@ class Customer extends TestCase {
|
|||
$request = new WP_REST_Request( 'POST', '/wc/store/customer' );
|
||||
$request->set_body_params(
|
||||
[
|
||||
'billing' => [
|
||||
'billing_address' => [
|
||||
'address_1' => '123 South Street',
|
||||
'address_2' => 'Apt 1',
|
||||
'city' => 'Philadelphia',
|
||||
|
@ -57,18 +57,18 @@ class Customer extends TestCase {
|
|||
$data = $response->get_data();
|
||||
|
||||
$this->assertEquals( 200, $response->get_status() );
|
||||
$this->assertEquals( '123 South Street', $data['billing']['address_1'] );
|
||||
$this->assertEquals( 'Apt 1', $data['billing']['address_2'] );
|
||||
$this->assertEquals( 'Philadelphia', $data['billing']['city'] );
|
||||
$this->assertEquals( 'PA', $data['billing']['state'] );
|
||||
$this->assertEquals( '19123', $data['billing']['postcode'] );
|
||||
$this->assertEquals( 'US', $data['billing']['country'] );
|
||||
$this->assertEquals( '123 South Street', $data['billing_address']['address_1'] );
|
||||
$this->assertEquals( 'Apt 1', $data['billing_address']['address_2'] );
|
||||
$this->assertEquals( 'Philadelphia', $data['billing_address']['city'] );
|
||||
$this->assertEquals( 'PA', $data['billing_address']['state'] );
|
||||
$this->assertEquals( '19123', $data['billing_address']['postcode'] );
|
||||
$this->assertEquals( 'US', $data['billing_address']['country'] );
|
||||
|
||||
// Invalid email.
|
||||
$request = new WP_REST_Request( 'POST', '/wc/store/customer' );
|
||||
$request->set_body_params(
|
||||
[
|
||||
'billing' => [
|
||||
'billing_address' => [
|
||||
'email' => 'not-an-email',
|
||||
],
|
||||
]
|
||||
|
@ -87,8 +87,8 @@ class Customer extends TestCase {
|
|||
$schema = $controller->get_item_schema();
|
||||
|
||||
$this->assertArrayHasKey( 'id', $schema['properties'] );
|
||||
$this->assertArrayHasKey( 'billing', $schema['properties'] );
|
||||
$this->assertArrayHasKey( 'shipping', $schema['properties'] );
|
||||
$this->assertArrayHasKey( 'billing_address', $schema['properties'] );
|
||||
$this->assertArrayHasKey( 'shipping_address', $schema['properties'] );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -121,22 +121,22 @@ class Customer extends TestCase {
|
|||
$response = $controller->prepare_item_for_response( $customer, [] );
|
||||
$data = $response->get_data();
|
||||
|
||||
$this->assertEquals( 'Name', $data['billing']['first_name'] );
|
||||
$this->assertEquals( 'Surname', $data['billing']['last_name'] );
|
||||
$this->assertEquals( '123 South Street', $data['billing']['address_1'] );
|
||||
$this->assertEquals( 'Apt 1', $data['billing']['address_2'] );
|
||||
$this->assertEquals( 'Philadelphia', $data['billing']['city'] );
|
||||
$this->assertEquals( 'PA', $data['billing']['state'] );
|
||||
$this->assertEquals( '19123', $data['billing']['postcode'] );
|
||||
$this->assertEquals( 'US', $data['billing']['country'] );
|
||||
$this->assertEquals( 'Name', $data['billing_address']['first_name'] );
|
||||
$this->assertEquals( 'Surname', $data['billing_address']['last_name'] );
|
||||
$this->assertEquals( '123 South Street', $data['billing_address']['address_1'] );
|
||||
$this->assertEquals( 'Apt 1', $data['billing_address']['address_2'] );
|
||||
$this->assertEquals( 'Philadelphia', $data['billing_address']['city'] );
|
||||
$this->assertEquals( 'PA', $data['billing_address']['state'] );
|
||||
$this->assertEquals( '19123', $data['billing_address']['postcode'] );
|
||||
$this->assertEquals( 'US', $data['billing_address']['country'] );
|
||||
|
||||
$this->assertEquals( 'Name', $data['shipping']['first_name'] );
|
||||
$this->assertEquals( 'Surname', $data['shipping']['last_name'] );
|
||||
$this->assertEquals( '123 South Street', $data['shipping']['address_1'] );
|
||||
$this->assertEquals( 'Apt 1', $data['shipping']['address_2'] );
|
||||
$this->assertEquals( 'Philadelphia', $data['shipping']['city'] );
|
||||
$this->assertEquals( 'PA', $data['shipping']['state'] );
|
||||
$this->assertEquals( '19123', $data['shipping']['postcode'] );
|
||||
$this->assertEquals( 'US', $data['shipping']['country'] );
|
||||
$this->assertEquals( 'Name', $data['shipping_address']['first_name'] );
|
||||
$this->assertEquals( 'Surname', $data['shipping_address']['last_name'] );
|
||||
$this->assertEquals( '123 South Street', $data['shipping_address']['address_1'] );
|
||||
$this->assertEquals( 'Apt 1', $data['shipping_address']['address_2'] );
|
||||
$this->assertEquals( 'Philadelphia', $data['shipping_address']['city'] );
|
||||
$this->assertEquals( 'PA', $data['shipping_address']['state'] );
|
||||
$this->assertEquals( '19123', $data['shipping_address']['postcode'] );
|
||||
$this->assertEquals( 'US', $data['shipping_address']['country'] );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
/**
|
||||
* Utility Tests.
|
||||
*
|
||||
* @package WooCommerce\Blocks\Tests
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\Tests\RestApi\StoreApi\Utilities;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use \WC_Helper_Order as OrderHelper;
|
||||
use \WC_Helper_Product as ProductHelper;
|
||||
use Automattic\WooCommerce\Blocks\RestApi\StoreApi\Utilities\ReserveStock;
|
||||
|
||||
/**
|
||||
* ReserveStock Utility Tests.
|
||||
*/
|
||||
class ReserveStockTests extends TestCase {
|
||||
|
||||
/**
|
||||
* Test that stock is reserved for draft orders.
|
||||
*/
|
||||
public function test_reserve_stock_for_order() {
|
||||
$class = new ReserveStock();
|
||||
|
||||
$product = ProductHelper::create_simple_product();
|
||||
$product->set_manage_stock( true );
|
||||
$product->set_stock( 10 );
|
||||
$product->save();
|
||||
|
||||
$order = OrderHelper::create_order( 1, $product ); // Note this adds 4 to the order.
|
||||
$order->set_status( 'checkout-draft' );
|
||||
$order->save();
|
||||
|
||||
$result = $class->reserve_stock_for_order( $order );
|
||||
$this->assertTrue( $result );
|
||||
$this->assertEquals( 4, $this->get_reserved_stock_by_product_id( $product->get_stock_managed_by_id() ) );
|
||||
|
||||
// Repeat.
|
||||
$order = OrderHelper::create_order( 1, $product );
|
||||
$order->set_status( 'checkout-draft' );
|
||||
$order->save();
|
||||
|
||||
$result = $class->reserve_stock_for_order( $order );
|
||||
$this->assertTrue( $result );
|
||||
$this->assertEquals( 8, $this->get_reserved_stock_by_product_id( $product->get_stock_managed_by_id() ) );
|
||||
|
||||
// Repeat again - should not be enough stock for this.
|
||||
$order = OrderHelper::create_order( 1, $product );
|
||||
$order->set_status( 'checkout-draft' );
|
||||
$order->save();
|
||||
|
||||
$result = $class->reserve_stock_for_order( $order );
|
||||
$this->assertTrue( is_wp_error( $result ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get the count of reserved stock.
|
||||
*
|
||||
* @param integer $product_id
|
||||
* @return integer
|
||||
*/
|
||||
protected function get_reserved_stock_by_product_id( $product_id ) {
|
||||
global $wpdb;
|
||||
return $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT SUM( stock_table.`stock_quantity` ) FROM $wpdb->wc_reserved_stock stock_table WHERE stock_table.`product_id` = %d",
|
||||
$product_id
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue