PluginProbe
Subscriptions for WooCommerce with Stripe Recurring Payments / 1.10.7
Subscriptions for WooCommerce with Stripe Recurring Payments v1.10.7
2.4.0 2.3.0 2.2.0 2.1.0 2.0.0 1.11.2 1.11.1 1.11.0 1.10.9 1.10.8 1.10.7 1.10.6 1.10.5 1.10.4 1.10.3 1.10.2 1.10.1 1.10.0 1.9.6 1.9.5 trunk 1.3.0 1.3.1 1.3.2 1.4.0 All 65 releases
subscription / includes / Frontend / Cart.php

Cart.php in Subscriptions for WooCommerce with Stripe Recurring Payments 1.10.7, at includes/Frontend/Cart.php

557 lines 17.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace SpringDevs\Subscription\Frontend;
4
5 use SpringDevs\Subscription\Illuminate\Helper;
6 use Automattic\WooCommerce\Blocks\Package;
7 use Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi;
8 use Automattic\WooCommerce\StoreApi\Schemas\V1\CartItemSchema;
9 use Automattic\WooCommerce\StoreApi\Schemas\V1\CartSchema;
10 use SpringDevs\Subscription\Illuminate\Subscription\Subscription;
11
12 /**
13 * Cart class
14 */
15 class Cart {
16
17 /**
18 * Initialize the class
19 */
20 public function __construct() {
21 add_filter( 'woocommerce_add_cart_item_data', array( $this, 'add_to_cart_item_data' ), 10, 2 );
22 add_action( 'woocommerce_blocks_loaded', array( $this, 'define_custom_schema' ) );
23 add_filter( 'woocommerce_cart_item_price', array( $this, 'change_price_cart_html' ), 10, 2 );
24 add_filter( 'woocommerce_cart_item_subtotal', array( $this, 'change_price_cart_html' ), 10, 2 );
25 add_action( 'woocommerce_cart_totals_after_order_total', array( $this, 'add_rows_order_total' ) );
26 add_action( 'woocommerce_review_order_after_order_total', array( $this, 'add_rows_order_total' ) );
27 add_filter( 'woocommerce_add_cart_item_data', array( $this, 'set_renew_status' ), 10, 2 );
28 add_action( 'woocommerce_check_cart_items', array( $this, 'check_cart_items' ) );
29 add_filter( 'woocommerce_get_item_data', array( $this, 'set_line_item_meta' ), 10, 2 );
30 add_action( 'woocommerce_before_calculate_totals', array( $this, 'add_calculation_price_filter' ) );
31 add_action( 'woocommerce_calculate_totals', array( $this, 'remove_calculation_price_filter' ) );
32 add_action( 'woocommerce_after_calculate_totals', array( $this, 'remove_calculation_price_filter' ) );
33
34 add_filter( 'woocommerce_add_to_cart_validation', array( $this, 'add_to_cart_validation' ), 10, 4 );
35 add_action( 'woocommerce_store_api_validate_add_to_cart', array( $this, 'add_to_cart_validation_store_api' ), 10, 1 );
36 }
37
38 /**
39 * Add to cart validation.
40 *
41 * @param bool $passed Passed ?.
42 * @param int $product_id Product Id.
43 * @param int $quantity Quantity.
44 * @param int $variation_id Variation Id.
45 *
46 * @return bool
47 */
48 public function add_to_cart_validation( bool $passed, int $product_id, int $quantity, int $variation_id = 0 ): bool {
49 $product_id = $variation_id > 0 ? $variation_id : $product_id;
50 $validation = $this->validate_cart_items( $product_id );
51
52 if ( $validation['failed'] ) {
53 $error_notice = empty( $validation['error_notice'] ) ? __( 'This product cannot be added to the cart.', 'subscription' ) : $validation['error_notice'];
54 wc_add_notice( $error_notice, 'error' );
55 return false;
56 }
57
58 // Validation passed.
59 return $passed;
60 }
61
62 /**
63 * Add to cart validation.
64 *
65 * @param \WC_Product $product Product.
66 * @throws \Exception If validation fails.
67 */
68 public function add_to_cart_validation_store_api( $product ) {
69 $product_id = $product->get_id();
70 $validation = $this->validate_cart_items( $product_id );
71
72 if ( $validation['failed'] ) {
73 $error_notice = empty( $validation['error_notice'] ) ? __( 'This product cannot be added to the cart.', 'subscription' ) : $validation['error_notice'];
74 throw new \Exception( esc_html( $error_notice ) );
75 }
76 }
77
78 /**
79 * Validate cart items.
80 *
81 * @param int $product_id Product Id.
82 * @return array
83 */
84 public function validate_cart_items( $product_id ) {
85 $cart_items = WC()->cart->cart_contents;
86
87 $product = Subscription::get_subs_product( $product_id );
88
89 $error_notice = null;
90 $failed = false;
91 $enabled = $product->is_enabled();
92
93 foreach ( $cart_items as $key => $cart_item ) {
94 if ( isset( $cart_item['subscription'] ) ) {
95 if ( $enabled ) {
96 $error_notice = __( 'You cannot purchase multiple subscriptions at the same time.', 'subscription' );
97 } else {
98 $error_notice = __( 'You cannot purchase a subscription and a non-subscription product at the same time.', 'subscription' );
99 }
100 $failed = true;
101 } elseif ( $enabled ) {
102 $error_notice = __( 'You cannot purchase a subscription along with other products. Please remove other products from your cart first.', 'subscription' );
103 $failed = true;
104 }
105 }
106
107 return [
108 'failed' => (bool) $failed,
109 'error_notice' => $error_notice,
110 ];
111 }
112
113 /**
114 * Add filter before cart calculation.
115 *
116 * @return void
117 */
118 public function add_calculation_price_filter() {
119 add_filter( 'woocommerce_product_get_price', array( $this, 'set_prices_for_calculation' ), 100, 2 );
120 }
121
122 /**
123 * Return 0 if product has trial.
124 *
125 * @param float $price Price.
126 * @param \WC_Product $product Product object.
127 *
128 * @return float
129 */
130 public function set_prices_for_calculation( $price, $product ) {
131 $product = Subscription::get_subs_product( $product );
132 if ( $product->is_enabled() && $product->is_type( 'simple' ) ) {
133 $trial_time_per = $product->get_meta( '_subscrpt_trial_timing_per' );
134 if ( ! empty( $trial_time_per ) && $trial_time_per > 0 && Helper::check_trial( $product->get_id() ) ) {
135 return 0;
136 }
137 }
138
139 return $price;
140 }
141
142 /**
143 * Remove filter after calculate calculation.
144 *
145 * @return void
146 */
147 public function remove_calculation_price_filter() {
148 remove_filter( 'woocommerce_product_get_price', array( $this, 'set_prices_for_calculation' ), 100 );
149 }
150
151 /**
152 * Set line item for display meta details.
153 *
154 * @param array $cart_item_data Cart Item Data.
155 * @param array $cart_item Cart Item.
156 *
157 * @return array
158 */
159 public function set_line_item_meta( $cart_item_data, $cart_item ) {
160 if ( isset( $cart_item['subscription'] ) ) {
161 if ( $cart_item['subscription']['trial'] ) {
162 $cart_item_data[] = array(
163 'key' => __( 'Free Trial', 'subscription' ),
164 'value' => $cart_item['subscription']['trial'],
165 'hidden' => true,
166 '__experimental_woocommerce_blocks_hidden' => false,
167 );
168 }
169 }
170
171 return $cart_item_data;
172 }
173
174 /**
175 * Check cart items if it's valid or not?
176 *
177 * @return void
178 */
179 public function check_cart_items() {
180 if ( subscrpt_pro_activated() ) {
181 return;
182 }
183 $cart_items = WC()->cart->cart_contents;
184 if ( is_array( $cart_items ) ) {
185 foreach ( $cart_items as $key => $value ) {
186 /**
187 * Product Object.
188 *
189 * @var \WC_Product $product
190 */
191 $product = $value['data'];
192 $product = Subscription::get_subs_product( $product );
193 if ( isset( $value['subscription'] ) ) {
194 if ( $product->is_type( 'simple' ) ) {
195 if ( Helper::get_typos( 1, $product->get_meta( '_subscrpt_timing_option' ) ) !== $value['subscription']['type'] || $product->get_trial() !== $value['subscription']['trial'] ) {
196 // remove the item.
197 wc_add_notice( __( 'An item which is no longer available was removed from your cart.', 'subscription' ), 'error' );
198 WC()->cart->remove_cart_item( $key );
199 }
200 } else {
201 // remove the item.
202 wc_add_notice( __( 'An item which is no longer available was removed from your cart.', 'subscription' ), 'error' );
203 WC()->cart->remove_cart_item( $key );
204 }
205 } elseif ( $product->get_meta( '_subscrpt_enabled' ) ) {
206 // remove the item.
207 wc_add_notice( __( 'An item which is no longer available was removed from your cart.', 'subscription' ), 'error' );
208 WC()->cart->remove_cart_item( $key );
209 }
210 }
211 }
212 }
213
214 /**
215 * Define custom schema.
216 *
217 * @return void
218 */
219 public function define_custom_schema() {
220 $this->register_endpoint_data(
221 array(
222 'endpoint' => CartItemSchema::IDENTIFIER,
223 'namespace' => 'sdevs_subscription',
224 'data_callback' => array( $this, 'extend_cart_item_data' ),
225 'schema_callback' => array( $this, 'extend_cart_item_schema' ),
226 'schema_type' => ARRAY_A,
227 )
228 );
229 $this->register_endpoint_data(
230 array(
231 'endpoint' => CartSchema::IDENTIFIER,
232 'namespace' => 'sdevs_subscription',
233 'data_callback' => array( $this, 'extend_cart_data' ),
234 'schema_callback' => array( $this, 'extend_cart_schema' ),
235 'schema_type' => ARRAY_A,
236 )
237 );
238 }
239
240 /**
241 * Register subscription product schema into cart/items endpoint.
242 *
243 * @return array Registered schema.
244 */
245 public function extend_cart_schema() {
246 return array(
247 'recurring_totals' => array(
248 'description' => __( 'List of recurring totals in cart.', 'subscription' ),
249 'type' => 'array',
250 'readonly' => true,
251 'recurring_totals' => array(
252 'price' => array(
253 'description' => __( 'price of the subscription.', 'subscription' ),
254 'type' => array( 'string' ),
255 'readonly' => true,
256 ),
257 'time' => array(
258 'description' => __( 'time of the subscription.', 'subscription' ),
259 'type' => array( 'number' ),
260 'readonly' => true,
261 ),
262 'type' => array(
263 'description' => __( 'type of the subscription.', 'subscription' ),
264 'type' => array( 'string' ),
265 'readonly' => true,
266 ),
267 'description' => array(
268 'description' => __( 'price of the subscription description.', 'subscription' ),
269 'type' => array( 'string' ),
270 'readonly' => true,
271 ),
272 'can_user_cancel' => array(
273 'description' => __( 'Allow User Cancellation?', 'subscription' ),
274 'type' => array( 'string' ),
275 'readonly' => true,
276 ),
277 'max_no_payment' => array(
278 'description' => __( 'Maximum Total Payments', 'subscription' ),
279 'type' => array( 'number' ),
280 'readonly' => true,
281 ),
282 ),
283 ),
284 );
285 }
286
287 /**
288 * Register subscription product data into cart/items endpoint.
289 *
290 * @return array $item_data Registered data or empty array if condition is not satisfied.
291 */
292 public function extend_cart_data() {
293 $cart_items = WC()->cart->cart_contents;
294 $recurrings = array();
295 if ( $cart_items ) {
296 foreach ( $cart_items as $cart_item ) {
297 if ( isset( $cart_item['subscription'] ) && $cart_item['subscription']['type'] ) {
298 $cart_subscription = $cart_item['subscription'];
299 $start_date = Helper::start_date( $cart_subscription['trial'] );
300 $next_date = Helper::next_date(
301 ( $cart_subscription['time'] ?? 1 ) . ' ' . $cart_subscription['type'],
302 $cart_subscription['trial']
303 );
304
305 // Total amount with tax
306 $product = $cart_item['data'];
307 $quantity = (int) $cart_item['quantity'];
308 $total_amount = wc_get_price_including_tax( $product, [ 'qty' => $quantity ] );
309
310 // Subscription timing & type
311 $time = $cart_subscription['time'];
312 $type = Helper::get_typos( $time, $cart_subscription['type'], true );
313
314 // Description
315 $description = empty( $cart_subscription['trial'] )
316 ? __( 'Next billing on', 'subscription' ) . ': ' . $next_date
317 : __( 'First billing on', 'subscription' ) . ': ' . $start_date;
318
319 $recurrings[] = apply_filters(
320 'subscrpt_cart_recurring_data',
321 array(
322 'price' => $total_amount,
323 'time' => $time,
324 'type' => $type,
325 'description' => $description,
326 'can_user_cancel' => $cart_item['data']->get_meta( '_subscrpt_user_cancel' ),
327 'max_no_payment' => $cart_item['data']->get_meta( '_subscrpt_max_no_payment' ),
328 ),
329 $cart_item
330 );
331 }
332 }
333 }
334
335 return $recurrings;
336 }
337
338 /**
339 * Register subscription product schema into cart/items endpoint.
340 *
341 * @return array Registered schema.
342 */
343 public function extend_cart_item_schema() {
344 return array(
345 'time' => array(
346 'description' => __( 'time of the subscription type.', 'subscription' ),
347 'type' => array( 'number', 'null' ),
348 'readonly' => true,
349 ),
350 'type' => array(
351 'description' => __( 'the subscription type.', 'subscription' ),
352 'type' => array( 'string', 'null' ),
353 'readonly' => true,
354 ),
355 'trial' => array(
356 'description' => __( 'the subscription trial.', 'subscription' ),
357 'type' => array( 'string', 'null' ),
358 'readonly' => true,
359 ),
360 'signup_fee' => array(
361 'description' => __( 'Signup Fee amount.', 'subscription' ),
362 'type' => array( 'string', 'null' ),
363 'readonly' => true,
364 ),
365 'cost' => array(
366 'description' => __( 'Recurring amount.', 'subscription' ),
367 'type' => array( 'string', 'null' ),
368 'readonly' => true,
369 ),
370 'max_no_payment' => array(
371 'description' => __( 'Maximum Total Payments', 'subscription' ),
372 'type' => array( 'number' ),
373 'readonly' => true,
374 ),
375 );
376 }
377
378 /**
379 * Register subscription product data into cart/items endpoint.
380 *
381 * @param array $cart_item Current cart item data.
382 *
383 * @return array $item_data Registered data or empty array if condition is not satisfied.
384 */
385 public function extend_cart_item_data( $cart_item ) {
386 $item_data = array(
387 'time' => null,
388 'type' => null,
389 'trial' => null,
390 'signup_fee' => null,
391 'cost' => null,
392 'max_no_payment' => null,
393 );
394
395 if ( isset( $cart_item['subscription'] ) ) {
396 $item_data = $cart_item['subscription'];
397 unset( $item_data['per_cost'] );
398 $item_data['cost'] = (float) $cart_item['subscription']['per_cost'] * $cart_item['quantity'];
399 }
400 if ( ! subscrpt_pro_activated() ) {
401 $item_data['time'] = null;
402 $item_data['signup_fee'] = null;
403 }
404
405 return $item_data;
406 }
407
408 /**
409 * Add product meta on cart item.
410 *
411 * @param array $cart_item_data cart_item_data.
412 * @param int $product_id Product ID.
413 *
414 * @return array
415 */
416 public function add_to_cart_item_data( array $cart_item_data, int $product_id ): array {
417 $product = Subscription::get_subs_product( $product_id );
418 if ( ! $product->is_type( 'simple' ) ) {
419 return $cart_item_data;
420 }
421 if ( $product->is_enabled() ) :
422 $subscription_data = array();
423 $subscription_data['time'] = null;
424 $subscription_data['type'] = $product->get_timing_option();
425 $subscription_data['trial'] = null;
426 if ( $product->has_trial() ) {
427 $subscription_data['trial'] = $product->get_trial();
428 }
429 $subscription_data['signup_fee'] = null;
430 $subscription_data['per_cost'] = $product->get_price();
431 $cart_item_data['subscription'] = apply_filters( 'subscrpt_block_simple_cart_item_data', $subscription_data, $product, $cart_item_data );
432 $cart_item_data['subscription']['max_no_payment'] = $product->get_meta( '_subscrpt_max_no_payment' );
433 endif;
434
435 return $cart_item_data;
436 }
437
438 /**
439 * Register endpoint data with the API.
440 *
441 * @param array $args Endpoint data to register.
442 */
443 protected function register_endpoint_data( $args ) {
444 if ( function_exists( 'woocommerce_store_api_register_endpoint_data' ) ) {
445 woocommerce_store_api_register_endpoint_data( $args );
446 } else {
447 Package::container()->get( ExtendRestApi::class )->register_endpoint_data( $args );
448 }
449 }
450
451 /**
452 * Display formatted price on cart.
453 *
454 * @param string $price price.
455 * @param array $cart_item cart item.
456 *
457 * @return string
458 */
459 public function change_price_cart_html( $price, $cart_item ) {
460 $product = Subscription::get_subs_product( $cart_item['product_id'] );
461 if ( ! $product->is_type( 'simple' ) ) {
462 return $price;
463 }
464
465 if ( $product->is_enabled() ) {
466 return $product->get_price_html();
467 }
468
469 return $price;
470 }
471
472 /**
473 * Display "Recurring totals" on cart
474 *
475 * @return void
476 */
477 public function add_rows_order_total() {
478 $cart_items = WC()->cart->get_cart_contents();
479 $recurrs = Helper::get_recurrs_from_cart( $cart_items );
480 if ( 0 === count( $recurrs ) ) {
481 return;
482 }
483 ?>
484 <tr class="recurring-total">
485 <th><?php esc_html_e( 'Recurring totals', 'subscription' ); ?></th>
486 <td data-title="<?php esc_attr_e( 'Recurring totals', 'subscription' ); ?>">
487 <?php foreach ( $recurrs as $recurr ) : ?>
488 <p>
489 <span><?php echo wp_kses_post( $recurr['price_html'] ); ?></span>
490 <?php if ( $recurr['max_no_payment'] > 0 ) : ?>
491 <span>x <?php echo esc_html( $recurr['max_no_payment'] ); ?></span>
492 <?php endif; ?>
493 <br />
494
495 <small>
496 <?php
497 $billing_text = $recurr['trial_status']
498 ? __( 'First billing on', 'subscription' )
499 : __( 'Next billing on', 'subscription' );
500
501 echo esc_html( $billing_text . ': ' );
502 echo esc_html( $recurr['trial_status'] ? $recurr['start_date'] : $recurr['next_date'] );
503 ?>
504 </small>
505
506 <?php if ( 'yes' === $recurr['can_user_cancel'] && 0 === (int) $recurr['max_no_payment'] ) : ?>
507 <br />
508 <small><?php esc_html_e( 'You can cancel subscription at any time!', 'subscription' ); ?></small>
509 <?php endif; ?>
510
511 <!-- add how many times will be build if _subscrpt_renewal_limit is not 0 -->
512 <?php if ( (int) $recurr['max_no_payment'] > 0 ) : ?>
513 <br>
514 <small>
515 <?php
516 echo wp_kses_post(
517 sprintf(
518 // translators: 1: number of installments, 2: total amount.
519 __( 'This subscription will be billed in %1$s installments, for a total of %2$s.', 'subscription' ),
520 esc_html( $recurr['max_no_payment'] ),
521 wc_price( $recurr['price'] * (int) $recurr['max_no_payment'] )
522 )
523 );
524 ?>
525 </small>
526 <?php endif; ?>
527 </p>
528 <?php endforeach; ?>
529 </td>
530 </tr>
531 <?php
532 }
533
534 /**
535 * Add renew status.
536 *
537 * @param array $cart_item_data cart_item_data.
538 * @param int $product_id Product ID.
539 *
540 * @return array
541 */
542 public function set_renew_status( $cart_item_data, $product_id ) {
543 $expired = Helper::subscription_exists( $product_id, 'expired' );
544 if ( $expired ) {
545 // Check if maximum payment limit has been reached
546 if ( subscrpt_is_max_payments_reached( $expired ) ) {
547 wc_add_notice( __( 'This subscription has reached its maximum payment limit and cannot be renewed further.', 'subscription' ), 'error' );
548 return $cart_item_data; // Don't add renew status
549 }
550
551 $cart_item_data['renew_subscrpt'] = true;
552 }
553
554 return $cart_item_data;
555 }
556 }
557