Merge branch 'trunk' into add/api-test-crud-products
This commit is contained in:
commit
f44f62c385
|
@ -35,8 +35,8 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||||
<?php
|
<?php
|
||||||
echo wc_price( $item->get_total(), array( 'currency' => $order->get_currency() ) );
|
echo wc_price( $item->get_total(), array( 'currency' => $order->get_currency() ) );
|
||||||
|
|
||||||
if ( $refunded = $order->get_total_refunded_for_item( $item_id, 'fee' ) ) {
|
if ( $refunded = -1 * $order->get_total_refunded_for_item( $item_id, 'fee' ) ) {
|
||||||
echo '<small class="refunded">-' . wc_price( $refunded, array( 'currency' => $order->get_currency() ) ) . '</small>';
|
echo '<small class="refunded">' . wc_price( $refunded, array( 'currency' => $order->get_currency() ) ) . '</small>';
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
|
@ -59,8 +59,8 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||||
<?php
|
<?php
|
||||||
echo ( '' !== $tax_item_total ) ? wc_price( wc_round_tax_total( $tax_item_total ), array( 'currency' => $order->get_currency() ) ) : '–';
|
echo ( '' !== $tax_item_total ) ? wc_price( wc_round_tax_total( $tax_item_total ), array( 'currency' => $order->get_currency() ) ) : '–';
|
||||||
|
|
||||||
if ( $refunded = $order->get_tax_refunded_for_item( $item_id, $tax_item_id, 'fee' ) ) {
|
if ( $refunded = -1 * $order->get_tax_refunded_for_item( $item_id, $tax_item_id, 'fee' ) ) {
|
||||||
echo '<small class="refunded">-' . wc_price( $refunded, array( 'currency' => $order->get_currency() ) ) . '</small>';
|
echo '<small class="refunded">' . wc_price( $refunded, array( 'currency' => $order->get_currency() ) ) . '</small>';
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -59,10 +59,10 @@ $row_class = apply_filters( 'woocommerce_admin_html_order_item_class', ! empt
|
||||||
<?php
|
<?php
|
||||||
echo '<small class="times">×</small> ' . esc_html( $item->get_quantity() );
|
echo '<small class="times">×</small> ' . esc_html( $item->get_quantity() );
|
||||||
|
|
||||||
$refunded_qty = $order->get_qty_refunded_for_item( $item_id );
|
$refunded_qty = -1 * $order->get_qty_refunded_for_item( $item_id );
|
||||||
|
|
||||||
if ( $refunded_qty ) {
|
if ( $refunded_qty ) {
|
||||||
echo '<small class="refunded">-' . esc_html( $refunded_qty * -1 ) . '</small>';
|
echo '<small class="refunded">' . esc_html( $refunded_qty * -1 ) . '</small>';
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
|
@ -108,10 +108,10 @@ $row_class = apply_filters( 'woocommerce_admin_html_order_item_class', ! empt
|
||||||
echo '<span class="wc-order-item-discount">' . sprintf( esc_html__( '%s discount', 'woocommerce' ), wc_price( wc_format_decimal( $item->get_subtotal() - $item->get_total(), '' ), array( 'currency' => $order->get_currency() ) ) ) . '</span>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
echo '<span class="wc-order-item-discount">' . sprintf( esc_html__( '%s discount', 'woocommerce' ), wc_price( wc_format_decimal( $item->get_subtotal() - $item->get_total(), '' ), array( 'currency' => $order->get_currency() ) ) ) . '</span>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||||
}
|
}
|
||||||
|
|
||||||
$refunded = $order->get_total_refunded_for_item( $item_id );
|
$refunded = -1 * $order->get_total_refunded_for_item( $item_id );
|
||||||
|
|
||||||
if ( $refunded ) {
|
if ( $refunded ) {
|
||||||
echo '<small class="refunded">-' . wc_price( $refunded, array( 'currency' => $order->get_currency() ) ) . '</small>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
echo '<small class="refunded">' . wc_price( $refunded, array( 'currency' => $order->get_currency() ) ) . '</small>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
|
@ -156,10 +156,10 @@ $row_class = apply_filters( 'woocommerce_admin_html_order_item_class', ! empt
|
||||||
echo '–';
|
echo '–';
|
||||||
}
|
}
|
||||||
|
|
||||||
$refunded = $order->get_tax_refunded_for_item( $item_id, $tax_item_id );
|
$refunded = -1 * $order->get_tax_refunded_for_item( $item_id, $tax_item_id );
|
||||||
|
|
||||||
if ( $refunded ) {
|
if ( $refunded ) {
|
||||||
echo '<small class="refunded">-' . wc_price( $refunded, array( 'currency' => $order->get_currency() ) ) . '</small>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
echo '<small class="refunded">' . wc_price( $refunded, array( 'currency' => $order->get_currency() ) ) . '</small>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -64,9 +64,9 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||||
<div class="view">
|
<div class="view">
|
||||||
<?php
|
<?php
|
||||||
echo wp_kses_post( wc_price( $item->get_total(), array( 'currency' => $order->get_currency() ) ) );
|
echo wp_kses_post( wc_price( $item->get_total(), array( 'currency' => $order->get_currency() ) ) );
|
||||||
$refunded = $order->get_total_refunded_for_item( $item_id, 'shipping' );
|
$refunded = -1 * $order->get_total_refunded_for_item( $item_id, 'shipping' );
|
||||||
if ( $refunded ) {
|
if ( $refunded ) {
|
||||||
echo wp_kses_post( '<small class="refunded">-' . wc_price( $refunded, array( 'currency' => $order->get_currency() ) ) . '</small>' );
|
echo wp_kses_post( '<small class="refunded">' . wc_price( $refunded, array( 'currency' => $order->get_currency() ) ) . '</small>' );
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
|
@ -89,9 +89,9 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||||
<div class="view">
|
<div class="view">
|
||||||
<?php
|
<?php
|
||||||
echo wp_kses_post( ( '' !== $tax_item_total ) ? wc_price( $tax_item_total, array( 'currency' => $order->get_currency() ) ) : '–' );
|
echo wp_kses_post( ( '' !== $tax_item_total ) ? wc_price( $tax_item_total, array( 'currency' => $order->get_currency() ) ) : '–' );
|
||||||
$refunded = $order->get_tax_refunded_for_item( $item_id, $tax_item_id, 'shipping' );
|
$refunded = -1 * $order->get_tax_refunded_for_item( $item_id, $tax_item_id, 'shipping' );
|
||||||
if ( $refunded ) {
|
if ( $refunded ) {
|
||||||
echo wp_kses_post( '<small class="refunded">-' . wc_price( $refunded, array( 'currency' => $order->get_currency() ) ) . '</small>' );
|
echo wp_kses_post( '<small class="refunded">' . wc_price( $refunded, array( 'currency' => $order->get_currency() ) ) . '</small>' );
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -165,6 +165,10 @@ class WC_Install {
|
||||||
'wc_update_560_create_refund_returns_page',
|
'wc_update_560_create_refund_returns_page',
|
||||||
'wc_update_560_db_version',
|
'wc_update_560_db_version',
|
||||||
),
|
),
|
||||||
|
'6.0.0' => array(
|
||||||
|
'wc_update_600_migrate_rate_limit_options',
|
||||||
|
'wc_update_600_db_version',
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -524,6 +528,7 @@ class WC_Install {
|
||||||
wp_clear_scheduled_hook( 'woocommerce_cleanup_logs' );
|
wp_clear_scheduled_hook( 'woocommerce_cleanup_logs' );
|
||||||
wp_clear_scheduled_hook( 'woocommerce_geoip_updater' );
|
wp_clear_scheduled_hook( 'woocommerce_geoip_updater' );
|
||||||
wp_clear_scheduled_hook( 'woocommerce_tracker_send_event' );
|
wp_clear_scheduled_hook( 'woocommerce_tracker_send_event' );
|
||||||
|
wp_clear_scheduled_hook( 'woocommerce_cleanup_rate_limits' );
|
||||||
|
|
||||||
$ve = get_option( 'gmt_offset' ) > 0 ? '-' : '+';
|
$ve = get_option( 'gmt_offset' ) > 0 ? '-' : '+';
|
||||||
|
|
||||||
|
@ -545,6 +550,7 @@ class WC_Install {
|
||||||
wp_schedule_event( time() + ( 6 * HOUR_IN_SECONDS ), 'twicedaily', 'woocommerce_cleanup_sessions' );
|
wp_schedule_event( time() + ( 6 * HOUR_IN_SECONDS ), 'twicedaily', 'woocommerce_cleanup_sessions' );
|
||||||
wp_schedule_event( time() + MINUTE_IN_SECONDS, 'fifteendays', 'woocommerce_geoip_updater' );
|
wp_schedule_event( time() + MINUTE_IN_SECONDS, 'fifteendays', 'woocommerce_geoip_updater' );
|
||||||
wp_schedule_event( time() + 10, apply_filters( 'woocommerce_tracker_event_recurrence', 'daily' ), 'woocommerce_tracker_send_event' );
|
wp_schedule_event( time() + 10, apply_filters( 'woocommerce_tracker_event_recurrence', 'daily' ), 'woocommerce_tracker_send_event' );
|
||||||
|
wp_schedule_event( time() + ( 3 * HOUR_IN_SECONDS ), 'daily', 'woocommerce_cleanup_rate_limits' );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1039,6 +1045,13 @@ CREATE TABLE {$wpdb->prefix}wc_reserved_stock (
|
||||||
`timestamp` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
`timestamp` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||||
`expires` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
`expires` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||||
PRIMARY KEY (`order_id`, `product_id`)
|
PRIMARY KEY (`order_id`, `product_id`)
|
||||||
|
) $collate;
|
||||||
|
CREATE TABLE {$wpdb->prefix}woocommerce_rate_limits (
|
||||||
|
rate_limit_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
rate_limit_key varchar(200) NOT NULL,
|
||||||
|
rate_limit_expiry BIGINT UNSIGNED NOT NULL,
|
||||||
|
PRIMARY KEY (rate_limit_id),
|
||||||
|
UNIQUE KEY rate_limit_key (rate_limit_key($max_index_length))
|
||||||
) $collate;
|
) $collate;
|
||||||
";
|
";
|
||||||
|
|
||||||
|
@ -1074,6 +1087,7 @@ CREATE TABLE {$wpdb->prefix}wc_reserved_stock (
|
||||||
"{$wpdb->prefix}woocommerce_tax_rate_locations",
|
"{$wpdb->prefix}woocommerce_tax_rate_locations",
|
||||||
"{$wpdb->prefix}woocommerce_tax_rates",
|
"{$wpdb->prefix}woocommerce_tax_rates",
|
||||||
"{$wpdb->prefix}wc_reserved_stock",
|
"{$wpdb->prefix}wc_reserved_stock",
|
||||||
|
"{$wpdb->prefix}woocommerce_rate_limits",
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -32,13 +32,57 @@ defined( 'ABSPATH' ) || exit;
|
||||||
class WC_Rate_Limiter {
|
class WC_Rate_Limiter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs Option name from action identifier.
|
* Cache group.
|
||||||
|
*/
|
||||||
|
const CACHE_GROUP = 'wc_rate_limit';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook in methods.
|
||||||
|
*/
|
||||||
|
public static function init() {
|
||||||
|
add_action( 'woocommerce_cleanup_rate_limits', array( __CLASS__, 'cleanup' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs key name from action identifier.
|
||||||
|
* Left in for backwards compatibility.
|
||||||
*
|
*
|
||||||
* @param string $action_id Identifier of the action.
|
* @param string $action_id Identifier of the action.
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function storage_id( $action_id ) {
|
public static function storage_id( $action_id ) {
|
||||||
return 'woocommerce_rate_limit_' . $action_id;
|
return $action_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a cache prefix.
|
||||||
|
*
|
||||||
|
* @param string $action_id Identifier of the action.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected static function get_cache_key( $action_id ) {
|
||||||
|
return WC_Cache_Helper::get_cache_prefix( 'rate_limit' . $action_id );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a cached rate limit.
|
||||||
|
*
|
||||||
|
* @param string $action_id Identifier of the action.
|
||||||
|
* @return bool|int
|
||||||
|
*/
|
||||||
|
protected static function get_cached( $action_id ) {
|
||||||
|
return wp_cache_get( self::get_cache_key( $action_id ), self::CACHE_GROUP );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache a rate limit.
|
||||||
|
*
|
||||||
|
* @param string $action_id Identifier of the action.
|
||||||
|
* @param int $expiry Timestamp when the limit expires.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected static function set_cache( $action_id, $expiry ) {
|
||||||
|
return wp_cache_set( self::get_cache_key( $action_id ), $expiry, self::CACHE_GROUP );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,10 +92,27 @@ class WC_Rate_Limiter {
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public static function retried_too_soon( $action_id ) {
|
public static function retried_too_soon( $action_id ) {
|
||||||
$next_try_allowed_at = get_option( self::storage_id( $action_id ) );
|
global $wpdb;
|
||||||
|
|
||||||
|
$next_try_allowed_at = self::get_cached( $action_id );
|
||||||
|
|
||||||
|
if ( false === $next_try_allowed_at ) {
|
||||||
|
$next_try_allowed_at = $wpdb->get_var(
|
||||||
|
$wpdb->prepare(
|
||||||
|
"
|
||||||
|
SELECT rate_limit_expiry
|
||||||
|
FROM {$wpdb->prefix}woocommerce_rate_limits
|
||||||
|
WHERE rate_limit_key = %s
|
||||||
|
",
|
||||||
|
$action_id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
self::set_cache( $action_id, $next_try_allowed_at );
|
||||||
|
}
|
||||||
|
|
||||||
// No record of action running, so action is allowed to run.
|
// No record of action running, so action is allowed to run.
|
||||||
if ( false === $next_try_allowed_at ) {
|
if ( null === $next_try_allowed_at ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,8 +133,41 @@ class WC_Rate_Limiter {
|
||||||
* @return bool True if the option setting was successful, false otherwise.
|
* @return bool True if the option setting was successful, false otherwise.
|
||||||
*/
|
*/
|
||||||
public static function set_rate_limit( $action_id, $delay ) {
|
public static function set_rate_limit( $action_id, $delay ) {
|
||||||
$option_name = self::storage_id( $action_id );
|
global $wpdb;
|
||||||
|
|
||||||
$next_try_allowed_at = time() + $delay;
|
$next_try_allowed_at = time() + $delay;
|
||||||
return update_option( $option_name, $next_try_allowed_at );
|
|
||||||
|
$result = $wpdb->replace(
|
||||||
|
$wpdb->prefix . 'woocommerce_rate_limits',
|
||||||
|
array(
|
||||||
|
'rate_limit_key' => $action_id,
|
||||||
|
'rate_limit_expiry' => $next_try_allowed_at,
|
||||||
|
),
|
||||||
|
array( '%s', '%d' )
|
||||||
|
);
|
||||||
|
|
||||||
|
self::set_cache( $action_id, $next_try_allowed_at );
|
||||||
|
|
||||||
|
return false !== $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleanup expired rate limits from the database and clear caches.
|
||||||
|
*/
|
||||||
|
public static function cleanup() {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$wpdb->query(
|
||||||
|
$wpdb->prepare(
|
||||||
|
"DELETE FROM {$wpdb->prefix}woocommerce_rate_limits WHERE rate_limit_expiry < %d",
|
||||||
|
time()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( class_exists( 'WC_Cache_Helper' ) ) {
|
||||||
|
WC_Cache_Helper::invalidate_cache_group( self::CACHE_GROUP );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WC_Rate_Limiter::init();
|
||||||
|
|
|
@ -358,7 +358,13 @@ class WC_Shipping {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter the calculated rates.
|
/**
|
||||||
|
* Filter the calculated shipping rates.
|
||||||
|
*
|
||||||
|
* @see https://gist.github.com/woogists/271654709e1d27648546e83253c1a813 for cache invalidation methods.
|
||||||
|
* @param array $package['rates'] Package rates.
|
||||||
|
* @param array $package Package of cart items.
|
||||||
|
*/
|
||||||
$package['rates'] = apply_filters( 'woocommerce_package_rates', $package['rates'], $package );
|
$package['rates'] = apply_filters( 'woocommerce_package_rates', $package['rates'], $package );
|
||||||
|
|
||||||
// Store in session to avoid recalculation.
|
// Store in session to avoid recalculation.
|
||||||
|
|
|
@ -207,10 +207,7 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
|
||||||
$args['post_type'] = $this->post_type;
|
$args['post_type'] = $this->post_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
$orderby = $request->get_param( 'orderby' );
|
$ordering_args = WC()->query->get_catalog_ordering_args( $args['orderby'], $args['order'] );
|
||||||
$order = $request->get_param( 'order' );
|
|
||||||
|
|
||||||
$ordering_args = WC()->query->get_catalog_ordering_args( $orderby, $order );
|
|
||||||
$args['orderby'] = $ordering_args['orderby'];
|
$args['orderby'] = $ordering_args['orderby'];
|
||||||
$args['order'] = $ordering_args['order'];
|
$args['order'] = $ordering_args['order'];
|
||||||
if ( $ordering_args['meta_key'] ) {
|
if ( $ordering_args['meta_key'] ) {
|
||||||
|
|
|
@ -2297,3 +2297,41 @@ function wc_update_560_create_refund_returns_page() {
|
||||||
function wc_update_560_db_version() {
|
function wc_update_560_db_version() {
|
||||||
WC_Install::update_db_version( '5.6.0' );
|
WC_Install::update_db_version( '5.6.0' );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate rate limit options to the new table.
|
||||||
|
*
|
||||||
|
* See @link https://github.com/woocommerce/woocommerce/issues/27103.
|
||||||
|
*/
|
||||||
|
function wc_update_600_migrate_rate_limit_options() {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$rate_limits = $wpdb->get_results(
|
||||||
|
"
|
||||||
|
SELECT option_name, option_value
|
||||||
|
FROM $wpdb->options
|
||||||
|
WHERE option_name LIKE 'woocommerce_rate_limit_add_payment_method_%'
|
||||||
|
",
|
||||||
|
ARRAY_A
|
||||||
|
);
|
||||||
|
$prefix_length = strlen( 'woocommerce_rate_limit_' );
|
||||||
|
|
||||||
|
foreach ( $rate_limits as $rate_limit ) {
|
||||||
|
$new_delay = (int) $rate_limit['option_value'] - time();
|
||||||
|
|
||||||
|
// Migrate the limit if it hasn't expired yet.
|
||||||
|
if ( 0 < $new_delay ) {
|
||||||
|
$action_id = substr( $rate_limit['option_name'], $prefix_length );
|
||||||
|
WC_Rate_Limiter::set_rate_limit( $action_id, $new_delay );
|
||||||
|
}
|
||||||
|
|
||||||
|
delete_option( $rate_limit['option_name'] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update DB version to 6.0.0.
|
||||||
|
*/
|
||||||
|
function wc_update_600_db_version() {
|
||||||
|
WC_Install::update_db_version( '6.0.0' );
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const { order, getOrderExample } = require('./order');
|
const { order, getOrderExample } = require('./order');
|
||||||
const { coupon } = require('./coupon');
|
const { coupon } = require('./coupon');
|
||||||
|
const { refund } = require('./refund');
|
||||||
const shared = require('./shared');
|
const shared = require('./shared');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -7,4 +8,5 @@ module.exports = {
|
||||||
getOrderExample,
|
getOrderExample,
|
||||||
coupon,
|
coupon,
|
||||||
shared,
|
shared,
|
||||||
|
refund,
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
/**
|
||||||
|
* A basic refund.
|
||||||
|
*
|
||||||
|
* For more details on the order refund properties, see:
|
||||||
|
*
|
||||||
|
* https://woocommerce.github.io/woocommerce-rest-api-docs/#order-refund-properties
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const refund = {
|
||||||
|
api_refund: false,
|
||||||
|
amount: '1.00',
|
||||||
|
reason: 'Late delivery refund.',
|
||||||
|
line_items: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
refund: refund,
|
||||||
|
};
|
|
@ -1,9 +1,11 @@
|
||||||
const { ordersApi } = require('./orders');
|
const { ordersApi } = require('./orders');
|
||||||
const { couponsApi } = require('./coupons');
|
const { couponsApi } = require('./coupons');
|
||||||
const { productsApi } = require('./products');
|
const { productsApi } = require('./products');
|
||||||
|
const { refundsApi } = require('./refunds');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ordersApi,
|
ordersApi,
|
||||||
couponsApi,
|
couponsApi,
|
||||||
productsApi,
|
productsApi,
|
||||||
|
refundsApi,
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
const {
|
||||||
|
getRequest,
|
||||||
|
postRequest,
|
||||||
|
putRequest,
|
||||||
|
deleteRequest,
|
||||||
|
} = require( '../utils/request' );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WooCommerce Refunds endpoints.
|
||||||
|
*
|
||||||
|
* https://woocommerce.github.io/woocommerce-rest-api-docs/#refunds
|
||||||
|
*/
|
||||||
|
const refundsApi = {
|
||||||
|
name: 'Refunds',
|
||||||
|
create: {
|
||||||
|
name: 'Create a refund',
|
||||||
|
method: 'POST',
|
||||||
|
path: 'orders/<id>/refunds',
|
||||||
|
responseCode: 201,
|
||||||
|
refund: async ( orderId, refundDetails ) =>
|
||||||
|
postRequest( `orders/${ orderId }/refunds`, refundDetails ),
|
||||||
|
},
|
||||||
|
retrieve: {
|
||||||
|
name: 'Retrieve a refund',
|
||||||
|
method: 'GET',
|
||||||
|
path: 'orders/<id>/refunds/<refund_id>',
|
||||||
|
responseCode: 200,
|
||||||
|
refund: async ( orderId, refundId ) =>
|
||||||
|
getRequest( `orders/${ orderId }/refunds/${ refundId }` ),
|
||||||
|
},
|
||||||
|
listAll: {
|
||||||
|
name: 'List all refunds',
|
||||||
|
method: 'GET',
|
||||||
|
path: 'orders/<id>/refunds',
|
||||||
|
responseCode: 200,
|
||||||
|
refunds: async ( orderId ) =>
|
||||||
|
getRequest( `orders/${ orderId }/refunds` ),
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
name: 'Delete a refund',
|
||||||
|
method: 'DELETE',
|
||||||
|
path: 'orders/<id>/refunds/<refund_id>',
|
||||||
|
responseCode: 200,
|
||||||
|
payload: {
|
||||||
|
force: false,
|
||||||
|
},
|
||||||
|
refund: async ( orderId, refundId, deletePermanently ) =>
|
||||||
|
deleteRequest(
|
||||||
|
`orders/${ orderId }/refunds/${ refundId }`,
|
||||||
|
deletePermanently
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
refundsApi: refundsApi,
|
||||||
|
};
|
|
@ -569,31 +569,35 @@ const { productsApi } = require('../../endpoints/products');
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
// This case will remain skipped until orderby slug is fixed.
|
it( 'slug', async () => {
|
||||||
// See: https://github.com/woocommerce/woocommerce/issues/30354#issuecomment-925955099.
|
const productNamesBySlugAsc = [
|
||||||
it.skip( 'slug', async () => {
|
'Polo', // The Polo isn't published so it has an empty slug.
|
||||||
|
...productNamesAsc.filter( p => p !== 'Polo' ),
|
||||||
|
];
|
||||||
|
const productNamesBySlugDesc = [ ...productNamesBySlugAsc ].reverse();
|
||||||
|
|
||||||
const result1 = await productsApi.listAll.products( {
|
const result1 = await productsApi.listAll.products( {
|
||||||
order: 'asc',
|
order: 'asc',
|
||||||
orderby: 'slug',
|
orderby: 'slug',
|
||||||
per_page: productNamesAsc.length,
|
per_page: productNamesBySlugAsc.length,
|
||||||
} );
|
} );
|
||||||
expect( result1.statusCode ).toEqual( 200 );
|
expect( result1.statusCode ).toEqual( 200 );
|
||||||
|
|
||||||
// Verify all results are in ascending order.
|
// Verify all results are in ascending order.
|
||||||
result1.body.forEach( ( { name }, idx ) => {
|
result1.body.forEach( ( { name }, idx ) => {
|
||||||
expect( name ).toBe( productNamesAsc[ idx ] );
|
expect( name ).toBe( productNamesBySlugAsc[ idx ] );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
const result2 = await productsApi.listAll.products( {
|
const result2 = await productsApi.listAll.products( {
|
||||||
order: 'desc',
|
order: 'desc',
|
||||||
orderby: 'slug',
|
orderby: 'slug',
|
||||||
per_page: productNamesDesc.length,
|
per_page: productNamesBySlugDesc.length,
|
||||||
} );
|
} );
|
||||||
expect( result2.statusCode ).toEqual( 200 );
|
expect( result2.statusCode ).toEqual( 200 );
|
||||||
|
|
||||||
// Verify all results are in descending order.
|
// Verify all results are in descending order.
|
||||||
result2.body.forEach( ( { name }, idx ) => {
|
result2.body.forEach( ( { name }, idx ) => {
|
||||||
expect( name ).toBe( productNamesDesc[ idx ] );
|
expect( name ).toBe( productNamesBySlugDesc[ idx ] );
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
@ -676,7 +680,7 @@ const { productsApi } = require('../../endpoints/products');
|
||||||
|
|
||||||
// This case will remain skipped until orderby include is fixed.
|
// This case will remain skipped until orderby include is fixed.
|
||||||
// See: https://github.com/woocommerce/woocommerce/issues/30354#issuecomment-925955099.
|
// See: https://github.com/woocommerce/woocommerce/issues/30354#issuecomment-925955099.
|
||||||
it.skip( 'include', async () => {
|
it( 'include', async () => {
|
||||||
const includeIds = [
|
const includeIds = [
|
||||||
sampleData.groupedProducts[ 0 ].id,
|
sampleData.groupedProducts[ 0 ].id,
|
||||||
sampleData.simpleProducts[ 3 ].id,
|
sampleData.simpleProducts[ 3 ].id,
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
const { refundsApi } = require( '../../endpoints/refunds' );
|
||||||
|
const { ordersApi } = require( '../../endpoints/orders' );
|
||||||
|
const { productsApi } = require( '../../endpoints/products' );
|
||||||
|
const { refund } = require( '../../data' );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for the WooCommerce Refunds API.
|
||||||
|
*
|
||||||
|
* @group api
|
||||||
|
* @group refunds
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
describe( 'Refunds API tests', () => {
|
||||||
|
let expectedRefund;
|
||||||
|
let orderId;
|
||||||
|
let productId;
|
||||||
|
|
||||||
|
beforeAll( async () => {
|
||||||
|
// Create a product and save its product ID
|
||||||
|
const product = {
|
||||||
|
name: 'Simple Product for Refunds API tests',
|
||||||
|
regular_price: '100',
|
||||||
|
};
|
||||||
|
const createProductResponse = await productsApi.create.product(
|
||||||
|
product
|
||||||
|
);
|
||||||
|
productId = createProductResponse.body.id;
|
||||||
|
|
||||||
|
// Create an order with a product line item, and save its Order ID
|
||||||
|
const order = {
|
||||||
|
status: 'pending',
|
||||||
|
line_items: [
|
||||||
|
{
|
||||||
|
product_id: productId,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const createOrderResponse = await ordersApi.create.order( order );
|
||||||
|
orderId = createOrderResponse.body.id;
|
||||||
|
|
||||||
|
// Setup the expected refund object
|
||||||
|
expectedRefund = {
|
||||||
|
...refund,
|
||||||
|
line_items: [
|
||||||
|
{
|
||||||
|
product_id: productId,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} );
|
||||||
|
|
||||||
|
afterAll( async () => {
|
||||||
|
// Cleanup the created product and order
|
||||||
|
await productsApi.delete.product( productId, true );
|
||||||
|
await ordersApi.delete.order( orderId, true );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'can create a refund', async () => {
|
||||||
|
const { status, body } = await refundsApi.create.refund(
|
||||||
|
orderId,
|
||||||
|
expectedRefund
|
||||||
|
);
|
||||||
|
expect( status ).toEqual( refundsApi.create.responseCode );
|
||||||
|
expect( body.id ).toBeDefined();
|
||||||
|
|
||||||
|
// Save the refund ID
|
||||||
|
expectedRefund.id = body.id;
|
||||||
|
|
||||||
|
// Verify that the order was refunded.
|
||||||
|
const getOrderResponse = await ordersApi.retrieve.order( orderId );
|
||||||
|
expect( getOrderResponse.body.refunds ).toHaveLength( 1 );
|
||||||
|
expect( getOrderResponse.body.refunds[ 0 ].id ).toEqual(
|
||||||
|
expectedRefund.id
|
||||||
|
);
|
||||||
|
expect( getOrderResponse.body.refunds[ 0 ].reason ).toEqual(
|
||||||
|
expectedRefund.reason
|
||||||
|
);
|
||||||
|
expect( getOrderResponse.body.refunds[ 0 ].total ).toEqual(
|
||||||
|
`-${ expectedRefund.amount }`
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'can retrieve a refund', async () => {
|
||||||
|
const { status, body } = await refundsApi.retrieve.refund(
|
||||||
|
orderId,
|
||||||
|
expectedRefund.id
|
||||||
|
);
|
||||||
|
|
||||||
|
expect( status ).toEqual( refundsApi.retrieve.responseCode );
|
||||||
|
expect( body.id ).toEqual( expectedRefund.id );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'can list all refunds', async () => {
|
||||||
|
const { status, body } = await refundsApi.listAll.refunds( orderId );
|
||||||
|
|
||||||
|
expect( status ).toEqual( refundsApi.listAll.responseCode );
|
||||||
|
expect( body ).toHaveLength( 1 );
|
||||||
|
expect( body[ 0 ].id ).toEqual( expectedRefund.id );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'can delete a refund', async () => {
|
||||||
|
const { status, body } = await refundsApi.delete.refund(
|
||||||
|
orderId,
|
||||||
|
expectedRefund.id,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
expect( status ).toEqual( refundsApi.delete.responseCode );
|
||||||
|
expect( body.id ).toEqual( expectedRefund.id );
|
||||||
|
|
||||||
|
// Verify that the refund cannot be retrieved
|
||||||
|
const retrieveRefundResponse = await refundsApi.retrieve.refund(
|
||||||
|
orderId,
|
||||||
|
expectedRefund.id
|
||||||
|
);
|
||||||
|
expect( retrieveRefundResponse.status ).toEqual( 404 );
|
||||||
|
|
||||||
|
// Verify that the order no longer has a refund
|
||||||
|
const retrieveOrderResponse = await ordersApi.retrieve.order( orderId );
|
||||||
|
expect( retrieveOrderResponse.body.refunds ).toHaveLength( 0 );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -37,16 +37,69 @@ class WC_Tests_Rate_Limiter extends WC_Unit_Test_Case {
|
||||||
$rate_limit_id_1 = $action_identifier . $user_1_id;
|
$rate_limit_id_1 = $action_identifier . $user_1_id;
|
||||||
$rate_limit_id_2 = $action_identifier . $user_2_id;
|
$rate_limit_id_2 = $action_identifier . $user_2_id;
|
||||||
|
|
||||||
WC_Rate_Limiter::set_rate_limit( $rate_limit_id_1, 1 );
|
WC_Rate_Limiter::set_rate_limit( $rate_limit_id_1, 0 );
|
||||||
|
|
||||||
$this->assertEquals( true, WC_Rate_Limiter::retried_too_soon( $rate_limit_id_1 ), 'retried_too_soon allowed action to run too soon before the delay.' );
|
$this->assertEquals( true, WC_Rate_Limiter::retried_too_soon( $rate_limit_id_1 ), 'retried_too_soon allowed action to run too soon before the delay.' );
|
||||||
$this->assertEquals( false, WC_Rate_Limiter::retried_too_soon( $rate_limit_id_2 ), 'retried_too_soon did not allow action to run for another user before the delay.' );
|
$this->assertEquals( false, WC_Rate_Limiter::retried_too_soon( $rate_limit_id_2 ), 'retried_too_soon did not allow action to run for another user before the delay.' );
|
||||||
|
|
||||||
// As retired_too_soon bails if current time <= limit, the actual time needs to be at least 1 second after the limit.
|
// As retired_too_soon bails if current time <= limit, the actual time needs to be at least 1 second after the limit.
|
||||||
sleep( 2 );
|
sleep( 1 );
|
||||||
|
|
||||||
$this->assertEquals( false, WC_Rate_Limiter::retried_too_soon( $rate_limit_id_1 ), 'retried_too_soon did not allow action to run after the designated delay.' );
|
$this->assertEquals( false, WC_Rate_Limiter::retried_too_soon( $rate_limit_id_1 ), 'retried_too_soon did not allow action to run after the designated delay.' );
|
||||||
$this->assertEquals( false, WC_Rate_Limiter::retried_too_soon( $rate_limit_id_2 ), 'retried_too_soon did not allow action to run for another user after the designated delay.' );
|
$this->assertEquals( false, WC_Rate_Limiter::retried_too_soon( $rate_limit_id_2 ), 'retried_too_soon did not allow action to run for another user after the designated delay.' );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test setting the limit and running rate limited actions when missing cache.
|
||||||
|
*/
|
||||||
|
public function test_rate_limit_limits_without_cache() {
|
||||||
|
$action_identifier = 'action_1';
|
||||||
|
$user_1_id = 10;
|
||||||
|
$user_2_id = 15;
|
||||||
|
|
||||||
|
$rate_limit_id_1 = $action_identifier . $user_1_id;
|
||||||
|
$rate_limit_id_2 = $action_identifier . $user_2_id;
|
||||||
|
|
||||||
|
WC_Rate_Limiter::set_rate_limit( $rate_limit_id_1, 0 );
|
||||||
|
// Clear cached value for user 1.
|
||||||
|
wp_cache_delete( WC_Cache_Helper::get_cache_prefix( 'rate_limit' . $rate_limit_id_1 ), WC_Rate_Limiter::CACHE_GROUP );
|
||||||
|
|
||||||
|
$this->assertEquals( true, WC_Rate_Limiter::retried_too_soon( $rate_limit_id_1 ), 'retried_too_soon allowed action to run too soon before the delay.' );
|
||||||
|
$this->assertEquals( false, WC_Rate_Limiter::retried_too_soon( $rate_limit_id_2 ), 'retried_too_soon did not allow action to run for another user before the delay.' );
|
||||||
|
|
||||||
|
// Clear cached values for both users.
|
||||||
|
wp_cache_delete( WC_Cache_Helper::get_cache_prefix( 'rate_limit' . $rate_limit_id_1 ), WC_Rate_Limiter::CACHE_GROUP );
|
||||||
|
wp_cache_delete( WC_Cache_Helper::get_cache_prefix( 'rate_limit' . $rate_limit_id_2 ), WC_Rate_Limiter::CACHE_GROUP );
|
||||||
|
|
||||||
|
// As retired_too_soon bails if current time <= limit, the actual time needs to be at least 1 second after the limit.
|
||||||
|
sleep( 1 );
|
||||||
|
|
||||||
|
$this->assertEquals( false, WC_Rate_Limiter::retried_too_soon( $rate_limit_id_1 ), 'retried_too_soon did not allow action to run after the designated delay.' );
|
||||||
|
$this->assertEquals( false, WC_Rate_Limiter::retried_too_soon( $rate_limit_id_2 ), 'retried_too_soon did not allow action to run for another user after the designated delay.' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that rate limit option migration only processes unexpired limits.
|
||||||
|
*/
|
||||||
|
public function test_rate_limit_option_migration() {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
// Add some options to be migrated.
|
||||||
|
add_option( 'woocommerce_rate_limit_add_payment_method_123', time() + 1000 );
|
||||||
|
add_option( 'woocommerce_rate_limit_add_payment_method_234', time() - 1 );
|
||||||
|
|
||||||
|
// Run the migration function.
|
||||||
|
include_once WC_Unit_Tests_Bootstrap::instance()->plugin_dir . '/includes/wc-update-functions.php';
|
||||||
|
wc_update_600_migrate_rate_limit_options();
|
||||||
|
|
||||||
|
// Ensure that only the _123 limit was migrated.
|
||||||
|
$migrated = $wpdb->get_col( "SELECT rate_limit_key FROM {$wpdb->prefix}woocommerce_rate_limits" );
|
||||||
|
|
||||||
|
$this->assertCount( 1, $migrated );
|
||||||
|
$this->assertEquals( 'add_payment_method_123', $migrated[0] );
|
||||||
|
|
||||||
|
// Verify that all rate limit options were deleted.
|
||||||
|
$this->assertFalse( get_option( 'woocommerce_rate_limit_add_payment_method_123' ) );
|
||||||
|
$this->assertFalse( get_option( 'woocommerce_rate_limit_add_payment_method_234' ) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ wp_clear_scheduled_hook( 'woocommerce_cleanup_personal_data' );
|
||||||
wp_clear_scheduled_hook( 'woocommerce_cleanup_logs' );
|
wp_clear_scheduled_hook( 'woocommerce_cleanup_logs' );
|
||||||
wp_clear_scheduled_hook( 'woocommerce_geoip_updater' );
|
wp_clear_scheduled_hook( 'woocommerce_geoip_updater' );
|
||||||
wp_clear_scheduled_hook( 'woocommerce_tracker_send_event' );
|
wp_clear_scheduled_hook( 'woocommerce_tracker_send_event' );
|
||||||
|
wp_clear_scheduled_hook( 'woocommerce_cleanup_rate_limits' );
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Only remove ALL product and page data if WC_REMOVE_ALL_DATA constant is set to true in user's
|
* Only remove ALL product and page data if WC_REMOVE_ALL_DATA constant is set to true in user's
|
||||||
|
|
Loading…
Reference in New Issue