diff --git a/assets/js/admin/admin.js b/assets/js/admin/admin.js index 3eaf716..e3b56b8 100644 --- a/assets/js/admin/admin.js +++ b/assets/js/admin/admin.js @@ -356,35 +356,6 @@ jQuery(document).ready(function($){ $( '#_subscription_one_time_shipping' ).prop( 'disabled', is_synced_or_has_trial ); }, - show_hidden_panels: function() { - // WooCommerce's show_hidden_panels() function introduced a change in WC 2.6.2 which would hide the general panel for variable subscriptions, this fixes that by running the show_hidden_panels() logic again after variable fields have been show/hidden for variable subscriptions - $( '.woocommerce_options_panel' ).each( function() { - - if ( 'none' != $( this ).css( 'display' ) ) { - return; - } - - var $children = $( this ).children( '.options_group' ); - - if ( 0 === $children.length ) { - return; - } - - var $invisble = $children.filter( function() { - return 'none' === $( this ).css( 'display' ); - }); - - // Show panel if it's been mistakenly hidden panel - if ( $invisble.length != $children.length ) { - var $id = $( this ).prop( 'id' ); - $( '.product_data_tabs' ).find( 'li a[href="#' + $id + '"]' ).parent().show(); - } - - }); - - // Now select the first panel in case it's changed - $( 'ul.wc-tabs li:visible' ).eq( 0 ).find( 'a' ).click(); - }, }); $('.options_group.pricing ._sale_price_field .description').prepend(''); @@ -392,6 +363,7 @@ jQuery(document).ready(function($){ // Move the subscription pricing section to the same location as the normal pricing section $('.options_group.subscription_pricing').not('.variable_subscription_pricing .options_group.subscription_pricing').insertBefore($('.options_group.pricing:first')); $('.show_if_subscription.clear').insertAfter($('.options_group.subscription_pricing')); + $( '.show_if_variable' ).addClass( 'show_if_variable-subscription' ); // Move the subscription variation pricing section to a better location in the DOM on load if($('#variable_product_options .variable_subscription_pricing').length > 0) { @@ -413,7 +385,6 @@ jQuery(document).ready(function($){ $.setTrialPeriods(); $.showHideSyncOptions(); $.disableEnableOneTimeShipping(); - $.show_hidden_panels(); } // Update subscription ranges when subscription period or interval is changed @@ -433,7 +404,6 @@ jQuery(document).ready(function($){ $.showHideSubscriptionMeta(); $.showHideVariableSubscriptionMeta(); $.showHideSyncOptions(); - $.show_hidden_panels(); }); $('input#_downloadable, input#_virtual').change(function(){ @@ -484,7 +454,16 @@ jQuery(document).ready(function($){ $('.order_actions .submitdelete').click(function(){ if($('[name="contains_subscription"]').val()=='true'){ - return confirm(WCSubscriptions.bulkTrashWarning); + return confirm(WCSubscriptions.trashWarning); + } + }); + + // Notify the store manager that trashing an order via the admin orders table row action also deletes the associated subscription if it exists + $( '.row-actions .submitdelete' ).click( function() { + var order = $( this ).closest( '.type-shop_order' ).attr( 'id' ); + + if ( true === $( '.contains_subscription', $( '#' + order ) ).data( 'contains_subscription' ) ) { + return confirm( WCSubscriptions.trashWarning ); } }); diff --git a/changelog.txt b/changelog.txt index 4971916..a4672a2 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,26 @@ *** WooCommerce Subscriptions Changelog *** +2016.09.23 - version 2.0.20 + * Tweak: add new 'woocommerce_subscription_before_actions' and 'woocommerce_subscription_after_actions' hooks to the view-subscription.php template. (PR#1608) + * Tweak: use WC_Subscriptions_Product::is_subscription() when checking if a product is sync'd instead of checking the product type directly so that the 'woocommerce_is_subscription' filter is applied and additional product types can add support for synchronisation. (PR#1635) + * Tweak: warn administrators when trashing subscription parent orders from row actions that the related subscriptions will also be trashed. (PR#1647) + * Tweak: add new 'woocommerce_subscriptions_admin_related_orders_to_display' filter to make it easier to manipulate the list of related orders on the Edit Subscription and Edit Order screens. (PR#1631) + * Tweak: add new 'woocommerce_subscriptions_switch_is_identical_product ' filter provide a simple way for 3rd party extensions to determine whether a product should be considered as identical or not, for the purposes of a switch. (PR#1659) + * Tweak: add new 'wcs_paypal_ipn_note' filter to make it easier to modify the order notes logged when processing PayPal IPN requests. @props David Marín (PR#1657) + * Tweak: display a link to Paypal's subscription page in the Subscription Billing Information section of the Edit Subscription administration screen. @props David Marín (PR#1658) + * Fix: display tax correctly for carts with only sync/trials products. (PR#1599) + * Fix: calcaulte tax correctly for prorated sign-up fee during 2...n switch. (PR#1607) + * Fix: remove duplicate call to get items from a subscription on the view-subscription.php template. (PR#1615) + * Fix: guard against fatal errors when 3rd party code calls WordPress' current_user_can() function with invalid parameters. (PR#1652) + * Fix: replace show_hidden_panels Javascript function which was inadvertently displaying 3rd party panels on Edit Product screen. (PR#1637) + * Fix: add the chosen recurring shipping method variable to the parameters passed to callbacks on 'woocommerce_subscriptions_after_recurring_shipping_rates'. (PR#1643) + * Fix: make sure non-logged in users can buy limited subscriptions by avoiding the call to get_posts() in wcs_get_users_subscriptions() when no user ID is specified and no user is logged in. (PR#1649) + * Fix: recalculate free shipping availability based on recurring cart object when applicable to fix issues with free shipping being available for the initial order but not recurring order, either becuase of price requirements or coupons. (PR#1549) + * Fix: make sure 'woocommerce_scheduled_subscription_end_of_prepaid_term' hook is unscheduled when a subscription with the pending cancellation status is trashed. (PR#1589) + * Fix: only get downloadable files from directly related subscriptions, not old subscriptions for subscriptions created by resubscribing. (PR#1612) + * Fix: variable subscription "From:" price when one variation with a higher ID has a longer free trial than the lower priced variation. (PR#1602) + * Fix: remove unused "Email Subject (paid)" and "Email Heading (paid)" fields from the Customer Renewal Invoice email settings page. (PR#1601) + 2016.08.12 - version 2.0.19 * Tweak: require new manually added subscriptions to have a customer to avoid issues with subscriptions created without a customer. (PR#1486) * Tweak: set expiration on caching transients to avoid them being autoloaded on every request and unnecessarily using memory on those requests. (PR#1540) diff --git a/includes/admin/class-wc-subscriptions-admin.php b/includes/admin/class-wc-subscriptions-admin.php index b7bcdb0..9ee7158 100644 --- a/includes/admin/class-wc-subscriptions-admin.php +++ b/includes/admin/class-wc-subscriptions-admin.php @@ -688,7 +688,8 @@ class WC_Subscriptions_Admin { $dependencies = array( 'jquery' ); - $woocommerce_admin_script_handle = 'wc-admin-meta-boxes'; + $woocommerce_admin_script_handle = 'wc-admin-meta-boxes'; + $trashing_subscription_order_warning = __( 'Trashing this order will also trash the subscriptions purchased with the order.', 'woocommerce-subscriptions' ); if ( $screen->id == 'product' ) { $dependencies[] = $woocommerce_admin_script_handle; @@ -709,6 +710,7 @@ class WC_Subscriptions_Admin { } else if ( 'edit-shop_order' == $screen->id ) { $script_params = array( 'bulkTrashWarning' => __( "You are about to trash one or more orders which contain a subscription.\n\nTrashing the orders will also trash the subscriptions purchased with these orders.", 'woocommerce-subscriptions' ), + 'trashWarning' => $trashing_subscription_order_warning, ); } else if ( 'shop_order' == $screen->id ) { $dependencies[] = $woocommerce_admin_script_handle; @@ -719,7 +721,7 @@ class WC_Subscriptions_Admin { } $script_params = array( - 'bulkTrashWarning' => __( 'Trashing this order will also trash the subscription purchased with the order.', 'woocommerce-subscriptions' ), + 'trashWarning' => $trashing_subscription_order_warning, 'changeMetaWarning' => __( "WARNING: Bad things are about to happen!\n\nThe payment gateway used to purchase this subscription does not support modifying a subscription's details.\n\nChanges to the billing period, recurring discount, recurring tax or recurring total may not be reflected in the amount charged by the payment gateway.", 'woocommerce-subscriptions' ), 'removeItemWarning' => __( 'You are deleting a subscription item. You will also need to manually cancel and trash the subscription on the Manage Subscriptions screen.', 'woocommerce-subscriptions' ), 'roundAtSubtotal' => esc_attr( get_option( 'woocommerce_tax_round_at_subtotal' ) ), diff --git a/includes/admin/meta-boxes/class-wcs-meta-box-related-orders.php b/includes/admin/meta-boxes/class-wcs-meta-box-related-orders.php index 5e73c32..5973902 100644 --- a/includes/admin/meta-boxes/class-wcs-meta-box-related-orders.php +++ b/includes/admin/meta-boxes/class-wcs-meta-box-related-orders.php @@ -110,6 +110,8 @@ class WCS_Meta_Box_Related_Orders { } } + $orders = apply_filters( 'woocommerce_subscriptions_admin_related_orders_to_display', $orders, $subscriptions, $post ); + foreach ( $orders as $order ) { if ( $order->id == $post->ID ) { continue; diff --git a/includes/class-wc-product-subscription-variation.php b/includes/class-wc-product-subscription-variation.php index ebee278..9f7d4cd 100644 --- a/includes/class-wc-product-subscription-variation.php +++ b/includes/class-wc-product-subscription-variation.php @@ -96,11 +96,11 @@ class WC_Product_Subscription_Variation extends WC_Product_Variation { * * @return string */ - public function get_sign_up_fee_including_tax( $qty = 1 ) { + public function get_sign_up_fee_including_tax( $qty = 1, $price = '' ) { add_filter( 'woocommerce_get_price', array( &$this, 'get_sign_up_fee' ), 100, 0 ); - $sign_up_fee_including_tax = parent::get_price_including_tax( $qty ); + $sign_up_fee_including_tax = parent::get_price_including_tax( $qty, $price ); remove_filter( 'woocommerce_get_price', array( &$this, 'get_sign_up_fee' ), 100, 0 ); @@ -113,11 +113,11 @@ class WC_Product_Subscription_Variation extends WC_Product_Variation { * * @return string */ - public function get_sign_up_fee_excluding_tax( $qty = 1 ) { + public function get_sign_up_fee_excluding_tax( $qty = 1, $price = '' ) { add_filter( 'woocommerce_get_price', array( &$this, 'get_sign_up_fee' ), 100, 0 ); - $sign_up_fee_excluding_tax = parent::get_price_excluding_tax( $qty ); + $sign_up_fee_excluding_tax = parent::get_price_excluding_tax( $qty, $price ); remove_filter( 'woocommerce_get_price', array( &$this, 'get_sign_up_fee' ), 100, 0 ); diff --git a/includes/class-wc-product-variable-subscription.php b/includes/class-wc-product-variable-subscription.php index be1f940..ab8f740 100644 --- a/includes/class-wc-product-variable-subscription.php +++ b/includes/class-wc-product-variable-subscription.php @@ -153,6 +153,16 @@ class WC_Product_Variable_Subscription extends WC_Product_Variable { $child_price = ( '' === $child_price ) ? 0 : $child_price; $child_sign_up_fee = ( '' === $child_sign_up_fee ) ? 0 : $child_sign_up_fee; + // Take taxes into a account to avoid displaying a variation with a lower price pre-tax, but higher price post-tax, in the case of variations having different tax classes (see: https://github.com/Prospress/woocommerce-subscriptions/pull/1602#issuecomment-241330131) + if ( $variation = $this->get_child( $child ) ) { + if ( $child_price > 0 ) { + $child_price = ( 'incl' == get_option( 'woocommerce_tax_display_shop' ) ) ? $variation->get_price_including_tax( 1, $child_price ) : $variation->get_price_excluding_tax( 1, $child_price ); + } + if ( $child_sign_up_fee > 0 ) { + $child_sign_up_fee = ( 'incl' == get_option( 'woocommerce_tax_display_shop' ) ) ? $variation->get_sign_up_fee_including_tax( 1, $child_sign_up_fee ) : $variation->get_sign_up_fee_excluding_tax( 1, $child_sign_up_fee ); + } + } + $has_free_trial = ( '' !== $child_free_trial_length && $child_free_trial_length > 0 ) ? true : false; // Determine some recurring price flags @@ -219,7 +229,7 @@ class WC_Product_Variable_Subscription extends WC_Product_Variable { $longest_trial_length = $child_free_trial_length; } - // If the current cheapest variation is also free then the shortest trial period is the most expensive + // If the current cheapest variation is also free, then the shortest trial period is the most expensive if ( 0 == $lowest_price || '' === $lowest_price ) { if ( '' === $shortest_trial_period ) { @@ -413,6 +423,35 @@ class WC_Product_Variable_Subscription extends WC_Product_Variable { } + /** + * Get the min or max variation (active) price. + * + * This is a copy of WooCommerce < 2.4's get_variation_price() method, because 2.4.0 introduced a new + * transient caching system which assumes asort() on prices yields correct results for min/max prices + * (which it does for prices alone, but that's not the full story for subscription prices). Unfortunately, + * the new caching system is also hard to hook into so we'll just use the old system instead as the + * @see self::variable_product_sync() uses the old method also. + * + * @param string $min_or_max - min or max + * @param boolean $display Whether the value is going to be displayed + * @return string + */ + public function get_variation_price( $min_or_max = 'min', $display = false ) { + $variation_id = get_post_meta( $this->id, '_' . $min_or_max . '_price_variation_id', true ); + + if ( $display ) { + if ( $variation = $this->get_child( $variation_id ) ) { + $price = ( 'incl' == get_option( 'woocommerce_tax_display_shop' ) ) ? $variation->get_price_including_tax() : $variation->get_price_excluding_tax(); + } else { + $price = ''; + } + } else { + $price = get_post_meta( $variation_id, '_price', true ); + } + + return apply_filters( 'woocommerce_get_variation_price', $price, $this, $min_or_max, $display ); + } + /** * Returns the price in html format. * diff --git a/includes/class-wc-subscription.php b/includes/class-wc-subscription.php index a051e5a..634603f 100644 --- a/includes/class-wc-subscription.php +++ b/includes/class-wc-subscription.php @@ -1661,11 +1661,11 @@ class WC_Subscription extends WC_Order { * with a 10 BTC sign-up fee was purchased, a total 30 BTC was paid as the sign-up fee but this function will return 10 BTC. * * @param array|int Either an order item (in the array format returned by self::get_items()) or the ID of an order item. - * @param string $tax Whether or not to adjust sign up fee if prices inc tax - ensures that the sign up fee paid amount includes the paid tax if inc + * @param string $tax_inclusive_or_exclusive Whether or not to adjust sign up fee if prices inc tax - ensures that the sign up fee paid amount includes the paid tax if inc * @return bool * @since 2.0 */ - public function get_items_sign_up_fee( $line_item, $tax = 'exclusive_of_tax' ) { + public function get_items_sign_up_fee( $line_item, $tax_inclusive_or_exclusive = 'exclusive_of_tax' ) { if ( ! is_array( $line_item ) ) { $line_item = wcs_get_order_item( $line_item, $this ); @@ -1705,12 +1705,13 @@ class WC_Subscription extends WC_Order { } // If prices inc tax, ensure that the sign up fee amount includes the tax - if ( 'inclusive_of_tax' === $tax && ! empty( $original_order_item ) && ( 'yes' == $this->prices_include_tax || true === $this->prices_include_tax ) ) { - $sign_up_fee += $original_order_item['line_tax']; + if ( 'inclusive_of_tax' === $tax_inclusive_or_exclusive && ! empty( $original_order_item ) && 'yes' == $this->prices_include_tax ) { + $proportion = $sign_up_fee / ( $original_order_item['line_total'] / $original_order_item['qty'] ); + $sign_up_fee += round( $original_order_item['line_tax'] * $proportion, 2 ); } } - return apply_filters( 'woocommerce_subscription_items_sign_up_fee', $sign_up_fee, $line_item, $this ); + return apply_filters( 'woocommerce_subscription_items_sign_up_fee', $sign_up_fee, $line_item, $this, $tax_inclusive_or_exclusive ); } /** diff --git a/includes/class-wc-subscriptions-cart.php b/includes/class-wc-subscriptions-cart.php index 0255599..7bc54f7 100644 --- a/includes/class-wc-subscriptions-cart.php +++ b/includes/class-wc-subscriptions-cart.php @@ -47,6 +47,13 @@ class WC_Subscriptions_Cart { */ private static $shipping_rates = array(); + /** + * A cache of the current recurring cart being calculated + * + * @since 2.0.20 + */ + private static $cached_recurring_cart = null; + /** * Bootstraps the class and hooks required actions & filters. * @@ -110,6 +117,9 @@ class WC_Subscriptions_Cart { // Validate chosen recurring shipping methods add_action( 'woocommerce_after_checkout_validation', __CLASS__ . '::validate_recurring_shipping_methods' ); + + // WooCommerce determines if free shipping is available using the WC->cart total and coupons, we need to recalculate its availability when obtaining shipping methods for a recurring cart + add_filter( 'woocommerce_shipping_free_shipping_is_available', __CLASS__ . '::maybe_recalculate_shipping_method_availability', 10, 2 ); } /** @@ -256,6 +266,9 @@ class WC_Subscriptions_Cart { $recurring_cart->next_payment_date = apply_filters( 'wcs_recurring_cart_next_payment_date', WC_Subscriptions_Product::get_first_renewal_payment_date( $product, $recurring_cart->start_date ), $recurring_cart, $product ); $recurring_cart->end_date = apply_filters( 'wcs_recurring_cart_end_date', WC_Subscriptions_Product::get_expiration_date( $product, $recurring_cart->start_date ), $recurring_cart, $product ); + // Before calculating recurring cart totals, store this recurring cart object + self::$cached_recurring_cart = $recurring_cart; + // No fees recur (yet) $recurring_cart->fees = array(); $recurring_cart->fee_total = 0; @@ -484,7 +497,7 @@ class WC_Subscriptions_Cart { $default_method = $chosen_methods[ $recurring_cart_package_key ]; // Set the chosen shipping method (if available) to workaround WC_Shipping::get_default_method() setting the default shipping method whenever method count changes - } elseif ( isset( $chosen_methods[ $package_index ] ) && $default_method !== $chosen_methods[ $package_index ] ) { + } elseif ( isset( $chosen_methods[ $package_index ] ) && $default_method !== $chosen_methods[ $package_index ] && isset( $available_methods[ $chosen_methods[ $package_index ] ] ) ) { $default_method = $chosen_methods[ $package_index ]; } @@ -1148,14 +1161,47 @@ class WC_Subscriptions_Cart { } /** - * Generate a unqiue package key for a given shipping package to be used for caching package rates. + * Generate a unique package key for a given shipping package to be used for caching package rates. * * @param array $package A shipping package in the form returned by WC_Cart->get_shipping_packages(). * @return string key hash * @since 2.0.18 */ private static function get_package_shipping_rates_cache_key( $package ) { - return md5( implode( array_keys( $package['contents'] ) ) ); + return md5( json_encode( array( array_keys( $package['contents'] ), $package['contents_cost'], $package['applied_coupons'] ) ) ); + } + + /** + * When calculating the free shipping method availability, WC uses the WC->cart object. During shipping calculations for + * recurring carts we need the recurring cart's total and coupons to be the base for checking its availability + * + * @param bool $is_available + * @param array $package + * @return bool $is_available a revised version of is_available based off the recurring cart object + * + * @since 2.0.20 + */ + public static function maybe_recalculate_shipping_method_availability( $is_available, $package ) { + + if ( isset( $package['recurring_cart_key'] ) && isset( self::$cached_recurring_cart ) && $package['recurring_cart_key'] == self::$cached_recurring_cart->recurring_cart_key ) { + + // Take a copy of the WC global cart object so we can temporarily set it to base shipping method availability on the cached recurring cart + $global_cart = WC()->cart; + WC()->cart = self::$cached_recurring_cart; + + foreach ( WC()->shipping->get_shipping_methods() as $shipping_method ) { + if ( $shipping_method->id == 'free_shipping' ) { + remove_filter( 'woocommerce_shipping_free_shipping_is_available', __METHOD__ ); + $is_available = $shipping_method->is_available( $package ); + add_filter( 'woocommerce_shipping_free_shipping_is_available', __METHOD__, 10, 2 ); + break; + } + } + + WC()->cart = $global_cart; + } + + return $is_available; } /* Deprecated */ diff --git a/includes/class-wc-subscriptions-email.php b/includes/class-wc-subscriptions-email.php index 1599160..da12ba1 100644 --- a/includes/class-wc-subscriptions-email.php +++ b/includes/class-wc-subscriptions-email.php @@ -159,8 +159,11 @@ class WC_Subscriptions_Email { 'new_renewal_order', 'customer_processing_renewal_order', 'customer_completed_renewal_order', - 'customer_renewal_invoice', ); + + if ( $theorder->needs_payment() ) { + array_push( $available_emails, 'customer_renewal_invoice' ); + } } return $available_emails; diff --git a/includes/class-wc-subscriptions-manager.php b/includes/class-wc-subscriptions-manager.php index 8975abf..b4a3684 100644 --- a/includes/class-wc-subscriptions-manager.php +++ b/includes/class-wc-subscriptions-manager.php @@ -774,7 +774,7 @@ class WC_Subscriptions_Manager { $subscription = wcs_get_subscription( $post_id ); - if ( ! $subscription->has_status( wcs_get_subscription_ended_statuses() ) ) { + if ( $subscription->can_be_updated_to( 'cancelled' ) ) { $subscription->update_status( 'cancelled' ); diff --git a/includes/class-wc-subscriptions-switcher.php b/includes/class-wc-subscriptions-switcher.php index 204ab60..1f22d6e 100644 --- a/includes/class-wc-subscriptions-switcher.php +++ b/includes/class-wc-subscriptions-switcher.php @@ -93,7 +93,7 @@ class WC_Subscriptions_Switcher { add_filter( 'woocommerce_email_order_items_table', __CLASS__ . '::add_print_switch_link' ); // Make sure sign-up fees paid on switch orders are accounted for in an items sign-up fee - add_filter( 'woocommerce_subscription_items_sign_up_fee', __CLASS__ . '::subscription_items_sign_up_fee', 10, 3 ); + add_filter( 'woocommerce_subscription_items_sign_up_fee', __CLASS__ . '::subscription_items_sign_up_fee', 10, 4 ); // Make sure switch orders are included in related orders returned for a subscription add_filter( 'woocommerce_subscription_related_orders', __CLASS__ . '::add_related_orders', 10, 4 ); @@ -936,6 +936,14 @@ class WC_Subscriptions_Switcher { } if ( $product_id == $item['product_id'] && ( empty( $variation_id ) || ( $variation_id == $item['variation_id'] && true == $identical_attributes ) ) && $quantity == $item['qty'] ) { + $is_identical_product = true; + } else { + $is_identical_product = false; + } + + $is_identical_product = apply_filters( 'woocommerce_subscriptions_switch_is_identical_product', $is_identical_product, $product_id, $quantity, $variation_id, $subscription, $item ); + + if ( $is_identical_product ) { throw new Exception( __( 'You can not switch to the same subscription.', 'woocommerce-subscriptions' ) ); } @@ -1042,10 +1050,11 @@ class WC_Subscriptions_Switcher { * Make sure the sign-up fee on a subscription line item takes into account sign-up fees paid for switching. * * @param WC_Subscription $subscription + * @param string $tax_inclusive_or_exclusive Defaults to the value tax setting stored on the subscription. * @return array $cart_item Details of an item in WC_Cart for a switch * @since 2.0 */ - public static function subscription_items_sign_up_fee( $sign_up_fee, $line_item, $subscription ) { + public static function subscription_items_sign_up_fee( $sign_up_fee, $line_item, $subscription, $tax_inclusive_or_exclusive = '' ) { // This item has never been switched, no need to add anything if ( ! isset( $line_item['switched_subscription_item_id'] ) ) { @@ -1055,9 +1064,14 @@ class WC_Subscriptions_Switcher { // First add any sign-up fees for previously switched items $switched_line_items = $subscription->get_items( 'line_item_switched' ); + // Default tax inclusive or exclusive to the value set on the subscription. This is for backwards compatibility + if ( empty( $tax_inclusive_or_exclusive ) ) { + $tax_inclusive_or_exclusive = ( 'yes' == $subscription->prices_include_tax ) ? 'inclusive_of_tax' : 'exclusive_of_tax'; + } + foreach ( $switched_line_items as $switched_line_item_id => $switched_line_item ) { if ( $line_item['switched_subscription_item_id'] == $switched_line_item_id ) { - $sign_up_fee += $subscription->get_items_sign_up_fee( $switched_line_item ); // Recursion: get the sign up fee for this item's old item and the sign up fee for that item's old item and the sign up fee for that item's old item and the sign up fee for that item's old item ... + $sign_up_fee += $subscription->get_items_sign_up_fee( $switched_line_item, $tax_inclusive_or_exclusive ); // Recursion: get the sign up fee for this item's old item and the sign up fee for that item's old item and the sign up fee for that item's old item and the sign up fee for that item's old item ... break; // Each item can only be switched once } } @@ -1078,7 +1092,13 @@ class WC_Subscriptions_Switcher { $sign_up_proportion = 1; } - $sign_up_fee += round( $order_item['line_total'] * $sign_up_proportion, 2 ); + $order_total = $order_item['line_total']; + + if ( 'inclusive_of_tax' == $tax_inclusive_or_exclusive && 'yes' == $order->prices_include_tax ) { + $order_total += $order_item['line_tax']; + } + + $sign_up_fee += round( $order_total * $sign_up_proportion, 2 ); } } } diff --git a/includes/class-wc-subscriptions-synchroniser.php b/includes/class-wc-subscriptions-synchroniser.php index b56b7db..52530fa 100644 --- a/includes/class-wc-subscriptions-synchroniser.php +++ b/includes/class-wc-subscriptions-synchroniser.php @@ -407,7 +407,7 @@ class WC_Subscriptions_Synchroniser { $product = wc_get_product( $product ); } - if ( ! is_object( $product ) || ! self::is_syncing_enabled() || 'day' == $product->subscription_period || ! $product->is_type( array( 'subscription', 'variable-subscription', 'subscription_variation' ) ) ) { + if ( ! is_object( $product ) || ! self::is_syncing_enabled() || 'day' == $product->subscription_period || ! WC_Subscriptions_Product::is_subscription( $product ) ) { return false; } diff --git a/includes/class-wcs-download-handler.php b/includes/class-wcs-download-handler.php index 308fb76..017d196 100644 --- a/includes/class-wcs-download-handler.php +++ b/includes/class-wcs-download-handler.php @@ -136,8 +136,8 @@ class WCS_Download_Handler { public static function get_item_downloads( $files, $item, $order ) { global $wpdb; - if ( wcs_order_contains_subscription( $order, 'any' ) ) { - $subscriptions = wcs_get_subscriptions_for_order( $order, array( 'order_type' => array( 'any' ) ) ); + if ( wcs_order_contains_subscription( $order, array( 'parent', 'renewal', 'switch' ) ) ) { + $subscriptions = wcs_get_subscriptions_for_order( $order, array( 'order_type' => array( 'parent', 'renewal', 'switch' ) ) ); } else { return $files; } diff --git a/includes/emails/class-wcs-email-customer-renewal-invoice.php b/includes/emails/class-wcs-email-customer-renewal-invoice.php index 0857a19..4dad736 100644 --- a/includes/emails/class-wcs-email-customer-renewal-invoice.php +++ b/includes/emails/class-wcs-email-customer-renewal-invoice.php @@ -35,8 +35,8 @@ class WCS_Email_Customer_Renewal_Invoice extends WC_Email_Customer_Invoice { $this->subject = __( 'Invoice for renewal order {order_number} from {order_date}', 'woocommerce-subscriptions' ); $this->heading = __( 'Invoice for renewal order {order_number}', 'woocommerce-subscriptions' ); - $this->subject_paid = __( 'Your {blogname} renewal order from {order_date}', 'woocommerce-subscriptions' ); - $this->heading_paid = __( 'Renewal order {order_number} details', 'woocommerce-subscriptions' ); + $this->subject_paid = null; + $this->heading_paid = null; // Triggers for this email add_action( 'woocommerce_generated_manual_renewal_order_renewal_notification', array( $this, 'trigger' ) ); @@ -160,6 +160,14 @@ class WCS_Email_Customer_Renewal_Invoice extends WC_Email_Customer_Invoice { parent::init_form_fields(); + if ( isset( $this->form_fields['heading_paid'] ) ) { + unset( $this->form_fields['heading_paid'] ); + } + + if ( isset( $this->form_fields['subject_paid'] ) ) { + unset( $this->form_fields['subject_paid'] ); + } + $this->form_fields = array_merge( array( 'enabled' => array( diff --git a/includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php b/includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php index 7252ce1..02aac90 100644 --- a/includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php +++ b/includes/gateways/paypal/includes/admin/class-wcs-paypal-admin.php @@ -33,6 +33,9 @@ class WCS_PayPal_Admin { // Maybe show notice to enter PayPal API credentials add_action( 'admin_notices', __CLASS__ . '::maybe_show_admin_notices' ); + + // Add the PayPal subscription information to the billing information + add_action( 'woocommerce_admin_order_data_after_billing_address', __CLASS__ . '::profile_link' ); } /** @@ -212,4 +215,42 @@ class WCS_PayPal_Admin { do_action( 'wcs_paypal_admin_update_credentials' ); } + + /** + * Prints link to the PayPal's profile related to the provided subscription + * + * @param WC_Subscription $subscription + */ + public static function profile_link( $subscription ) { + if ( wcs_is_subscription( $subscription ) && 'paypal' == $subscription->payment_method ) { + + $paypal_profile_id = wcs_get_paypal_id( $subscription ); + + if ( ! empty( $paypal_profile_id ) ) { + + $url = ''; + + if ( false === wcs_is_paypal_profile_a( $paypal_profile_id, 'billing_agreement' ) ) { + // Standard subscription + $url = 'https://www.paypal.com/?cmd=_profile-recurring-payments&encrypted_profile_id=' . $paypal_profile_id; + } else if ( wcs_is_paypal_profile_a( $paypal_profile_id, 'billing_agreement' ) ) { + // Reference Transaction subscription + $url = 'https://www.paypal.com/?cmd=_profile-merchant-pull&encrypted_profile_id=' . $paypal_profile_id . '&mp_id=' . $paypal_profile_id . '&return_to=merchant&flag_flow=merchant'; + } + + echo '
'; + echo esc_html( __( 'PayPal Subscription ID:', 'woocommerce-subscriptions' ) ); + echo ''; + if ( ! empty( $url ) ) { + echo '' . esc_html( $paypal_profile_id ) . ''; + } else { + echo esc_html( $paypal_profile_id ); + } + echo '
", 4$: "", 5$: "", 6$: "", 7$: "", 8$: #. "
" @@ -3002,7 +2998,7 @@ msgid "" "%6$sVariable subscription%7$s.%8$s" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:751 +#: includes/admin/class-wc-subscriptions-admin.php:753 #. translators: placeholders are for HTML tags. They are 1$: "", 4$: "
" msgctxt "used in admin pointer script params in javascript as price pointer content" @@ -3012,48 +3008,48 @@ msgid "" "sign-up fee and free trial.%4$s" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1081 +#: includes/admin/class-wc-subscriptions-admin.php:1083 msgctxt "option section heading" msgid "Renewals" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1113 +#: includes/admin/class-wc-subscriptions-admin.php:1115 msgctxt "options section heading" msgid "Miscellaneous" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1121 +#: includes/admin/class-wc-subscriptions-admin.php:1123 msgctxt "there's a number immediately in front of this text" msgid "suspensions per billing period." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1388 +#: includes/admin/class-wc-subscriptions-admin.php:1390 msgctxt "in [subscriptions] shortcode" msgid "No subscriptions found." msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1397 +#: includes/admin/class-wc-subscriptions-admin.php:1399 #. translators: order number msgctxt "in [subscriptions] shortcode" msgid "Subscription %s" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1417 +#: includes/admin/class-wc-subscriptions-admin.php:1419 msgctxt "label that indicates whether debugging is turned on for the plugin" msgid "WCS_DEBUG" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1423 +#: includes/admin/class-wc-subscriptions-admin.php:1425 msgctxt "Live or Staging, Label on WooCommerce -> System Status page" msgid "Subscriptions Mode" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1424 +#: includes/admin/class-wc-subscriptions-admin.php:1426 msgctxt "refers to staging site" msgid "Staging" msgstr "" -#: includes/admin/class-wc-subscriptions-admin.php:1424 +#: includes/admin/class-wc-subscriptions-admin.php:1426 msgctxt "refers to live site" msgid "Live" msgstr "" @@ -3170,7 +3166,7 @@ msgstr "" #: templates/myaccount/my-subscriptions.php:28 #: templates/myaccount/related-orders.php:25 #: templates/myaccount/related-subscriptions.php:23 -#: templates/myaccount/view-subscription.php:94 +#: templates/myaccount/view-subscription.php:96 msgctxt "table heading" msgid "Total" msgstr "" @@ -3286,7 +3282,7 @@ msgctxt "Subscription status" msgid "On-hold" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1999 wcs-functions.php:208 +#: includes/class-wc-subscriptions-switcher.php:2019 wcs-functions.php:208 msgctxt "Subscription status" msgid "Switched" msgstr "" @@ -3407,22 +3403,22 @@ msgctxt "used in order notes" msgid "Customer switched from: %1$s to %2$s." msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1604 +#: includes/class-wc-subscriptions-switcher.php:1624 msgctxt "a switch order" msgid "Downgrade" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1607 +#: includes/class-wc-subscriptions-switcher.php:1627 msgctxt "a switch order" msgid "Upgrade" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1610 +#: includes/class-wc-subscriptions-switcher.php:1630 msgctxt "a switch order" msgid "Crossgrade" msgstr "" -#: includes/class-wc-subscriptions-switcher.php:1615 +#: includes/class-wc-subscriptions-switcher.php:1635 #. translators: %1: product subtotal, %2: HTML span tag, %3: direction #. (upgrade, downgrade, crossgrade), %4: closing HTML span tag msgctxt "product subtotal string" @@ -3523,7 +3519,7 @@ msgid "[%s] Subscription Cancelled" msgstr "" #: includes/emails/class-wcs-email-cancelled-subscription.php:120 -#: includes/emails/class-wcs-email-customer-renewal-invoice.php:166 +#: includes/emails/class-wcs-email-customer-renewal-invoice.php:174 msgctxt "an email notification" msgid "Enable/Disable" msgstr "" @@ -3638,8 +3634,8 @@ msgstr "" #: includes/gateways/paypal/includes/class-wcs-paypal-reference-transaction-api-response.php:119 #: templates/admin/post-types/writepanels/order-shipping-html.php:14 #: templates/admin/post-types/writepanels/order-tax-html.php:9 -#: templates/myaccount/view-subscription.php:274 -#: templates/myaccount/view-subscription.php:293 +#: templates/myaccount/view-subscription.php:276 +#: templates/myaccount/view-subscription.php:295 msgctxt "no information about something" msgid "N/A" msgstr "" @@ -3735,8 +3731,8 @@ msgid "Free" msgstr "" #: includes/wcs-cart-functions.php:255 -#: templates/myaccount/view-subscription.php:199 -#: templates/myaccount/view-subscription.php:204 +#: templates/myaccount/view-subscription.php:201 +#: templates/myaccount/view-subscription.php:206 #. translators: placeholder is price string, denotes tax included in cart/order #. total msgctxt "includes tax" @@ -3904,7 +3900,7 @@ msgstr "" #: templates/emails/customer-completed-switch-order.php:75 #: templates/emails/customer-processing-renewal-order.php:31 #: templates/emails/customer-renewal-invoice.php:42 -#: templates/myaccount/view-subscription.php:93 +#: templates/myaccount/view-subscription.php:95 msgctxt "table headings in notification email" msgid "Product" msgstr "" @@ -4084,12 +4080,12 @@ msgctxt "admin subscription table header" msgid "Trial End Date" msgstr "" -#: templates/myaccount/view-subscription.php:73 +#: templates/myaccount/view-subscription.php:75 msgctxt "date on subscription updates list. Will be localized" msgid "l jS \\o\\f F Y, h:ia" msgstr "" -#: templates/myaccount/view-subscription.php:134 +#: templates/myaccount/view-subscription.php:136 #. translators: %1$s is the number of the file (only in plural!), %2$s: the #. name of the file msgctxt "Used as link text in view-subscription template" @@ -4098,29 +4094,29 @@ msgid_plural "Download file %1$s: %2$s" msgstr[0] "" msgstr[1] "" -#: templates/myaccount/view-subscription.php:231 +#: templates/myaccount/view-subscription.php:233 msgctxt "customer note" msgid "Note:" msgstr "" -#: templates/myaccount/view-subscription.php:247 +#: templates/myaccount/view-subscription.php:249 #. translators: there is markup here, hence can't use Email: %s msgctxt "heading in customer details on subscription detail page" msgid "Email" msgstr "" -#: templates/myaccount/view-subscription.php:252 +#: templates/myaccount/view-subscription.php:254 #. translators: there is markup here, hence can't use Email: %s msgctxt "heading in customer details on subscription detail page" msgid "Tel" msgstr "" -#: templates/myaccount/view-subscription.php:247 +#: templates/myaccount/view-subscription.php:249 msgctxt "Used in data attribute for a td tag, escaped." msgid "Email" msgstr "" -#: templates/myaccount/view-subscription.php:252 +#: templates/myaccount/view-subscription.php:254 msgctxt "Used in data attribute for a td tag, escaped." msgid "Telephone" msgstr "" diff --git a/templates/checkout/recurring-totals.php b/templates/checkout/recurring-totals.php index 30d0e02..50adc1e 100644 --- a/templates/checkout/recurring-totals.php +++ b/templates/checkout/recurring-totals.php @@ -57,14 +57,14 @@ $display_th = true; cart->tax_display_cart === 'excl' ) : ?> - cart->get_tax_totals() as $code => $tax ) : ?> + cart->get_taxes() as $tax_id => $tax_total ) : ?> $recurring_cart ) : ?> next_payment_date ) : ?> get_tax_totals() as $recurring_code => $recurring_tax ) : ?> - -