PluginProbe ʕ •ᴥ•ʔ
TaxCloud for WooCommerce / 8.4.11
TaxCloud for WooCommerce v8.4.11
8.4.11 8.4.10 8.4.9 trunk 6.0.11 6.0.12 6.0.13 6.0.14 6.1.0 6.1.1 6.1.2 6.2.0 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.2.6 6.3.0 6.3.1 6.3.10 6.3.11 6.3.12 6.3.13 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.3.8 6.3.9 7.0.0 7.0.1 7.0.10 7.0.11 7.0.12 7.0.13 7.0.2 7.0.3 7.0.4 7.0.5 7.0.6 7.0.7 7.0.8 7.0.9 8.0.0 8.0.1 8.0.10 8.0.11 8.0.12 8.0.13 8.0.14 8.0.15 8.0.16 8.0.17 8.0.2 8.0.3 8.0.4 8.0.5 8.0.6 8.0.7 8.0.8 8.0.9 8.1.0 8.1.1 8.2.0 8.2.1 8.2.2 8.2.3 8.2.4 8.3.0 8.3.1 8.3.2 8.3.3 8.3.4 8.3.5 8.3.6 8.3.7 8.3.8 8.4.0 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.4.6 8.4.7 8.4.8
simple-sales-tax / includes / integrations / class-sst-subscriptions.php
simple-sales-tax / includes / integrations Last commit date
class-sst-adp.php 4 months ago class-sst-composite-products.php 1 month ago class-sst-deposits-for-wc.php 11 months ago class-sst-dokan.php 8 months ago class-sst-subscriptions.php 3 months ago class-sst-wc-vendors.php 8 months ago class-sst-wcfm.php 8 months ago class-sst-wcmp.php 8 months ago
class-sst-subscriptions.php
330 lines
1 <?php
2
3 if ( ! defined( 'ABSPATH' ) ) {
4 exit; // Exit if accessed directly.
5 }
6
7 /**
8 * Subscriptions.
9 *
10 * Enables WooCommerce Subscriptions support.
11 *
12 * @author Simple Sales Tax
13 * @package SST
14 * @since 5.0
15 */
16 class SST_Subscriptions {
17
18 /**
19 * Constructor.
20 *
21 * @since 5.0
22 */
23 public function __construct() {
24 add_filter( 'wootax_add_fees', array( $this, 'exclude_fees' ) );
25 add_filter( 'wootax_cart_packages_before_split', array( $this, 'add_package_for_no_ship_subs' ), 10, 2 );
26 add_filter( 'wootax_order_packages_before_split', array( $this, 'add_order_package_for_no_ship_subs' ), 10, 2 );
27 add_filter( 'wootax_product_tic', array( $this, 'set_signup_fee_tic' ), 10, 3 );
28 add_filter( 'woocommerce_calculated_total', array( $this, 'save_shipping_taxes' ), 1200, 2 );
29 add_action( 'woocommerce_cart_updated', array( $this, 'restore_shipping_taxes' ) );
30 add_action( 'woocommerce_checkout_update_order_meta', array( $this, 'destroy_session' ) );
31 add_filter( 'wcs_renewal_order_created', array( $this, 'recalc_taxes_for_renewal' ), 1, 2 );
32 add_filter( 'wootax_save_packages_for_capture', array( $this, 'should_save_packages_for_capture' ) );
33 add_filter( 'woocommerce_subscriptions_calculated_total', array( $this, 'filter_calculated_total' ) );
34 }
35
36 /**
37 * Exclude fees from tax lookups when subscription there is no subscription
38 * sign up fee and all subscriptions qualify for a free trial.
39 *
40 * @param bool $add_fees Whether to include fees in the tax lookup.
41 *
42 * @return bool
43 * @since 5.0
44 */
45 public function exclude_fees( $add_fees ) {
46 if ( ! did_action( 'woocommerce_calculate_totals' ) ) {
47 return $add_fees; /* In backend; TODO: better way to handle? */
48 } else {
49 $calc_type = WC_Subscriptions_Cart::get_calculation_type();
50 $sign_up_fee = WC_Subscriptions_Cart::get_cart_subscription_sign_up_fee();
51 $all_have_free_trial = WC_Subscriptions_Cart::all_cart_items_have_free_trial();
52
53 if ( 'recurring_total' !== $calc_type && 0 === (int) $sign_up_fee && $all_have_free_trial ) {
54 return false;
55 }
56 }
57
58 return $add_fees;
59 }
60
61 /**
62 * This function is executed when a new renewal order is created. It
63 * recalculates the sales tax for the order to account for the fact
64 * that the customer address (and tax rates) may have changed.
65 *
66 * @param WC_Order $renewal_order The renewal order that was just created.
67 * @param WC_Subscription $subscription The subscription associated with the renewal order.
68 *
69 * @return WC_Order
70 * @since 5.0
71 */
72 public function recalc_taxes_for_renewal( $renewal_order, $subscription ) {
73 $order = new SST_Order( $renewal_order );
74
75 /* Reset packages to force recalc */
76 $order->set_packages( array() );
77
78 /* Reset status to ensure Lookup isn't blocked */
79 $order->update_meta( 'status', 'pending' );
80
81 /* Recalc taxes */
82 try {
83 $order->calculate_taxes();
84 $order->calculate_totals( false );
85 } catch ( Exception $ex ) {
86 $order->add_order_note(
87 sprintf(
88 /* translators: 1 - Renewal order ID, 2 - Error message */
89 __( 'Failed to calculate sales tax for renewal order %1$d: %2$s.', 'simple-sales-tax' ),
90 $order->get_id(),
91 $ex->getMessage()
92 )
93 );
94 }
95
96 return $renewal_order;
97 }
98
99 /**
100 * Subscriptions will recalculate the shipping totals for the main cart
101 * after calculating the recurring totals. When this is done, the shipping
102 * taxes for the main cart will be reset. This function saves the
103 * computed shipping taxes before the recurring totals are calculated so
104 * they can be restored later.
105 *
106 * IMPORTANT: This hook needs to run after SST_Checkout::calculate_tax_totals().
107 *
108 * @param double $total Current cart total.
109 * @param WC_Cart $cart Cart object.
110 *
111 * @return double
112 * @since 5.0
113 */
114 public function save_shipping_taxes( $total, $cart ) {
115 $calc_type = WC_Subscriptions_Cart::get_calculation_type();
116
117 if ( in_array( $calc_type, array( 'none', 'recurring_total' ), true ) ) {
118 $saved_taxes = WC()->session->get( 'sst_saved_shipping_taxes', array() );
119
120 $saved_taxes[ $calc_type ] = $cart->get_shipping_taxes();
121
122 WC()->session->set( 'sst_saved_shipping_taxes', $saved_taxes );
123 }
124
125 return $total;
126 }
127
128 /**
129 * Restore shipping taxes after recurring order totals are updated.
130 *
131 * @since 5.0
132 */
133 public function restore_shipping_taxes() {
134 $calc_type = WC_Subscriptions_Cart::get_calculation_type();
135
136 if ( in_array( $calc_type, array( 'none', 'recurring_total' ), true ) ) {
137 $saved_taxes = WC()->session->get( 'sst_saved_shipping_taxes', array() );
138
139 if ( array_key_exists( $calc_type, $saved_taxes ) ) {
140 WC()->cart->set_shipping_taxes( $saved_taxes[ $calc_type ] );
141 }
142 }
143 }
144
145 /**
146 * Delete session data post-checkout.
147 *
148 * @since 5.0
149 */
150 public function destroy_session() {
151 WC()->session->set( 'sst_saved_shipping_taxes', array() );
152 }
153
154 /**
155 * When the cart calculation type is 'none,' Subscriptions removes all
156 * subscriptions with free trials from the cart packages. Similarly, when
157 * the calculation type is 'recurring_total,' it removes all subscriptions
158 * that have one time shipping. As a consequence, the fees for these subs
159 * (if any) will not be included in lookups. To avoid this, we hook
160 * wootax_cart_packages_before_split and add a special package containing
161 * all removed subs. Since all subs in this package do not ship, we use the
162 * customer billing address as the destination address.
163 *
164 * @param array $packages SST packages for cart.
165 * @param WC_Cart $cart WooCommerce cart object.
166 *
167 * @return array
168 * @since 5.0
169 */
170 public function add_package_for_no_ship_subs( $packages, $cart ) {
171 $contents = array();
172 $calc_type = WC_Subscriptions_Cart::get_calculation_type();
173
174 if ( 'none' === $calc_type && WC_Subscriptions_Cart::cart_contains_free_trial() ) {
175 foreach ( $cart->get_cart() as $key => $cart_item ) {
176 if ( WC_Subscriptions_Product::get_trial_length( $cart_item['data'] ) > 0 ) {
177 $contents[ $key ] = $cart_item;
178 }
179 }
180 } elseif ( 'recurring_total' === $calc_type ) {
181 foreach ( $cart->get_cart() as $key => $cart_item ) {
182 if ( WC_Subscriptions_Product::needs_one_time_shipping( $cart_item['data'] ) ) {
183 $contents[ $key ] = $cart_item;
184 }
185 }
186 }
187
188 if ( ! empty( $contents ) ) { /* Add package */
189 $packages[] = sst_create_package(
190 array(
191 'contents' => $contents,
192 'user' => array(
193 'ID' => get_current_user_id(),
194 ),
195 'destination' => array(
196 'address' => WC()->customer->get_billing_address(),
197 'address_2' => WC()->customer->get_billing_address_2(),
198 'city' => WC()->customer->get_billing_city(),
199 'state' => WC()->customer->get_billing_state(),
200 'postcode' => WC()->customer->get_billing_postcode(),
201 ),
202 )
203 );
204 }
205
206 return $packages;
207 }
208
209 /**
210 * Same as add_package_for_no_ship_subs, but for when we're calculating the
211 * tax for an order from the Edit Order screen.
212 *
213 * @param array $packages SST packages for order.
214 * @param WC_Order $order WooCommerce order object.
215 *
216 * @return array
217 * @since 6.0.13
218 */
219 public function add_order_package_for_no_ship_subs( $packages, $order ) {
220 $contents = array();
221
222 /** @var WC_Order_Item_Product $item */
223 if ( ! wcs_order_contains_renewal( $order ) ) {
224 foreach ( $order->get_items() as $item_id => $item ) {
225 if ( WC_Subscriptions_Product::get_trial_length( $item->get_product() ) > 0 ) {
226 $contents[ $item_id ] = $item;
227 }
228 }
229 } else {
230 foreach ( $order->get_items() as $item_id => $item ) {
231 if ( WC_Subscriptions_Product::needs_one_time_shipping( $item->get_product() ) ) {
232 $contents[ $item_id ] = $item;
233 }
234 }
235 }
236
237 if ( ! empty( $contents ) ) { /* Add package */
238 $contents = sst_format_order_items( $contents );
239 $packages[] = sst_create_package(
240 array(
241 'contents' => $contents,
242 'user' => array(
243 'ID' => get_current_user_id(),
244 ),
245 'destination' => array(
246 'address' => $order->get_billing_address_1(),
247 'address_2' => $order->get_billing_address_2(),
248 'city' => $order->get_billing_city(),
249 'state' => $order->get_billing_state(),
250 'postcode' => $order->get_billing_postcode(),
251 ),
252 )
253 );
254 }
255
256 return $packages;
257 }
258
259 /**
260 * If we are calculating tax for the initial order (i.e. the calculation
261 * type is 'none'), set the TIC for all subscription products with a free
262 * trial period and sign up fee to "Membership fees" (91070). If this isn't
263 * done, sign-up fees will be taxed as if they are subscriptions.
264 *
265 * @param int $tic TIC to use for product.
266 * @param int $product_id Product ID.
267 * @param int $variation_id Variation ID (default: 0).
268 *
269 * @return int
270 * @since 5.0
271 */
272 public function set_signup_fee_tic( $tic, $product_id, $variation_id = 0 ) {
273 if ( ! $this->is_initial_subscription_order() ) {
274 return $tic;
275 }
276
277 $has_free_trial = WC_Subscriptions_Product::get_trial_length( $product_id ) > 0;
278 $has_fee = WC_Subscriptions_Product::get_sign_up_fee( $product_id );
279
280 if ( $has_free_trial && $has_fee ) {
281 return apply_filters( 'wootax_sign_up_fee_tic', 91070 ); // Default is "Membership fees" (91070).
282 }
283
284 return $tic;
285 }
286
287 /**
288 * Checks whether we're calculating the tax for an initial subscription order.
289 *
290 * @return bool
291 */
292 private function is_initial_subscription_order() {
293 if ( isset( $_REQUEST['order_id'] ) ) { // phpcs:ignore WordPress.CSRF.NonceVerification
294 // Re-calculating totals in WP admin.
295 return ! wcs_order_contains_renewal( absint( $_REQUEST['order_id'] ) ); // phpcs:ignore WordPress.CSRF.NonceVerification
296 } elseif ( doing_filter( 'wcs_renewal_order_created' ) ) {
297 return false;
298 }
299
300 return 'none' === WC_Subscriptions_Cart::get_calculation_type();
301 }
302
303 /**
304 * Filters `wootax_save_packages_for_capture` to prevent Simple Sales Tax from sending recurring totals to TaxCloud.
305 *
306 * @return bool
307 */
308 public function should_save_packages_for_capture() {
309 return 'none' === WC_Subscriptions_Cart::get_calculation_type();
310 }
311
312 /**
313 * Filters calculated total to fix double tax on initial cart with
314 * WC 9.2+.
315 *
316 * @param float $total Cart total
317 *
318 * @return float
319 */
320 public function filter_calculated_total( $total ) {
321 $taxes = WC()->cart->get_cart_contents_taxes();
322 $sst_tax = isset( $taxes[ SST_RATE_ID ] ) ? $taxes[ SST_RATE_ID ] : 0;
323
324 return max( 0, round( $total - $sst_tax, wc_get_price_decimals() ) );
325 }
326
327 }
328
329 new SST_Subscriptions();
330