Merge branch 'release/2.3.4' into develop
0
assets/css/about.css
Executable file → Normal file
0
assets/css/admin.css
Executable file → Normal file
0
assets/css/checkout.css
Executable file → Normal file
0
assets/css/dashboard.css
Executable file → Normal file
0
assets/css/view-subscription.css
Executable file → Normal file
0
assets/css/wcs-upgrade.css
Executable file → Normal file
0
assets/images/add-edit-subscription-screen.png
Executable file → Normal file
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
0
assets/images/admin-change-payment-method.jpg
Executable file → Normal file
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
0
assets/images/ajax-loader.gif
Executable file → Normal file
Before Width: | Height: | Size: 885 B After Width: | Height: | Size: 885 B |
0
assets/images/ajax-loader@2x.gif
Executable file → Normal file
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
0
assets/images/billing-schedules-meta-box.png
Executable file → Normal file
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
0
assets/images/checkout-recurring-totals.png
Executable file → Normal file
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
0
assets/images/drip-downloadable-content.jpg
Executable file → Normal file
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
0
assets/images/gift-subscription.png
Executable file → Normal file
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 122 KiB |
0
assets/images/renewal-retry-settings.png
Executable file → Normal file
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 109 KiB |
0
assets/images/subscribe-all-the-things.png
Executable file → Normal file
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
0
assets/images/subscription-reports.png
Executable file → Normal file
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 133 KiB |
0
assets/images/subscription-suspended-email.jpg
Executable file → Normal file
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 172 KiB |
0
assets/images/subscriptions-importer-exporter.png
Executable file → Normal file
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
0
assets/images/view-subscription.png
Executable file → Normal file
Before Width: | Height: | Size: 250 KiB After Width: | Height: | Size: 250 KiB |
0
assets/images/woocommerce_subscriptions_logo.png
Executable file → Normal file
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
0
assets/js/admin/admin-pointers.js
Executable file → Normal file
0
assets/js/admin/admin.js
Executable file → Normal file
0
assets/js/admin/jquery.flot.axislabels.js
Executable file → Normal file
0
assets/js/admin/jquery.flot.axislabels.min.js
vendored
Executable file → Normal file
0
assets/js/admin/jquery.flot.orderBars.js
Executable file → Normal file
0
assets/js/admin/jquery.flot.orderBars.min.js
vendored
Executable file → Normal file
0
assets/js/admin/jstz.js
Executable file → Normal file
0
assets/js/admin/jstz.min.js
vendored
Executable file → Normal file
0
assets/js/admin/meta-boxes-coupon.js
Executable file → Normal file
0
assets/js/admin/meta-boxes-subscription.js
Executable file → Normal file
0
assets/js/admin/moment.js
Executable file → Normal file
0
assets/js/admin/moment.min.js
vendored
Executable file → Normal file
0
assets/js/admin/reports.js
Executable file → Normal file
0
assets/js/admin/wcs-meta-boxes-order.js
Executable file → Normal file
0
assets/js/wcs-upgrade.js
Executable file → Normal file
18
changelog.txt
Executable file → Normal file
@@ -1,5 +1,23 @@
|
||||
*** WooCommerce Subscriptions Changelog ***
|
||||
|
||||
2018.08.10 - version 2.3.4
|
||||
* New: Add filter to bypass coupon removal for recurring carts. PR#2832
|
||||
* Fix: Check if subscription is payable before populating the cart with a renewal order. PR#2838
|
||||
* Fix: Add a margin to the bottom of subscription information email table to better align with other sections of the email. PR#2853
|
||||
* Fix: Prevent infinite loops which can occur while trying to repair PayPal Standard subscriptions with `S-****` Profile IDs. PR#2848
|
||||
* Fix: [WC3.4.4] Check refunded order totals in a WC version compatible way. Fixes an issue where limited coupons weren't removed after reaching their limit. PR#2850
|
||||
* Tweak: Require payment for recurring carts which contain limited coupons which will expire and then require payment. PR#2850
|
||||
|
||||
2018.07.24 - version 2.3.3
|
||||
* Fix: Prevent infinite loops while generating variable product min and max data. PR#2810
|
||||
* Fix: Filter renewal order cart shipping packages, removing one time shipping products from shipping calculations. PR#2811
|
||||
* Fix: Only generate persistent related order and customer subscription caches via the Transients API to avoid the possibility of retrieving out of date data stored in the option. PR#2823
|
||||
* Fix: Update WC_Subscriptions_Order::maybe_autocomplete_order() to allow for it to handle orders before they have an ID (before they are inserted). PR#2784
|
||||
* Fix: Adjust for possible differences in billing cycles because of rounding when calculating prorated totals for switch requests. PR#2617
|
||||
* Tweak: Improve UX for variation selection when limited variable product is in cart. PR#2683
|
||||
* Tweak: Remove code which supported for WC 2.5. PR#2806
|
||||
* Tweak: Enforce array return value for `pre_get_users_subscriptions` filter. PR#2814
|
||||
|
||||
2018.07.10 - version 2.3.2
|
||||
* Fix: Extend the next payment date after early renewal if there's no end date. PR#2804
|
||||
* Fix: Prevent throwing type hinting errors when getting related orders from cache. RR#2805
|
||||
|
1252
composer.lock
generated
Normal file
0
includes/abstracts/abstract-wcs-background-updater.php
Executable file → Normal file
0
includes/abstracts/abstract-wcs-background-upgrader.php
Executable file → Normal file
0
includes/abstracts/abstract-wcs-cache-manager.php
Executable file → Normal file
0
includes/abstracts/abstract-wcs-customer-store.php
Executable file → Normal file
0
includes/abstracts/abstract-wcs-debug-tool-cache-updater.php
Executable file → Normal file
0
includes/abstracts/abstract-wcs-debug-tool.php
Executable file → Normal file
0
includes/abstracts/abstract-wcs-dynamic-hook-deprecator.php
Executable file → Normal file
0
includes/abstracts/abstract-wcs-hook-deprecator.php
Executable file → Normal file
0
includes/abstracts/abstract-wcs-related-order-store.php
Executable file → Normal file
0
includes/abstracts/abstract-wcs-retry-store.php
Executable file → Normal file
0
includes/abstracts/abstract-wcs-scheduler.php
Executable file → Normal file
0
includes/admin/class-wc-subscriptions-admin.php
Executable file → Normal file
0
includes/admin/class-wcs-admin-meta-boxes.php
Executable file → Normal file
0
includes/admin/class-wcs-admin-notice.php
Executable file → Normal file
0
includes/admin/class-wcs-admin-post-types.php
Executable file → Normal file
0
includes/admin/class-wcs-admin-reports.php
Executable file → Normal file
0
includes/admin/class-wcs-admin-system-status.php
Executable file → Normal file
0
includes/admin/debug-tools/class-wcs-debug-tool-cache-background-updater.php
Executable file → Normal file
0
includes/admin/debug-tools/class-wcs-debug-tool-cache-eraser.php
Executable file → Normal file
0
includes/admin/debug-tools/class-wcs-debug-tool-cache-generator.php
Executable file → Normal file
0
includes/admin/debug-tools/class-wcs-debug-tool-factory.php
Executable file → Normal file
0
includes/admin/meta-boxes/class-wcs-meta-box-payment-retries.php
Executable file → Normal file
0
includes/admin/meta-boxes/class-wcs-meta-box-related-orders.php
Executable file → Normal file
0
includes/admin/meta-boxes/class-wcs-meta-box-subscription-data.php
Executable file → Normal file
0
includes/admin/meta-boxes/class-wcs-meta-box-subscription-schedule.php
Executable file → Normal file
0
includes/admin/meta-boxes/views/html-related-orders-row.php
Executable file → Normal file
0
includes/admin/meta-boxes/views/html-related-orders-table.php
Executable file → Normal file
0
includes/admin/meta-boxes/views/html-retries-table.php
Executable file → Normal file
0
includes/admin/meta-boxes/views/html-subscription-schedule.php
Executable file → Normal file
0
includes/admin/reports/class-wcs-report-cache-manager.php
Executable file → Normal file
0
includes/admin/reports/class-wcs-report-dashboard.php
Executable file → Normal file
0
includes/admin/reports/class-wcs-report-retention-rate.php
Executable file → Normal file
0
includes/admin/reports/class-wcs-report-subscription-by-customer.php
Executable file → Normal file
0
includes/admin/reports/class-wcs-report-subscription-by-product.php
Executable file → Normal file
0
includes/admin/reports/class-wcs-report-subscription-events-by-date.php
Executable file → Normal file
0
includes/admin/reports/class-wcs-report-subscription-payment-retry.php
Executable file → Normal file
0
includes/admin/reports/class-wcs-report-upcoming-recurring-revenue.php
Executable file → Normal file
0
includes/admin/views/html-report-by-period.php
Executable file → Normal file
0
includes/admin/wcs-admin-functions.php
Executable file → Normal file
0
includes/api/class-wc-rest-subscription-notes-controller.php
Executable file → Normal file
0
includes/api/class-wc-rest-subscriptions-controller.php
Executable file → Normal file
0
includes/api/legacy/class-wc-api-subscriptions-customers.php
Executable file → Normal file
0
includes/api/legacy/class-wc-api-subscriptions.php
Executable file → Normal file
0
includes/api/legacy/class-wc-rest-subscription-notes-controller.php
Executable file → Normal file
0
includes/api/legacy/class-wc-rest-subscriptions-controller.php
Executable file → Normal file
0
includes/class-wc-order-item-pending-switch.php
Executable file → Normal file
0
includes/class-wc-product-subscription-variation.php
Executable file → Normal file
0
includes/class-wc-product-subscription.php
Executable file → Normal file
0
includes/class-wc-product-variable-subscription.php
Executable file → Normal file
2
includes/class-wc-subscription.php
Executable file → Normal file
@@ -731,7 +731,7 @@ class WC_Subscription extends WC_Order {
|
||||
/**
|
||||
* Get suspension count.
|
||||
*
|
||||
* @return string
|
||||
* @return int
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public function get_suspension_count( $context = 'view' ) {
|
||||
|
0
includes/class-wc-subscriptions-addresses.php
Executable file → Normal file
21
includes/class-wc-subscriptions-cart.php
Executable file → Normal file
@@ -846,29 +846,26 @@ class WC_Subscriptions_Cart {
|
||||
* @return bool
|
||||
*/
|
||||
public static function cart_needs_payment( $needs_payment, $cart ) {
|
||||
|
||||
if ( false === $needs_payment && self::cart_contains_subscription() && $cart->total == 0 && false === WC_Subscriptions_Switcher::cart_contains_switches() && 'yes' !== get_option( WC_Subscriptions_Admin::$option_prefix . '_turn_off_automatic_payments', 'no' ) ) {
|
||||
|
||||
$recurring_total = 0;
|
||||
$is_one_period = true;
|
||||
$is_synced = false;
|
||||
$contains_synced = false;
|
||||
$contains_expiring_limited_coupon = false;
|
||||
|
||||
foreach ( WC()->cart->recurring_carts as $cart ) {
|
||||
foreach ( WC()->cart->recurring_carts as $recurring_cart ) {
|
||||
$recurring_total += $recurring_cart->total;
|
||||
$subscription_length = wcs_cart_pluck( $recurring_cart, 'subscription_length' );
|
||||
$contains_synced = $contains_synced || (bool) WC_Subscriptions_Synchroniser::cart_contains_synced_subscription( $recurring_cart );
|
||||
$contains_expiring_limited_coupon = $contains_expiring_limited_coupon || WC_Subscriptions_Coupon::recurring_cart_contains_expiring_coupon( $recurring_cart );
|
||||
|
||||
$recurring_total += $cart->total;
|
||||
|
||||
$cart_length = wcs_cart_pluck( $cart, 'subscription_length' );
|
||||
|
||||
if ( 0 == $cart_length || wcs_cart_pluck( $cart, 'subscription_period_interval' ) != $cart_length ) {
|
||||
if ( 0 == $subscription_length || wcs_cart_pluck( $recurring_cart, 'subscription_period_interval' ) != $subscription_length ) {
|
||||
$is_one_period = false;
|
||||
}
|
||||
|
||||
$is_synced = ( $is_synced || false != WC_Subscriptions_Synchroniser::cart_contains_synced_subscription( $cart ) ) ? true : false;
|
||||
}
|
||||
|
||||
$has_trial = self::cart_contains_free_trial();
|
||||
|
||||
if ( $recurring_total > 0 && ( false === $is_one_period || true === $has_trial || ( false !== $is_synced && false == WC_Subscriptions_Synchroniser::is_today( WC_Subscriptions_Synchroniser::calculate_first_payment_date( $is_synced['data'], 'timestamp' ) ) ) ) ) {
|
||||
if ( $contains_expiring_limited_coupon || $recurring_total > 0 && ( ! $is_one_period || $has_trial || $contains_synced ) ) {
|
||||
$needs_payment = true;
|
||||
}
|
||||
}
|
||||
|
0
includes/class-wc-subscriptions-change-payment-gateway.php
Executable file → Normal file
0
includes/class-wc-subscriptions-checkout.php
Executable file → Normal file
68
includes/class-wc-subscriptions-coupon.php
Executable file → Normal file
@@ -196,8 +196,8 @@ class WC_Subscriptions_Coupon {
|
||||
if ( ! wcs_cart_contains_renewal() && ! WC_Subscriptions_Product::is_subscription( $cart_item['data'] ) ) {
|
||||
return $discount;
|
||||
}
|
||||
// But if cart contains a renewal, we need to handle both subscription products and manually added non-susbscription products that could be part of a subscription
|
||||
if ( wcs_cart_contains_renewal() && ! self::is_subsbcription_renewal_line_item( $cart_item['data'], $cart_item ) ) {
|
||||
// But if cart contains a renewal, we need to handle both subscription products and manually added non-subscription products that could be part of a subscription
|
||||
if ( wcs_cart_contains_renewal() && ! self::is_subscription_renewal_line_item( $cart_item['data'], $cart_item ) ) {
|
||||
return $discount;
|
||||
}
|
||||
|
||||
@@ -552,6 +552,19 @@ class WC_Subscriptions_Coupon {
|
||||
foreach ( $applied_coupons as $coupon_code ) {
|
||||
$coupon = new WC_Coupon( $coupon_code );
|
||||
$coupon_type = wcs_get_coupon_property( $coupon, 'discount_type' );
|
||||
|
||||
/**
|
||||
* Filters whether the coupon should be allowed to be removed.
|
||||
*
|
||||
* @param bool $bypass_removal Whether to bypass removing the coupon.
|
||||
* @param WC_Coupon $coupon The coupon object.
|
||||
* @param string $coupon_type The coupon's discount_type property.
|
||||
* @param string $calculation_type The current calculation type.
|
||||
*/
|
||||
if ( apply_filters( 'wcs_bypass_coupon_removal', false, $coupon, $coupon_type, $calculation_type ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! isset( self::$recurring_coupons[ $coupon_type ] ) ) {
|
||||
$cart->remove_coupon( $coupon_code );
|
||||
continue;
|
||||
@@ -625,15 +638,14 @@ class WC_Subscriptions_Coupon {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a product is a renewal order line item (rather than a "susbscription") - to pick up non-subsbcription products added a subscription manually
|
||||
* Check if a product is a renewal order line item (rather than a "subscription") - to pick up non-subscription products added to a subscription manually
|
||||
*
|
||||
* @param int|WC_Product $product_id
|
||||
* @param array $cart_item
|
||||
* @param WC_Cart $cart The WooCommerce cart object.
|
||||
* @return boolean whether a product is a renewal order line item
|
||||
* @since 2.0.10
|
||||
*/
|
||||
private static function is_subsbcription_renewal_line_item( $product_id, $cart_item ) {
|
||||
private static function is_subscription_renewal_line_item( $product_id, $cart_item ) {
|
||||
|
||||
$is_subscription_line_item = false;
|
||||
|
||||
@@ -732,6 +744,43 @@ class WC_Subscriptions_Coupon {
|
||||
return $has_coupon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a given recurring cart contains a limited use coupon which when applied to a subscription will reach its usage limit within the subscription's length.
|
||||
*
|
||||
* @param WC_Cart $recurring_cart The recurring cart object.
|
||||
* @return bool
|
||||
*/
|
||||
public static function recurring_cart_contains_expiring_coupon( $recurring_cart ) {
|
||||
$limited_recurring_coupons = array();
|
||||
|
||||
if ( isset( $recurring_cart->applied_coupons ) ) {
|
||||
$limited_recurring_coupons = array_filter( $recurring_cart->applied_coupons, array( __CLASS__, 'coupon_is_limited' ) );
|
||||
}
|
||||
|
||||
// Bail early if there are no limited coupons applied to the recurring cart or if there is no discount provided.
|
||||
if ( empty( $limited_recurring_coupons ) || ! $recurring_cart->discount_cart ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$has_expiring_coupon = false;
|
||||
$subscription_length = wcs_cart_pluck( $recurring_cart, 'subscription_length' );
|
||||
$subscription_payments = $subscription_length / wcs_cart_pluck( $recurring_cart, 'subscription_period_interval' );
|
||||
|
||||
// Limited recurring coupons will always expire at some point on subscriptions with no length.
|
||||
if ( empty( $subscription_length ) ) {
|
||||
$has_expiring_coupon = true;
|
||||
} else {
|
||||
foreach ( $limited_recurring_coupons as $code ) {
|
||||
if ( WC_Subscriptions_Coupon::get_coupon_limit( $code ) < $subscription_payments ) {
|
||||
$has_expiring_coupon = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $has_expiring_coupon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a given coupon is limited to a certain number of renewals.
|
||||
*
|
||||
@@ -951,7 +1000,7 @@ class WC_Subscriptions_Coupon {
|
||||
*/
|
||||
$refunded = $order->get_total_refunded();
|
||||
$total = $order->get_total();
|
||||
if ( null !== $refunded && $total == $refunded ) {
|
||||
if ( $refunded && $total == $refunded ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1046,12 +1095,7 @@ class WC_Subscriptions_Coupon {
|
||||
$coupon_type = wcs_get_coupon_property( $coupon, 'discount_type' );
|
||||
$coupon_amount = wcs_get_coupon_property( $coupon, 'coupon_amount' );
|
||||
|
||||
// Pre 2.5 is_valid_for_product() does not use wc_get_product_coupon_types()
|
||||
if ( WC_Subscriptions::is_woocommerce_pre( '2.5' ) ) {
|
||||
$is_valid_for_product = true;
|
||||
} else {
|
||||
$is_valid_for_product = $coupon->is_valid_for_product( $cart_item['data'], $cart_item );
|
||||
}
|
||||
$is_valid_for_product = $coupon->is_valid_for_product( $cart_item['data'], $cart_item );
|
||||
|
||||
if ( $coupon->apply_before_tax() && $coupon->is_valid() && $is_valid_for_product ) {
|
||||
|
||||
|
34
includes/class-wc-subscriptions-email.php
Executable file → Normal file
@@ -237,31 +237,23 @@ class WC_Subscriptions_Email {
|
||||
}
|
||||
|
||||
if ( is_a( $order, 'WC_Abstract_Order' ) ) {
|
||||
$show_download_links_callback = ( isset( $args['show_download_links'] ) && $args['show_download_links'] ) ? '__return_true' : '__return_false';
|
||||
$show_purchase_note_callback = ( isset( $args['show_purchase_note'] ) && $args['show_purchase_note'] ) ? '__return_true' : '__return_false';
|
||||
|
||||
if ( WC_Subscriptions::is_woocommerce_pre( '2.5' ) ) {
|
||||
unset( $args['show_download_links'] );
|
||||
unset( $args['show_purchase_note'] );
|
||||
|
||||
$items_table = call_user_func_array( array( $order, 'email_order_items_table' ), $args );
|
||||
add_filter( 'woocommerce_order_is_download_permitted', $show_download_links_callback );
|
||||
add_filter( 'woocommerce_order_is_paid', $show_purchase_note_callback );
|
||||
|
||||
if ( function_exists( 'wc_get_email_order_items' ) ) { // WC 3.0+
|
||||
$items_table = wc_get_email_order_items( $order, $args );
|
||||
} else {
|
||||
|
||||
// 2.5 doesn't support both the show_download_links or show_purchase_note parameters but uses $order->is_download_permitted and $order->is_paid instead
|
||||
$show_download_links_callback = ( isset( $args['show_download_links'] ) && $args['show_download_links'] ) ? '__return_true' : '__return_false';
|
||||
$show_purchase_note_callback = ( isset( $args['show_purchase_note'] ) && $args['show_purchase_note'] ) ? '__return_true' : '__return_false';
|
||||
|
||||
unset( $args['show_download_links'] );
|
||||
unset( $args['show_purchase_note'] );
|
||||
|
||||
add_filter( 'woocommerce_order_is_download_permitted', $show_download_links_callback );
|
||||
add_filter( 'woocommerce_order_is_paid', $show_purchase_note_callback );
|
||||
|
||||
if ( function_exists( 'wc_get_email_order_items' ) ) { // WC 3.0+
|
||||
$items_table = wc_get_email_order_items( $order, $args );
|
||||
} else {
|
||||
$items_table = $order->email_order_items_table( $args );
|
||||
}
|
||||
|
||||
remove_filter( 'woocommerce_order_is_download_permitted', $show_download_links_callback );
|
||||
remove_filter( 'woocommerce_order_is_paid', $show_purchase_note_callback );
|
||||
$items_table = $order->email_order_items_table( $args );
|
||||
}
|
||||
|
||||
remove_filter( 'woocommerce_order_is_download_permitted', $show_download_links_callback );
|
||||
remove_filter( 'woocommerce_order_is_paid', $show_purchase_note_callback );
|
||||
}
|
||||
|
||||
return $items_table;
|
||||
|
0
includes/class-wc-subscriptions-manager.php
Executable file → Normal file
100
includes/class-wc-subscriptions-order.php
Executable file → Normal file
@@ -67,13 +67,12 @@ class WC_Subscriptions_Order {
|
||||
|
||||
add_filter( 'woocommerce_my_account_my_orders_actions', __CLASS__ . '::maybe_remove_pay_action', 10, 2 );
|
||||
|
||||
add_action( 'woocommerce_order_partially_refunded', __CLASS__ . '::maybe_cancel_subscription_on_partial_refund' );
|
||||
add_action( 'woocommerce_order_fully_refunded', __CLASS__ . '::maybe_cancel_subscription_on_full_refund' );
|
||||
|
||||
add_filter( 'woocommerce_order_needs_shipping_address', __CLASS__ . '::maybe_display_shipping_address', 10, 3 );
|
||||
|
||||
// Autocomplete subscription orders when they only contain a synchronised subscription or a resubscribe
|
||||
add_filter( 'woocommerce_payment_complete_order_status', __CLASS__ . '::maybe_autocomplete_order', 10, 2 );
|
||||
add_filter( 'woocommerce_payment_complete_order_status', __CLASS__ . '::maybe_autocomplete_order', 10, 3 );
|
||||
|
||||
add_filter( 'woocommerce_order_data_store_cpt_get_orders_query', array( __CLASS__, 'add_subscription_order_query_args' ), 10, 2 );
|
||||
}
|
||||
@@ -981,27 +980,10 @@ class WC_Subscriptions_Order {
|
||||
* @param $order_id
|
||||
*
|
||||
* @since 2.0
|
||||
* @deprecated 2.3.3
|
||||
*/
|
||||
public static function maybe_cancel_subscription_on_partial_refund( $order_id ) {
|
||||
|
||||
if ( WC_Subscriptions::is_woocommerce_pre( '2.5' ) && wcs_order_contains_subscription( $order_id, array( 'parent', 'renewal' ) ) ) {
|
||||
|
||||
$order = wc_get_order( $order_id );
|
||||
$remaining_order_total = wc_format_decimal( $order->get_total() - $order->get_total_refunded() );
|
||||
$remaining_order_items = absint( $order->get_item_count() - $order->get_item_count_refunded() );
|
||||
$order_has_free_item = false;
|
||||
|
||||
foreach ( $order->get_items() as $item ) {
|
||||
if ( ! $item['line_total'] ) {
|
||||
$order_has_free_item = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! ( $remaining_order_total > 0 || ( $order_has_free_item && $remaining_order_items > 0 ) ) ) {
|
||||
self::maybe_cancel_subscription_on_full_refund( $order );
|
||||
}
|
||||
}
|
||||
wcs_deprecated_function( __METHOD__, '2.3.3' );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1041,51 +1023,61 @@ class WC_Subscriptions_Order {
|
||||
* Automatically set the order's status to complete if the order total is zero and all the subscriptions
|
||||
* in an order are synced or the order contains a resubscribe.
|
||||
*
|
||||
* @param string $new_order_status
|
||||
* @param int $order_id
|
||||
* @param string $new_order_status
|
||||
* @param int $order_id
|
||||
* @param WC_Order $order
|
||||
*
|
||||
* @return string $new_order_status
|
||||
*
|
||||
* @since 2.1.3
|
||||
*/
|
||||
public static function maybe_autocomplete_order( $new_order_status, $order_id ) {
|
||||
public static function maybe_autocomplete_order( $new_order_status, $order_id, $order = null ) {
|
||||
// Exit early if the order has no ID, or if the new order status is not 'processing'.
|
||||
if ( 0 === $order_id || 'processing' !== $new_order_status ) {
|
||||
return $new_order_status;
|
||||
}
|
||||
|
||||
// Guard against infinite loops in WC 3.0+ where woocommerce_payment_complete_order_status is called while instantiating WC_Order objects
|
||||
remove_filter( 'woocommerce_payment_complete_order_status', __METHOD__, 10 );
|
||||
$order = wc_get_order( $order_id );
|
||||
add_filter( 'woocommerce_payment_complete_order_status', __METHOD__, 10, 2 );
|
||||
if ( null === $order ) {
|
||||
remove_filter( 'woocommerce_payment_complete_order_status', __METHOD__, 10 );
|
||||
$order = wc_get_order( $order_id );
|
||||
add_filter( 'woocommerce_payment_complete_order_status', __METHOD__, 10, 3 );
|
||||
}
|
||||
|
||||
if ( 'processing' == $new_order_status && $order->get_subtotal() == 0 && wcs_order_contains_subscription( $order ) ) {
|
||||
// Exit early if the order subtotal is not zero, or if the order does not contain a subscription.
|
||||
if ( 0 != $order->get_subtotal() || ! wcs_order_contains_subscription( $order ) ) {
|
||||
return $new_order_status;
|
||||
}
|
||||
|
||||
if ( wcs_order_contains_resubscribe( $order ) ) {
|
||||
if ( wcs_order_contains_resubscribe( $order ) ) {
|
||||
$new_order_status = 'completed';
|
||||
} elseif ( wcs_order_contains_switch( $order ) ) {
|
||||
$all_switched = true;
|
||||
|
||||
foreach ( $order->get_items() as $item ) {
|
||||
if ( ! isset( $item['switched_subscription_price_prorated'] ) ) {
|
||||
$all_switched = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $all_switched || 1 == count( $order->get_items() ) ) {
|
||||
$new_order_status = 'completed';
|
||||
} elseif ( wcs_order_contains_switch( $order ) ) {
|
||||
$all_switched = true;
|
||||
}
|
||||
} else {
|
||||
$subscriptions = wcs_get_subscriptions_for_order( $order_id );
|
||||
$all_synced = true;
|
||||
|
||||
foreach ( $order->get_items() as $item ) {
|
||||
if ( ! isset( $item['switched_subscription_price_prorated'] ) ) {
|
||||
$all_switched = false;
|
||||
break;
|
||||
}
|
||||
foreach ( $subscriptions as $subscription_id => $subscription ) {
|
||||
|
||||
if ( ! WC_Subscriptions_Synchroniser::subscription_contains_synced_product( $subscription_id ) ) {
|
||||
$all_synced = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $all_switched || 1 == count( $order->get_items() ) ) {
|
||||
$new_order_status = 'completed';
|
||||
}
|
||||
} else {
|
||||
$subscriptions = wcs_get_subscriptions_for_order( $order_id );
|
||||
$all_synced = true;
|
||||
|
||||
foreach ( $subscriptions as $subscription_id => $subscription ) {
|
||||
|
||||
if ( ! WC_Subscriptions_Synchroniser::subscription_contains_synced_product( $subscription_id ) ) {
|
||||
$all_synced = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $all_synced ) {
|
||||
$new_order_status = 'completed';
|
||||
}
|
||||
if ( $all_synced ) {
|
||||
$new_order_status = 'completed';
|
||||
}
|
||||
}
|
||||
|
||||
|
5
includes/class-wc-subscriptions-product.php
Executable file → Normal file
@@ -887,11 +887,6 @@ class WC_Subscriptions_Product {
|
||||
|
||||
if ( ! isset( $data['value'] ) ) {
|
||||
return;
|
||||
} elseif ( WC_Subscriptions::is_woocommerce_pre( '2.5' ) ) {
|
||||
// Pre 2.5 we don't have the product type information available so we have to check if it is a subscription - downside here is this only works if the product has already been saved
|
||||
if ( ! self::is_subscription( $variable_product_id ) ) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Since 2.5 we have the product type information available so we don't have to wait for the product to be saved to check if it is a subscription
|
||||
if ( empty( $_POST['security'] ) || ! wp_verify_nonce( $_POST['security'], 'bulk-edit-variations' ) || 'variable-subscription' !== $_POST['product_type'] ) {
|
||||
|
0
includes/class-wc-subscriptions-renewal-order.php
Executable file → Normal file
41
includes/class-wc-subscriptions-switcher.php
Executable file → Normal file
@@ -1372,16 +1372,12 @@ class WC_Subscriptions_Switcher {
|
||||
// Find the $price per day for the old subscription's recurring total
|
||||
$old_price_per_day = $days_in_old_cycle > 0 ? $old_recurring_total / $days_in_old_cycle : $old_recurring_total;
|
||||
|
||||
// Find the price per day for the new subscription's recurring total
|
||||
// If the subscription uses the same billing interval & cycle as the old subscription,
|
||||
if ( WC_Subscriptions_Product::get_period( $item_data ) == $subscription->get_billing_period() && WC_Subscriptions_Product::get_interval( $item_data ) == $subscription->get_billing_interval() ) {
|
||||
// Find the price per day for the new subscription's recurring total based on billing schedule
|
||||
$days_in_new_cycle = wcs_get_days_in_cycle( WC_Subscriptions_Product::get_period( $item_data ), WC_Subscriptions_Product::get_interval( $item_data ) );
|
||||
|
||||
$days_in_new_cycle = $days_in_old_cycle; // Use $days_in_old_cycle to make sure they're consistent
|
||||
|
||||
} else {
|
||||
|
||||
// We need to figure out the price per day for the new subscription based on its billing schedule
|
||||
$days_in_new_cycle = wcs_get_days_in_cycle( WC_Subscriptions_Product::get_period( $item_data ), WC_Subscriptions_Product::get_interval( $item_data ) );
|
||||
// Set days in new cycle to days in old if it only differs because of rounding
|
||||
if ( ceil( $days_in_new_cycle ) == $days_in_old_cycle || floor( $days_in_new_cycle ) == $days_in_old_cycle ) {
|
||||
$days_in_new_cycle = $days_in_old_cycle;
|
||||
}
|
||||
|
||||
// We need to use the cart items price to ensure we include extras added by extensions like Product Add-ons, but we don't want the sign-up fee accounted for in the price, so make sure WC_Subscriptions_Cart::set_subscription_prices_for_calculation() isn't adding that.
|
||||
@@ -1410,12 +1406,7 @@ class WC_Subscriptions_Switcher {
|
||||
|
||||
// Find out how many days at the new price per day the customer would receive for the total amount already paid
|
||||
// (e.g. if the customer paid $10 / month previously, and was switching to a $5 / week subscription, she has pre-paid 14 days at the new price)
|
||||
$pre_paid_days = $new_total_paid = 0;
|
||||
|
||||
while ( $new_total_paid < $old_recurring_total ) {
|
||||
$pre_paid_days++;
|
||||
$new_total_paid = $pre_paid_days * $new_price_per_day;
|
||||
}
|
||||
$pre_paid_days = self::calculate_pre_paid_days( $old_recurring_total, $new_price_per_day );
|
||||
|
||||
// If the total amount the customer has paid entitles her to more days at the new price than she has received, there is no gap payment, just shorten the pre-paid term the appropriate number of days
|
||||
if ( $days_since_last_payment < $pre_paid_days ) {
|
||||
@@ -1461,15 +1452,12 @@ class WC_Subscriptions_Switcher {
|
||||
} elseif ( $old_price_per_day > $new_price_per_day && $new_price_per_day > 0 ) {
|
||||
|
||||
$old_total_paid = $old_price_per_day * $days_until_next_payment;
|
||||
$new_total_paid = $new_price_per_day;
|
||||
|
||||
// if downgrades are apportioned, extend the next payment date for n more days
|
||||
if ( in_array( $apportion_recurring_price, array( 'virtual', 'yes' ) ) ) {
|
||||
|
||||
// Find how many more days at the new lower price it takes to exceed the amount already paid
|
||||
for ( $days_to_add = 0; $new_total_paid <= $old_total_paid; $days_to_add++ ) {
|
||||
$new_total_paid = $days_to_add * $new_price_per_day;
|
||||
}
|
||||
$days_to_add = self::calculate_pre_paid_days( $old_total_paid, $new_price_per_day );
|
||||
|
||||
$days_to_add -= $days_until_next_payment;
|
||||
} else {
|
||||
@@ -1501,6 +1489,21 @@ class WC_Subscriptions_Switcher {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the number of days that have already been paid
|
||||
*
|
||||
* @param int $old_total_paid The amount paid previously, such as the old recurring total
|
||||
* @param int $new_price_per_day The amount per day price for the new subscription
|
||||
* @return int $pre_paid_days The number of days paid for already
|
||||
*/
|
||||
private static function calculate_pre_paid_days( $old_total_paid, $new_price_per_day ) {
|
||||
$pre_paid_days = 0;
|
||||
if ( 0 != $new_price_per_day ) {
|
||||
$pre_paid_days = ceil( $old_total_paid / $new_price_per_day );
|
||||
}
|
||||
return $pre_paid_days;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure when displaying the first payment date for a switched subscription, the date takes into
|
||||
* account the switch (i.e. prepaid days and possibly a downgrade).
|
||||
|