PluginProbe
Subscriptions for WooCommerce with Stripe Recurring Payments / 2.0.0
Subscriptions for WooCommerce with Stripe Recurring Payments v2.0.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 1.4.1 1.4.2 1.5.0 1.5.1 All 61 releases
← All changes | includes/Frontend/Cart.php +285 -70 1.3.22.0.0 View file →
@@ -1,5 +1,10 @@
1 1 <?php
2 +/**
3 + * Cart handling for subscription products.
4 + *
5 + * @package SpringDevs\Subscription
6 + */
2 7
3 8 namespace SpringDevs\Subscription\Frontend;
4 9
5 10 use SpringDevs\Subscription\Illuminate\Helper;
@@ -6,8 +11,9 @@
6 11 use Automattic\WooCommerce\Blocks\Package;
7 12 use Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi;
8 13 use Automattic\WooCommerce\StoreApi\Schemas\V1\CartItemSchema;
9 14 use Automattic\WooCommerce\StoreApi\Schemas\V1\CartSchema;
15 +use SpringDevs\Subscription\Illuminate\Subscription\Subscription;
10 16
11 17 /**
12 18 * Cart class
13 19 */
@@ -28,9 +34,11 @@
28 34 add_filter( 'woocommerce_get_item_data', array( $this, 'set_line_item_meta' ), 10, 2 );
29 35 add_action( 'woocommerce_before_calculate_totals', array( $this, 'add_calculation_price_filter' ) );
30 36 add_action( 'woocommerce_calculate_totals', array( $this, 'remove_calculation_price_filter' ) );
31 37 add_action( 'woocommerce_after_calculate_totals', array( $this, 'remove_calculation_price_filter' ) );
32 - add_filter( 'woocommerce_add_to_cart_validation', array( $this, 'add_to_cart_validation' ), 10, 2 );
38 +
39 + add_filter( 'woocommerce_add_to_cart_validation', array( $this, 'add_to_cart_validation' ), 10, 4 );
40 + add_action( 'woocommerce_store_api_validate_add_to_cart', array( $this, 'add_to_cart_validation_store_api' ), 10, 1 );
33 41 }
34 42
35 43 /**
36 44 * Add to cart validation.
@@ -36,38 +44,77 @@
36 44 * Add to cart validation.
37 45 *
38 46 * @param bool $passed Passed ?.
39 47 * @param int $product_id Product Id.
48 + * @param int $quantity Quantity.
49 + * @param int $variation_id Variation Id.
40 50 *
41 51 * @return bool
42 52 */
43 - public function add_to_cart_validation( bool $passed, int $product_id ): bool {
44 - $cart_items = WC()->cart->cart_contents;
53 + public function add_to_cart_validation( $passed, $product_id, $quantity, $variation_id = 0 ) {
54 + $product_id = (int) $variation_id > 0 ? (int) $variation_id : (int) $product_id;
55 + $validation = $this->validate_cart_items( $product_id );
56 +
57 + if ( $validation['failed'] ) {
58 + $error_notice = empty( $validation['error_notice'] ) ? __( 'This product cannot be added to the cart.', 'subscription' ) : $validation['error_notice'];
59 + wc_add_notice( $error_notice, 'error' );
60 + return false;
61 + }
62 +
63 + // Validation passed.
64 + return $passed;
65 + }
66 +
67 + /**
68 + * Add to cart validation.
69 + *
70 + * @param \WC_Product $product Product.
71 + * @throws \Exception If validation fails.
72 + */
73 + public function add_to_cart_validation_store_api( $product ) {
74 + $product_id = $product->get_id();
75 + $validation = $this->validate_cart_items( $product_id );
76 +
77 + if ( $validation['failed'] ) {
78 + $error_notice = empty( $validation['error_notice'] ) ? __( 'This product cannot be added to the cart.', 'subscription' ) : $validation['error_notice'];
79 + throw new \Exception( esc_html( $error_notice ) );
80 + }
81 + }
82 +
83 + /**
84 + * Validate cart items.
85 + *
86 + * @param int $product_id Product Id.
87 + * @return array
88 + */
89 + public function validate_cart_items( $product_id ) {
90 + $cart_items = WC()->cart->cart_contents;
91 +
92 + $product = Subscription::get_subs_product( $product_id );
93 +
45 94 $error_notice = null;
46 95 $failed = false;
47 - $product = sdevs_get_subscription_product( $product_id );
48 - $enabled = $product->is_enabled();
96 + // A tied plan counts as a subscription even without classic `_subscrpt_enabled`.
97 + $enabled = $product->is_enabled() || subscrpt_plan_offered( $product_id );
98 +
49 99 foreach ( $cart_items as $key => $cart_item ) {
50 100 if ( isset( $cart_item['subscription'] ) ) {
51 101 if ( $enabled ) {
52 - $error_notice = __( 'Currently You have an another Subscriptional product on cart !!', 'sdevs_subscrpt' );
102 + $error_notice = __( 'You cannot purchase multiple subscriptions at the same time.', 'subscription' );
53 103 } else {
54 - $error_notice = __( 'Currently You have Subscriptional product in a cart !!', 'sdevs_subscrpt' );
104 + $error_notice = __( 'You cannot purchase a subscription and a non-subscription product at the same time.', 'subscription' );
55 105 }
56 106 $failed = true;
57 107 } elseif ( $enabled ) {
108 + $error_notice = __( 'You cannot purchase a subscription along with other products. Please remove other products from your cart first.', 'subscription' );
58 109 $failed = true;
59 - $error_notice = __( 'Your cart isn\'t empty !!', 'sdevs_subscrpt' );
60 110 }
61 111 }
62 112
63 - if ( $failed ) {
64 - wc_add_notice( $error_notice, 'error' );
65 -
66 - return false;
67 - }
68 -
69 - return $passed;
113 + return [
114 + 'failed' => (bool) $failed,
115 + 'error_notice' => $error_notice,
116 + ];
70 117 }
71 118
72 119 /**
73 120 * Add filter before cart calculation.
@@ -86,9 +133,9 @@
86 133 *
87 134 * @return float
88 135 */
89 136 public function set_prices_for_calculation( $price, $product ) {
90 - $product = sdevs_get_subscription_product( $product );
137 + $product = Subscription::get_subs_product( $product );
91 138 if ( $product->is_enabled() && $product->is_type( 'simple' ) ) {
92 139 $trial_time_per = $product->get_meta( '_subscrpt_trial_timing_per' );
93 140 if ( ! empty( $trial_time_per ) && $trial_time_per > 0 && Helper::check_trial( $product->get_id() ) ) {
94 141 return 0;
@@ -118,9 +165,9 @@
118 165 public function set_line_item_meta( $cart_item_data, $cart_item ) {
119 166 if ( isset( $cart_item['subscription'] ) ) {
120 167 if ( $cart_item['subscription']['trial'] ) {
121 168 $cart_item_data[] = array(
122 - 'key' => __( 'Free Trial', 'sdevs_subscrpt' ),
169 + 'key' => __( 'Free Trial', 'subscription' ),
123 170 'value' => $cart_item['subscription']['trial'],
124 171 'hidden' => true,
125 172 '__experimental_woocommerce_blocks_hidden' => false,
126 173 );
@@ -141,8 +188,16 @@
141 188 }
142 189 $cart_items = WC()->cart->cart_contents;
143 190 if ( is_array( $cart_items ) ) {
144 191 foreach ( $cart_items as $key => $value ) {
192 + // Plan items were validated against the plan by the resolver at
193 + // add-to-cart; their `subscription` snapshot intentionally differs
194 + // from the product's classic meta, so skip the classic re-check
195 + // (which would otherwise drop them from the cart).
196 + if ( ! empty( $value['subscrpt_plan_id'] ) ) {
197 + continue;
198 + }
199 +
145 200 /**
146 201 * Product Object.
147 202 *
148 203 * @var \WC_Product $product
@@ -147,24 +202,24 @@
147 202 *
148 203 * @var \WC_Product $product
149 204 */
150 205 $product = $value['data'];
151 - $product = sdevs_get_subscription_product( $product );
206 + $product = Subscription::get_subs_product( $product );
152 207 if ( isset( $value['subscription'] ) ) {
153 208 if ( $product->is_type( 'simple' ) ) {
154 209 if ( Helper::get_typos( 1, $product->get_meta( '_subscrpt_timing_option' ) ) !== $value['subscription']['type'] || $product->get_trial() !== $value['subscription']['trial'] ) {
155 210 // remove the item.
156 - wc_add_notice( __( 'An item which is no longer available was removed from your cart.', 'sdevs_subscrpt' ), 'error' );
211 + wc_add_notice( __( 'An item which is no longer available was removed from your cart.', 'subscription' ), 'error' );
157 212 WC()->cart->remove_cart_item( $key );
158 213 }
159 214 } else {
160 215 // remove the item.
161 - wc_add_notice( __( 'An item which is no longer available was removed from your cart.', 'sdevs_subscrpt' ), 'error' );
216 + wc_add_notice( __( 'An item which is no longer available was removed from your cart.', 'subscription' ), 'error' );
162 217 WC()->cart->remove_cart_item( $key );
163 218 }
164 - } elseif ( $product->get_meta( '_subscrpt_enabled' ) ) {
219 + } elseif ( $product->is_enabled() ) {
165 220 // remove the item.
166 - wc_add_notice( __( 'An item which is no longer available was removed from your cart.', 'sdevs_subscrpt' ), 'error' );
221 + wc_add_notice( __( 'An item which is no longer available was removed from your cart.', 'subscription' ), 'error' );
167 222 WC()->cart->remove_cart_item( $key );
168 223 }
169 224 }
170 225 }
@@ -203,37 +258,72 @@
203 258 */
204 259 public function extend_cart_schema() {
205 260 return array(
206 261 'recurring_totals' => array(
207 - 'description' => __( 'List of recurring totals in cart.', 'sdevs_subscrpt' ),
262 + 'description' => __( 'List of recurring totals in cart.', 'subscription' ),
208 263 'type' => 'array',
209 264 'readonly' => true,
210 265 'recurring_totals' => array(
211 - 'price' => array(
212 - 'description' => __( 'price of the subscription.', 'sdevs_subscrpt' ),
266 + 'price' => array(
267 + 'description' => __( 'price of the subscription, after any discount that applies to renewals.', 'subscription' ),
213 268 'type' => array( 'string' ),
214 269 'readonly' => true,
215 270 ),
216 - 'time' => array(
217 - 'description' => __( 'time of the subscription.', 'sdevs_subscrpt' ),
271 + 'full_price' => array(
272 + 'description' => __( 'price of the subscription before any discount.', 'subscription' ),
273 + 'type' => array( 'string' ),
274 + 'readonly' => true,
275 + ),
276 + 'first_price' => array(
277 + 'description' => __( 'amount charged today, after all discounts.', 'subscription' ),
278 + 'type' => array( 'string' ),
279 + 'readonly' => true,
280 + ),
281 + 'has_recurring_discount' => array(
282 + 'description' => __( 'Whether a discount applies to renewals.', 'subscription' ),
283 + 'type' => array( 'boolean' ),
284 + 'readonly' => true,
285 + ),
286 + 'has_one_time_discount' => array(
287 + 'description' => __( 'Whether a discount applies to the first payment only.', 'subscription' ),
288 + 'type' => array( 'boolean' ),
289 + 'readonly' => true,
290 + ),
291 + 'recurring_limit' => array(
292 + 'description' => __( 'Number of payments the recurring discount covers. 0 means unlimited.', 'subscription' ),
218 293 'type' => array( 'number' ),
219 294 'readonly' => true,
220 295 ),
221 - 'type' => array(
222 - 'description' => __( 'type of the subscription.', 'sdevs_subscrpt' ),
296 + 'time' => array(
297 + 'description' => __( 'time of the subscription.', 'subscription' ),
298 + 'type' => array( 'number' ),
299 + 'readonly' => true,
300 + ),
301 + 'type' => array(
302 + 'description' => __( 'type of the subscription.', 'subscription' ),
223 303 'type' => array( 'string' ),
224 304 'readonly' => true,
225 305 ),
226 - 'description' => array(
227 - 'description' => __( 'price of the subscription description.', 'sdevs_subscrpt' ),
306 + 'description' => array(
307 + 'description' => __( 'price of the subscription description.', 'subscription' ),
228 308 'type' => array( 'string' ),
229 309 'readonly' => true,
230 310 ),
231 - 'can_user_cancel' => array(
232 - 'description' => __( 'Can User Cancel?', 'sdevs_subscrpt' ),
311 + 'can_user_cancel' => array(
312 + 'description' => __( 'Allow User Cancellation?', 'subscription' ),
233 313 'type' => array( 'string' ),
234 314 'readonly' => true,
235 315 ),
316 + 'max_no_payment' => array(
317 + 'description' => __( 'Maximum Total Payments', 'subscription' ),
318 + 'type' => array( 'number' ),
319 + 'readonly' => true,
320 + ),
321 + 'split_total' => array(
322 + 'description' => __( 'Total price for a split-payment plan (entered plan price).', 'subscription' ),
323 + 'type' => array( 'number', 'null' ),
324 + 'readonly' => true,
325 + ),
236 326 ),
237 327 ),
238 328 );
239 329 }
@@ -246,9 +336,9 @@
246 336 public function extend_cart_data() {
247 337 $cart_items = WC()->cart->cart_contents;
248 338 $recurrings = array();
249 339 if ( $cart_items ) {
250 - foreach ( $cart_items as $cart_item ) {
340 + foreach ( $cart_items as $cart_item_key => $cart_item ) {
251 341 if ( isset( $cart_item['subscription'] ) && $cart_item['subscription']['type'] ) {
252 342 $cart_subscription = $cart_item['subscription'];
253 343 $start_date = Helper::start_date( $cart_subscription['trial'] );
254 344 $next_date = Helper::next_date(
@@ -255,16 +345,37 @@
255 345 ( $cart_subscription['time'] ?? 1 ) . ' ' . $cart_subscription['type'],
256 346 $cart_subscription['trial']
257 347 );
258 348
349 + // Subscription timing & type
350 + $time = $cart_subscription['time'];
351 + $type = Helper::get_typos( $time, $cart_subscription['type'], true );
352 +
353 + // Discount-aware totals, shared with the classic cart.
354 + $price_data = Helper::build_cart_recurring_price_data( $cart_item, $cart_item_key, $type );
355 +
356 + // Description
357 + $description = empty( $cart_subscription['trial'] )
358 + ? __( 'Next billing on', 'subscription' ) . ': ' . $next_date
359 + : __( 'First billing on', 'subscription' ) . ': ' . $start_date;
360 +
259 361 $recurrings[] = apply_filters(
260 362 'subscrpt_cart_recurring_data',
261 363 array(
262 - 'price' => ( $cart_item['subscription']['per_cost'] * $cart_item['quantity'] ) * 100,
263 - 'time' => $cart_subscription['time'],
264 - 'type' => $cart_subscription['type'],
265 - 'description' => empty( $cart_subscription['trial'] ) ? 'Next billing on: ' . $next_date : 'First billing on: ' . $start_date,
266 - 'can_user_cancel' => $cart_item['data']->get_meta( '_subscrpt_user_cancel' ),
364 + 'price' => $price_data['total'],
365 + 'full_price' => $price_data['full_total'],
366 + 'first_price' => $price_data['first_total'],
367 + 'has_recurring_discount' => $price_data['has_recurring_discount'],
368 + 'has_one_time_discount' => $price_data['has_one_time_discount'],
369 + 'recurring_limit' => $price_data['recurring_limit'],
370 + 'time' => $time,
371 + 'type' => $type,
372 + 'description' => $description,
373 + 'can_user_cancel' => $cart_item['data']->get_meta( '_subscrpt_user_cancel' ),
374 + 'max_no_payment' => ! empty( $cart_item['subscrpt_max_no_payment'] )
375 + ? (int) $cart_item['subscrpt_max_no_payment']
376 + : $cart_item['data']->get_meta( '_subscrpt_max_no_payment' ),
377 + 'split_total' => isset( $cart_item['subscrpt_split_total'] ) ? (float) $cart_item['subscrpt_split_total'] : null,
267 378 ),
268 379 $cart_item
269 380 );
270 381 }
@@ -280,33 +391,38 @@
280 391 * @return array Registered schema.
281 392 */
282 393 public function extend_cart_item_schema() {
283 394 return array(
284 - 'time' => array(
285 - 'description' => __( 'time of the subscription type.', 'sdevs_subscrpt' ),
395 + 'time' => array(
396 + 'description' => __( 'time of the subscription type.', 'subscription' ),
286 397 'type' => array( 'number', 'null' ),
287 398 'readonly' => true,
288 399 ),
289 - 'type' => array(
290 - 'description' => __( 'the subscription type.', 'sdevs_subscrpt' ),
400 + 'type' => array(
401 + 'description' => __( 'the subscription type.', 'subscription' ),
291 402 'type' => array( 'string', 'null' ),
292 403 'readonly' => true,
293 404 ),
294 - 'trial' => array(
295 - 'description' => __( 'the subscription trial.', 'sdevs_subscrpt' ),
405 + 'trial' => array(
406 + 'description' => __( 'the subscription trial.', 'subscription' ),
296 407 'type' => array( 'string', 'null' ),
297 408 'readonly' => true,
298 409 ),
299 - 'signup_fee' => array(
300 - 'description' => __( 'Signup Fee amount.', 'sdevs_subscrpt' ),
410 + 'signup_fee' => array(
411 + 'description' => __( 'Signup Fee amount.', 'subscription' ),
301 412 'type' => array( 'string', 'null' ),
302 413 'readonly' => true,
303 414 ),
304 - 'cost' => array(
305 - 'description' => __( 'Recurring amount.', 'sdevs_subscrpt' ),
415 + 'cost' => array(
416 + 'description' => __( 'Recurring amount.', 'subscription' ),
306 417 'type' => array( 'string', 'null' ),
307 418 'readonly' => true,
308 419 ),
420 + 'max_no_payment' => array(
421 + 'description' => __( 'Maximum Total Payments', 'subscription' ),
422 + 'type' => array( 'number' ),
423 + 'readonly' => true,
424 + ),
309 425 );
310 426 }
311 427
312 428 /**
@@ -317,18 +433,37 @@
317 433 * @return array $item_data Registered data or empty array if condition is not satisfied.
318 434 */
319 435 public function extend_cart_item_data( $cart_item ) {
320 436 $item_data = array(
321 - 'time' => null,
322 - 'type' => null,
323 - 'trial' => null,
324 - 'signup_fee' => null,
325 - 'cost' => null,
437 + 'time' => null,
438 + 'type' => null,
439 + 'trial' => null,
440 + 'signup_fee' => null,
441 + 'cost' => null,
442 + 'max_no_payment' => null,
326 443 );
444 +
327 445 if ( isset( $cart_item['subscription'] ) ) {
328 446 $item_data = $cart_item['subscription'];
329 447 unset( $item_data['per_cost'] );
330 - $item_data['cost'] = $cart_item['subscription']['per_cost'] * $cart_item['quantity'];
448 + $item_data['cost'] = (float) $cart_item['subscription']['per_cost'] * $cart_item['quantity'];
449 +
450 + // Plan items don't stamp the installment count into the subscription
451 + // array (it rides the cart item as subscrpt_max_no_payment); classic
452 + // products carry it on the product meta.
453 + if ( ! isset( $item_data['max_no_payment'] ) ) {
454 + $item_data['max_no_payment'] = ! empty( $cart_item['subscrpt_max_no_payment'] )
455 + ? (int) $cart_item['subscrpt_max_no_payment']
456 + : $cart_item['data']->get_meta( '_subscrpt_max_no_payment' );
457 + }
458 +
459 + // Normalise the cadence word to singular/plural by frequency for the
460 + // blocks (Store API) cart — plan items store the raw plural interval
461 + // (e.g. "months"), which the block would otherwise render as-is.
462 + if ( ! empty( $item_data['type'] ) ) {
463 + $sub_time = max( 1, (int) ( $item_data['time'] ?? 1 ) );
464 + $item_data['type'] = Helper::get_typos( $sub_time, $item_data['type'] );
465 + }
331 466 }
332 467 if ( ! subscrpt_pro_activated() ) {
333 468 $item_data['time'] = null;
334 469 $item_data['signup_fee'] = null;
@@ -345,12 +480,19 @@
345 480 *
346 481 * @return array
347 482 */
348 483 public function add_to_cart_item_data( array $cart_item_data, int $product_id ): array {
349 - $product = sdevs_get_subscription_product( $product_id );
484 + $product = Subscription::get_subs_product( $product_id );
350 485 if ( ! $product->is_type( 'simple' ) ) {
351 486 return $cart_item_data;
352 487 }
488 + // Plan products stamp their subscription snapshot in the plan checkout
489 + // (Frontend\PlanCheckout / Pro), gated on the chosen plan id — so a One-Time
490 + // purchase of a plan product is not wrongly tagged as a subscription here
491 + // (which would show a cadence + list it under "Recurring totals").
492 + if ( subscrpt_product_has_plan( $product_id ) ) {
493 + return $cart_item_data;
494 + }
353 495 if ( $product->is_enabled() ) :
354 496 $subscription_data = array();
355 497 $subscription_data['time'] = null;
356 498 $subscription_data['type'] = $product->get_timing_option();
@@ -357,11 +499,12 @@
357 499 $subscription_data['trial'] = null;
358 500 if ( $product->has_trial() ) {
359 501 $subscription_data['trial'] = $product->get_trial();
360 502 }
361 - $subscription_data['signup_fee'] = null;
362 - $subscription_data['per_cost'] = $product->get_price();
363 - $cart_item_data['subscription'] = apply_filters( 'subscrpt_block_simple_cart_item_data', $subscription_data, $product, $cart_item_data );
503 + $subscription_data['signup_fee'] = null;
504 + $subscription_data['per_cost'] = $product->get_price();
505 + $cart_item_data['subscription'] = apply_filters( 'subscrpt_block_simple_cart_item_data', $subscription_data, $product, $cart_item_data );
506 + $cart_item_data['subscription']['max_no_payment'] = $product->get_meta( '_subscrpt_max_no_payment' );
364 507 endif;
365 508
366 509 return $cart_item_data;
367 510 }
@@ -387,14 +530,17 @@
387 530 *
388 531 * @return string
389 532 */
390 533 public function change_price_cart_html( $price, $cart_item ) {
391 - $product = sdevs_get_subscription_product( $cart_item['product_id'] );
534 + $product = Subscription::get_subs_product( $cart_item['product_id'] );
392 535 if ( ! $product->is_type( 'simple' ) ) {
393 536 return $price;
394 537 }
395 538
396 - if ( $product->is_enabled() ) {
539 + // A tied plan makes it a subscription even without classic `_subscrpt_enabled`;
540 + // get_price_html() already resolves to the plan line (Frontend\Plans), so this
541 + // shows the plan cadence on the cart line without doubling.
542 + if ( $product->is_enabled() || subscrpt_plan_offered( $product->get_id() ) ) {
397 543 return $product->get_price_html();
398 544 }
399 545
400 546 return $price;
@@ -405,25 +551,88 @@
405 551 *
406 552 * @return void
407 553 */
408 554 public function add_rows_order_total() {
409 - $cart_items = WC()->cart->cart_contents;
410 - $recurrs = Helper::get_recurrs_for_cart( $cart_items );
555 + $cart_items = WC()->cart->get_cart_contents();
556 + $recurrs = Helper::get_recurrs_from_cart( $cart_items );
411 557 if ( 0 === count( $recurrs ) ) {
412 558 return;
413 559 }
414 560 ?>
415 561 <tr class="recurring-total">
416 - <th><?php esc_html_e( 'Recurring totals', 'sdevs_subscrpt' ); ?></th>
417 - <td data-title="<?php esc_attr_e( 'Recurring totals', 'sdevs_subscrpt' ); ?>">
562 + <th><?php esc_html_e( 'Recurring totals', 'subscription' ); ?></th>
563 + <td data-title="<?php esc_attr_e( 'Recurring totals', 'subscription' ); ?>">
418 564 <?php foreach ( $recurrs as $recurr ) : ?>
419 565 <p>
420 - <span><?php echo wp_kses_post( $recurr['price_html'] ); ?></span><br />
421 - <small><?php esc_html_e( ( $recurr['trial_status'] ? 'First billing on' : 'Next billing on' ), 'sdevs_subscrpt' ); ?>
422 - : <?php echo esc_html( $recurr['trial_status'] ? $recurr['start_date'] : $recurr['next_date'] ); ?></small>
423 - <?php if ( 'yes' === $recurr['can_user_cancel'] ) : ?>
566 + <span><?php echo wp_kses_post( $recurr['price_html'] ); ?></span>
567 + <?php if ( $recurr['max_no_payment'] > 0 ) : ?>
568 + <span>x <?php echo esc_html( $recurr['max_no_payment'] ); ?></span>
569 + <?php endif; ?>
570 + <br />
571 +
572 + <small>
573 + <?php
574 + $billing_text = $recurr['trial_status']
575 + ? __( 'First billing on', 'subscription' )
576 + : __( 'Next billing on', 'subscription' );
577 +
578 + echo esc_html( $billing_text . ': ' );
579 + echo esc_html( $recurr['trial_status'] ? $recurr['start_date'] : $recurr['next_date'] );
580 + ?>
581 + </small>
582 +
583 + <?php if ( ! empty( $recurr['has_one_time_discount'] ) ) : ?>
584 + <br />
585 + <small>
586 + <?php
587 + echo wp_kses_post(
588 + sprintf(
589 + // translators: 1: amount paid today, 2: amount charged on each renewal.
590 + __( 'You pay %1$s today. %2$s will be charged from the next renewal.', 'subscription' ),
591 + wc_price( $recurr['first_total'] ?? 0 ),
592 + wc_price( $recurr['total'] ?? 0 )
593 + )
594 + );
595 + ?>
596 + </small>
597 + <?php endif; ?>
598 +
599 + <?php if ( ! empty( $recurr['has_recurring_discount'] ) && (int) ( $recurr['recurring_limit'] ?? 0 ) > 1 ) : ?>
600 + <br />
601 + <small>
602 + <?php
603 + echo wp_kses_post(
604 + sprintf(
605 + // translators: 1: number of discounted payments, 2: full price charged afterwards.
606 + __( 'Discount applies to your first %1$s payments. After that, %2$s.', 'subscription' ),
607 + number_format_i18n( (int) $recurr['recurring_limit'] ),
608 + $recurr['full_price_html'] ?? ''
609 + )
610 + );
611 + ?>
612 + </small>
613 + <?php endif; ?>
614 +
615 + <?php if ( 'yes' === $recurr['can_user_cancel'] && 0 === (int) $recurr['max_no_payment'] ) : ?>
616 + <br />
617 + <small><?php esc_html_e( 'You can cancel subscription at any time!', 'subscription' ); ?></small>
618 + <?php endif; ?>
619 +
620 + <!-- add how many times will be build if _subscrpt_renewal_limit is not 0 -->
621 + <?php if ( (int) $recurr['max_no_payment'] > 0 ) : ?>
424 622 <br>
425 - <small><?php esc_html_e( 'You can cancel subscription at any time!', 'sdevs_subscrpt' ); ?></small>
623 + <small>
624 + <?php
625 + echo wp_kses_post(
626 + sprintf(
627 + // translators: 1: number of installments, 2: total amount.
628 + __( 'This subscription will be billed in %1$s installments, for a total of %2$s.', 'subscription' ),
629 + esc_html( $recurr['max_no_payment'] ),
630 + wc_price( isset( $recurr['split_total'] ) && null !== $recurr['split_total'] ? $recurr['split_total'] : $recurr['price'] * (int) $recurr['max_no_payment'] )
631 + )
632 + );
633 + ?>
634 + </small>
426 635 <?php endif; ?>
427 636 </p>
428 637 <?php endforeach; ?>
429 638 </td>
@@ -441,8 +650,14 @@
441 650 */
442 651 public function set_renew_status( $cart_item_data, $product_id ) {
443 652 $expired = Helper::subscription_exists( $product_id, 'expired' );
444 653 if ( $expired ) {
654 + // Check if maximum payment limit has been reached
655 + if ( subscrpt_is_max_payments_reached( $expired ) ) {
656 + wc_add_notice( __( 'This subscription has reached its maximum payment limit and cannot be renewed further.', 'subscription' ), 'error' );
657 + return $cart_item_data; // Don't add renew status
658 + }
659 +
445 660 $cart_item_data['renew_subscrpt'] = true;
446 661 }
447 662
448 663 return $cart_item_data;