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 / functions.php

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

640 lines 19.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
4 use SpringDevs\Subscription\Illuminate\Subscription\Subscription;
5 use SpringDevs\Subscription\Utils\Product;
6
7 /**
8 * Include tailwind CSS file
9 *
10 * * Add this class to the parent element to apply tailwind css styles:
11 * * "`wpsubs-tw-root`"
12 * *
13 * * Use "`yarn build:tailwind`" to build the tailwind CSS file.
14 * * Use "`yarn watch:tailwind`" to continuously build the tailwind CSS file.
15 *
16 * ? This stylesheet is added to the all admin pages of the plugin.
17 * ? You can use this function to add the stylesheet on other pages if necessary.
18 */
19 function subscrpt_include_tailwind_css() {
20 wp_enqueue_style( 'wpsubs-tailwind', SUBSCRPT_ASSETS . '/css/tailwind/output.css', [], SUBSCRPT_VERSION );
21 }
22
23 /**
24 * Generate URL for Subscription Action.
25 *
26 * @param string $action Action.
27 * @param string $nonce nonce.
28 * @param int $subscription_id Subscription ID.
29 *
30 * @return string
31 */
32 function subscrpt_get_action_url( $action, $nonce, $subscription_id ) {
33 $view_subscription_endpoint = Subscription::get_user_endpoint( 'view_subs' );
34 return add_query_arg(
35 array(
36 'subscrpt_id' => $subscription_id,
37 'action' => $action,
38 'wpnonce' => $nonce,
39 ),
40 wc_get_endpoint_url( $view_subscription_endpoint, $subscription_id, wc_get_page_permalink( 'myaccount' ) )
41 );
42 }
43
44
45 /**
46 * Get typos.
47 *
48 * @param int $number Number.
49 * @param string $typo Typo.
50 *
51 * @return string
52 */
53 function subscrpt_get_typos( $number, $typo ) {
54 if ( $number == 1 && $typo == 'days' ) {
55 return ucfirst( __( 'day', 'subscription' ) );
56 } elseif ( $number == 1 && $typo == 'weeks' ) {
57 return ucfirst( __( 'week', 'subscription' ) );
58 } elseif ( $number == 1 && $typo == 'months' ) {
59 return ucfirst( __( 'month', 'subscription' ) );
60 } elseif ( $number == 1 && $typo == 'years' ) {
61 return ucfirst( __( 'year', 'subscription' ) );
62 } else {
63 return ucfirst( $typo );
64 }
65 }
66
67 /**
68 * Format time with trial.
69 *
70 * @param mixed $time Time.
71 * @param null|string $trial Trial.
72 *
73 * @return string
74 */
75 function subscrpt_next_date( $time, $trial = null ) {
76 if ( null === $trial ) {
77 $start_date = time();
78 } else {
79 $start_date = strtotime( $trial );
80 }
81
82 return gmdate( 'F d, Y', strtotime( $time, $start_date ) );
83 }
84
85 /**
86 * Check if subscription-pro activated.
87 *
88 * @return bool
89 */
90 function subscrpt_pro_activated(): bool {
91 return class_exists( 'Sdevs_Wc_Subscription_Pro' );
92 }
93
94 /**
95 * Get renewal process settings.
96 *
97 * @return bool
98 */
99 function subscrpt_is_auto_renew_enabled() {
100 return 'auto' === get_option( 'subscrpt_renewal_process', 'auto' );
101 }
102
103 /**
104 * Get maximum payments for a subscription, checking variation, product, and subscription meta.
105 *
106 * @param int $subscription_id Subscription ID.
107 * @return string|int Maximum payments or empty string if not set.
108 */
109 function subscrpt_get_max_payments( $subscription_id ) {
110 $product_id = get_post_meta( $subscription_id, '_subscrpt_product_id', true );
111 if ( ! $product_id ) {
112 return '';
113 }
114
115 $max_payments = null;
116
117 // Check for variation first
118 $variation_id = get_post_meta( $subscription_id, '_subscrpt_variation_id', true );
119 if ( $variation_id ) {
120 $max_payments = get_post_meta( $variation_id, '_subscrpt_max_no_payment', true );
121 }
122
123 // Fallback to product if variation doesn't have max payments or no variation
124 if ( ! $max_payments ) {
125 $max_payments = get_post_meta( $product_id, '_subscrpt_max_no_payment', true );
126 }
127
128 // Also check subscription's own meta data as final fallback
129 if ( ! $max_payments ) {
130 $max_payments = get_post_meta( $subscription_id, '_subscrpt_max_no_payment', true );
131 }
132
133 return $max_payments ?: '';
134 }
135
136 /**
137 * Count total payments made.
138 *
139 * @param int $subscription_id Subscription ID.
140 * @return int Number of payments made.
141 */
142 function subscrpt_count_payments_made( $subscription_id ) {
143 global $wpdb;
144
145 $table_name = $wpdb->prefix . 'subscrpt_order_relation';
146
147 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
148 $relations = $wpdb->get_results(
149 $wpdb->prepare(
150 "SELECT sr.*, p.post_status, p.post_date
151 FROM $table_name sr
152 INNER JOIN {$wpdb->posts} p ON sr.order_id = p.ID
153 WHERE sr.subscription_id = %d
154 ORDER BY p.post_date ASC",
155 $subscription_id
156 )
157 );
158 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
159
160 // Define all payment-related order types (allow filtering for extensibility)
161 $payment_types = apply_filters( 'subscrpt_payment_order_types', array( 'new', 'renew', 'early-renew' ) );
162
163 // Count successful payments
164 $successful_count = 0;
165 foreach ( $relations as $relation ) {
166 // Count all payment-related types
167 if ( in_array( $relation->type, $payment_types ) ) {
168 // Get the actual WooCommerce order
169 $order = wc_get_order( $relation->order_id );
170 if ( $order ) {
171 // Check if order was paid/successful
172 if ( $order->is_paid() || in_array( $order->get_status(), array( 'completed', 'processing', 'on-hold' ) ) ) {
173 ++$successful_count;
174 }
175 }
176 }
177 }
178
179 return $successful_count;
180 }
181
182 /**
183 * Check if subscription has reached its maximum payment limit.
184 *
185 * @param int $subscription_id Subscription ID.
186 * @return bool True if limit reached, false otherwise.
187 */
188 function subscrpt_is_max_payments_reached( $subscription_id ) {
189 // Get the product ID from subscription
190 $product_id = get_post_meta( $subscription_id, '_subscrpt_product_id', true );
191 if ( ! $product_id ) {
192 return false;
193 }
194
195 // Get maximum payments using helper function
196 $max_payments = subscrpt_get_max_payments( $subscription_id );
197
198 // Allow override of total installments
199 $max_payments = apply_filters( 'subscrpt_split_payment_total_override', $max_payments, $subscription_id, $product_id );
200
201 // If no limit set or unlimited, more payments are allowed
202 if ( ! $max_payments || intval( $max_payments ) <= 0 ) {
203 return false;
204 }
205
206 // Count payments made
207 $payments_made = subscrpt_count_payments_made( $subscription_id );
208
209 // Enhanced completion logic considering failed payments
210 $is_reached = subscrpt_check_enhanced_completion( $subscription_id, $payments_made, $max_payments );
211
212 // Fire action when split payment plan is completed (first time only)
213 if ( $is_reached && ! get_post_meta( $subscription_id, '_subscrpt_split_payment_completed_fired', true ) ) {
214 // Add completion milestone note
215 subscrpt_add_payment_completion_note( $subscription_id, $payments_made, $max_payments );
216
217 // Allow customization of subscription status after completion
218 $expire_status = apply_filters( 'subscrpt_split_payment_expire_status', 'expired', $subscription_id, $payments_made, $max_payments );
219
220 // Update subscription status if different from current
221 $current_status = get_post_status( $subscription_id );
222 if ( $current_status !== $expire_status ) {
223 wp_update_post(
224 array(
225 'ID' => $subscription_id,
226 'post_status' => $expire_status,
227 )
228 );
229 }
230
231 do_action( 'subscrpt_split_payment_completed', $subscription_id, $payments_made, $max_payments );
232 update_post_meta( $subscription_id, '_subscrpt_split_payment_completed_fired', true );
233
234 // Handle split payment access timing if Pro version is active
235 if ( function_exists( 'subscrpt_pro_activated' ) && subscrpt_pro_activated() ) {
236 if ( class_exists( '\SpringDevs\SubscriptionPro\Illuminate\SplitPaymentHandler' ) ) {
237 \SpringDevs\SubscriptionPro\Illuminate\SplitPaymentHandler::handle_split_payment_completion( $subscription_id, $payments_made, $max_payments );
238 }
239 }
240 }
241
242 return $is_reached;
243 }
244
245 /**
246 * Get remaining payments for a subscription.
247 *
248 * @param int $subscription_id Subscription ID.
249 * @return int|string Number of remaining payments or 'unlimited'.
250 */
251 function subscrpt_get_remaining_payments( $subscription_id ) {
252 // Get maximum payments using helper function
253 $max_payments = subscrpt_get_max_payments( $subscription_id );
254
255 // If no limit set or unlimited
256 if ( ! $max_payments || intval( $max_payments ) <= 0 ) {
257 return 'unlimited';
258 }
259
260 // Count payments made
261 $payments_made = subscrpt_count_payments_made( $subscription_id );
262
263 // Calculate remaining
264 $remaining = intval( $max_payments ) - intval( $payments_made );
265
266 return max( 0, $remaining );
267 }
268
269 /**
270 * Get payment type for a subscription (handles variations properly).
271 *
272 * @param int $subscription_id Subscription ID.
273 * @return string Payment type ('split_payment' or 'recurring').
274 */
275 function subscrpt_get_payment_type( $subscription_id ) {
276 $product_id = get_post_meta( $subscription_id, '_subscrpt_product_id', true );
277 $variation_id = get_post_meta( $subscription_id, '_subscrpt_variation_id', true );
278
279 $payment_type = 'recurring'; // Default
280
281 // Check variation first if it exists
282 if ( $variation_id ) {
283 $variation_payment_type = get_post_meta( $variation_id, '_subscrpt_payment_type', true );
284 if ( $variation_payment_type ) {
285 $payment_type = $variation_payment_type;
286 }
287 }
288
289 // Fallback to product if no variation payment type
290 if ( $payment_type === 'recurring' && $product_id ) {
291 $product_payment_type = get_post_meta( $product_id, '_subscrpt_payment_type', true );
292 if ( $product_payment_type ) {
293 $payment_type = $product_payment_type;
294 }
295 }
296
297 // Final fallback: check subscription's own meta data
298 if ( $payment_type === 'recurring' ) {
299 $subscription_payment_type = get_post_meta( $subscription_id, '_subscrpt_payment_type', true );
300 if ( $subscription_payment_type ) {
301 $payment_type = $subscription_payment_type;
302 }
303 }
304
305 return $payment_type;
306 }
307
308 /**
309 * Enhanced completion check considering failed payments and access suspension.
310 *
311 * @param int $subscription_id Subscription ID.
312 * @param int $payments_made Number of successful payments made.
313 * @param int $max_payments Maximum payments required.
314 * @return bool True if subscription should be considered complete.
315 */
316 function subscrpt_check_enhanced_completion( $subscription_id, $payments_made, $max_payments ) {
317 // Standard completion check
318 if ( $payments_made >= $max_payments ) {
319 return true;
320 }
321
322 // Check for access suspension due to payment failures
323 if ( function_exists( '\SpringDevs\SubscriptionPro\Illuminate\PaymentFailureHandler::is_access_suspended' ) ) {
324 $is_suspended = \SpringDevs\SubscriptionPro\Illuminate\PaymentFailureHandler::is_access_suspended( $subscription_id );
325 if ( $is_suspended ) {
326 // If access is suspended, check if we should force completion
327 $force_completion_on_suspension = apply_filters( 'subscrpt_force_completion_on_suspension', false, $subscription_id );
328 if ( $force_completion_on_suspension ) {
329 return true;
330 }
331 }
332 }
333
334 // Check for maximum failure threshold
335 $failure_count = get_post_meta( $subscription_id, '_subscrpt_payment_failure_count', true ) ?: 0;
336 $max_failures_before_completion = apply_filters( 'subscrpt_max_failures_before_completion', 0, $subscription_id );
337
338 if ( $max_failures_before_completion > 0 && $failure_count >= $max_failures_before_completion ) {
339 // Force completion after too many failures
340 return true;
341 }
342
343 // Check for time-based completion (e.g., if too much time has passed)
344 $completion_timeout_days = apply_filters( 'subscrpt_completion_timeout_days', 0, $subscription_id );
345 if ( $completion_timeout_days > 0 ) {
346 $start_date = get_post_meta( $subscription_id, '_subscrpt_start_date', true );
347 if ( $start_date ) {
348 $timeout_timestamp = $start_date + ( $completion_timeout_days * DAY_IN_SECONDS );
349 if ( current_time( 'timestamp' ) >= $timeout_timestamp ) {
350 return true;
351 }
352 }
353 }
354
355 return false;
356 }
357
358 /**
359 * Count total payment attempts (including failed ones) for a subscription.
360 *
361 * @param int $subscription_id Subscription ID.
362 * @return array Array with 'successful', 'failed', and 'total' counts.
363 */
364 function subscrpt_count_all_payment_attempts( $subscription_id ) {
365 global $wpdb;
366
367 $table_name = $wpdb->prefix . 'subscrpt_order_relation';
368
369 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
370 $relations = $wpdb->get_results(
371 $wpdb->prepare(
372 "SELECT sr.*, p.post_status, p.post_date
373 FROM $table_name sr
374 INNER JOIN {$wpdb->posts} p ON sr.order_id = p.ID
375 WHERE sr.subscription_id = %d
376 ORDER BY p.post_date ASC",
377 $subscription_id
378 )
379 );
380 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
381
382 // Define all payment-related order types
383 $payment_types = apply_filters( 'subscrpt_payment_order_types', array( 'new', 'renew', 'early-renew' ) );
384
385 $successful_count = 0;
386 $failed_count = 0;
387
388 foreach ( $relations as $relation ) {
389 if ( in_array( $relation->type, $payment_types ) ) {
390 $order = wc_get_order( $relation->order_id );
391 if ( $order ) {
392 if ( $order->is_paid() || in_array( $order->get_status(), array( 'completed', 'processing', 'on-hold' ) ) ) {
393 ++$successful_count;
394 } elseif ( in_array( $order->get_status(), array( 'failed', 'cancelled' ) ) ) {
395 ++$failed_count;
396 }
397 }
398 }
399 }
400
401 return array(
402 'successful' => $successful_count,
403 'failed' => $failed_count,
404 'total' => $successful_count + $failed_count,
405 );
406 }
407
408 if ( ! function_exists( 'wps_subscription_order_relation_type_cast' ) ) {
409 /**
410 * Return Label against key.
411 *
412 * @param string $key Key to return cast Value.
413 *
414 * @return string
415 */
416 function order_relation_type_cast( string $key ) {
417 // add Deprecated notice
418 _deprecated_function( 'order_relation_type_cast', '1.5.3', 'wps_subscription_order_relation_type_cast' );
419 return wps_subscription_order_relation_type_cast( $key );
420 }
421 /**
422 * Order relation type cast.
423 *
424 * @param string $key Key.
425 *
426 * @return string
427 */
428 function wps_subscription_order_relation_type_cast( string $key ) {
429 $relational_type_keys = apply_filters(
430 'subscrpt_order_relational_types',
431 array(
432 'new' => __( 'New Subscription Order', 'subscription' ),
433 'renew' => __( 'Renewal Order', 'subscription' ),
434 )
435 );
436
437 return isset( $relational_type_keys[ $key ] ) ? $relational_type_keys[ $key ] : '-';
438 }
439 }
440
441 if ( ! function_exists( 'wps_subscription_is_wc_order_hpos_enabled' ) ) {
442 /**
443 * Check if HPOS enabled.
444 */
445 function is_wc_order_hpos_enabled() {
446 // add Deprecated notice
447 _deprecated_function( 'is_wc_order_hpos_enabled', '1.5.3', 'wps_subscription_is_wc_order_hpos_enabled' );
448 return wps_subscription_is_wc_order_hpos_enabled();
449 }
450 /**
451 * Check if HPOS enabled.
452 *
453 * @return bool
454 */
455 function wps_subscription_is_wc_order_hpos_enabled() {
456 return function_exists( 'wc_get_container' ) ?
457 wc_get_container()
458 ->get( CustomOrdersTableController::class )
459 ->custom_orders_table_usage_is_enabled()
460 : false;
461 }
462 }
463
464 if ( ! function_exists( 'sdevs_wp_strtotime' ) ) {
465 /**
466 * Get strtotime with WordPress timezone config.
467 *
468 * @param string $str string.
469 * @param int|null $base_timestamp base timestamp.
470 *
471 * @return int
472 */
473 function sdevs_wp_strtotime( $str, $base_timestamp = null ) {
474 return strtotime( wp_date( 'Y-m-d H:i:s', strtotime( $str, $base_timestamp ) ) );
475 }
476 }
477
478 if ( ! function_exists( 'sdevs_order_status_label' ) ) {
479 /**
480 * Get order status label from slug.
481 *
482 * @param string $status Status.
483 *
484 * @return string
485 */
486 function sdevs_order_status_label( $status ) {
487 $order_statuses = wc_get_order_statuses();
488
489 return ( isset( $order_statuses[ "wc-{$status}" ] ) ? $order_statuses[ "wc-{$status}" ] : $status );
490 }
491 }
492
493 if ( ! function_exists( 'wps_subscription_get_timing_types' ) ) {
494 /**
495 * Get labels.
496 *
497 * @param bool $key_value key_value.
498 *
499 * @return array
500 */
501 function get_timing_types( $key_value = false ): array {
502 // add Deprecated notice
503 _deprecated_function( 'get_timing_types', '1.5.3', 'wps_subscription_get_timing_types' );
504 return wps_subscription_get_timing_types( $key_value );
505 }
506 /**
507 * Get timing types.
508 *
509 * @param bool $key_value Key value.
510 *
511 * @return array
512 */
513 function wps_subscription_get_timing_types( $key_value = false ): array {
514 return $key_value ? array(
515 'days' => 'Daily',
516 'weeks' => 'Weekly',
517 'months' => 'Monthly',
518 'years' => 'Yearly',
519 ) : array(
520 array(
521 'label' => __( 'Day', 'subscription' ),
522 'value' => 'days',
523 ),
524 array(
525 'label' => __( 'Week', 'subscription' ),
526 'value' => 'weeks',
527 ),
528 array(
529 'label' => __( 'Month', 'subscription' ),
530 'value' => 'months',
531 ),
532 array(
533 'label' => __( 'Year', 'subscription' ),
534 'value' => 'years',
535 ),
536 );
537 }
538 }
539
540 /**
541 * Get WC product in subscription wrapper.
542 *
543 * @deprecated 1.8.17 Use SpringDevs\Subscription\Illuminate\Subscription\Subscription::get_subs_product().
544 */
545 function sdevs_get_subscription_product( $product ) {
546 // Deprecated notice.
547 _deprecated_function( 'sdevs_get_subscription_product', '1.8.17', 'SpringDevs\Subscription\Illuminate\Subscription\Subscription::get_subs_product' );
548
549 return Subscription::get_subs_product( $product );
550 }
551
552 /**
553 * Logger
554 *
555 * @param mixed $message Message.
556 * @param bool $should_print Print the output.
557 */
558 function subscrpt_write_log( $message, bool $should_print = false ): void {
559 $logger = wc_get_logger();
560
561 $message = is_array( $message ) || is_object( $message ) ? wp_json_encode( $message ) : $message;
562 $logger->add( 'wp_subscription', $message );
563
564 echo esc_html( $should_print ? $message : '' );
565 }
566
567 /**
568 * Debug Logger
569 *
570 * @param mixed $log logs.
571 */
572 function subscrpt_write_debug_log( $log ): void {
573 if ( defined( 'WP_DEBUG' ) && WP_DEBUG === true ) {
574 if ( is_array( $log ) || is_object( $log ) ) {
575 error_log( print_r( $log, true ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions
576 } else {
577 error_log( 'wp_subscription: ' . $log ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions
578 }
579 }
580 }
581
582 /**
583 * Add payment completion note for split payment subscriptions.
584 *
585 * @param int $subscription_id Subscription ID.
586 * @param int $payments_made Number of payments made.
587 * @param int $max_payments Maximum number of payments.
588 */
589 function subscrpt_add_payment_completion_note( $subscription_id, $payments_made, $max_payments ) {
590 // Check if this is a split payment subscription
591 if ( ! function_exists( 'subscrpt_get_payment_type' ) ) {
592 return;
593 }
594
595 $payment_type = subscrpt_get_payment_type( $subscription_id );
596 if ( 'split_payment' !== $payment_type ) {
597 return;
598 }
599
600 // Create completion note
601 $completion_note = sprintf(
602 /* translators: %1$d: payments made, %2$d: total payments */
603 __( 'Split payment plan completed successfully! %1$d of %2$d payments received.', 'subscription' ),
604 $payments_made,
605 $max_payments
606 );
607
608 // Add the completion note
609 $comment_id = wp_insert_comment(
610 array(
611 'comment_author' => 'Subscription for WooCommerce',
612 'comment_content' => $completion_note,
613 'comment_post_ID' => $subscription_id,
614 'comment_type' => 'order_note',
615 )
616 );
617 update_comment_meta( $comment_id, '_subscrpt_activity', __( 'Split Payment - Plan Complete', 'subscription' ) );
618 update_comment_meta( $comment_id, '_subscrpt_activity_type', 'split_payment' );
619
620 // Add payment summary note
621 $payment_summary = sprintf(
622 /* translators: %1$d: payments made, %2$d: total payments, %3$s: completion date */
623 __( 'Payment Summary: %1$d of %2$d installments completed on %3$s. All payments received successfully.', 'subscription' ),
624 $payments_made,
625 $max_payments,
626 date_i18n( wc_date_format(), current_time( 'timestamp' ) )
627 );
628
629 $summary_comment_id = wp_insert_comment(
630 array(
631 'comment_author' => 'Subscription for WooCommerce',
632 'comment_content' => $payment_summary,
633 'comment_post_ID' => $subscription_id,
634 'comment_type' => 'order_note',
635 )
636 );
637 update_comment_meta( $summary_comment_id, '_subscrpt_activity', __( 'Payment Summary - Complete', 'subscription' ) );
638 update_comment_meta( $summary_comment_id, '_subscrpt_activity_type', 'split_payment_summary' );
639 }
640