Passed
Push — master ( b39af0...f4e65e )
by Brian
04:33
created

WPInv_Subscription::create_payment()   B

Complexity

Conditions 10
Paths 49

Size

Total Lines 40
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 10
eloc 21
c 3
b 1
f 0
nc 49
nop 0
dl 0
loc 40
rs 7.6666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Contains the subscription class.
4
 *
5
 * @since 1.0.19
6
 * @package Invoicing
7
 */
8
9
defined( 'ABSPATH' ) || exit;
10
11
/**
12
 * The Subscription Class
13
 *
14
 * @since  1.0.0
15
 */
16
class WPInv_Subscription extends GetPaid_Data {
17
18
	/**
19
	 * Which data store to load.
20
	 *
21
	 * @var string
22
	 */
23
	protected $data_store_name = 'subscription';
24
25
	/**
26
	 * This is the name of this object type.
27
	 *
28
	 * @var string
29
	 */
30
	protected $object_type = 'subscription';
31
32
	/**
33
	 * Item Data array. This is the core item data exposed in APIs.
34
	 *
35
	 * @since 1.0.19
36
	 * @var array
37
	 */
38
	protected $data = array(
39
		'customer_id'       => 0,
40
		'frequency'         => 1,
41
		'period'            => 'D',
42
		'initial_amount'    => null,
43
		'recurring_amount'  => null,
44
		'bill_times'        => 0,
45
		'transaction_id'    => '',
46
		'parent_payment_id' => null,
47
		'product_id'        => 0,
48
		'created'           => '0000-00-00 00:00:00',
49
		'expiration'        => '0000-00-00 00:00:00',
50
		'trial_period'      => '',
51
		'status'            => 'pending',
52
		'profile_id'        => '',
53
		'gateway'           => '',
54
		'customer'          => '',
55
	);
56
57
	/**
58
	 * Stores the status transition information.
59
	 *
60
	 * @since 1.0.19
61
	 * @var bool
62
	 */
63
	protected $status_transition = false;
64
65
	/**
66
	 * Get the subscription if ID is passed, otherwise the subscription is new and empty.
67
	 *
68
	 * @param  int|string|object|WPInv_Subscription $subscription Subscription id, profile_id, or object to read.
69
	 * @param  bool $deprecated
70
	 */
71
	function __construct( $subscription = 0, $deprecated = false ) {
72
73
		parent::__construct( $subscription );
74
75
		if ( ! $deprecated && ! empty( $subscription ) && is_numeric( $subscription ) ) {
76
			$this->set_id( $subscription );
0 ignored issues
show
Bug introduced by
It seems like $subscription can also be of type string; however, parameter $id of GetPaid_Data::set_id() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

76
			$this->set_id( /** @scrutinizer ignore-type */ $subscription );
Loading history...
77
		} elseif ( $subscription instanceof self ) {
78
			$this->set_id( $subscription->get_id() );
79
		} elseif ( $deprecated && $subscription_id = self::get_subscription_id_by_field( $subscription, 'profile_id' ) ) {
80
			$this->set_id( $subscription_id );
81
		} elseif ( ! empty( $subscription->id ) ) {
82
			$this->set_id( $subscription->id );
83
		} else {
84
			$this->set_object_read( true );
85
		}
86
87
		// Load the datastore.
88
		$this->data_store = GetPaid_Data_Store::load( $this->data_store_name );
89
90
		if ( $this->get_id() > 0 ) {
91
			$this->data_store->read( $this );
92
		}
93
94
	}
95
96
	/**
97
	 * Given an invoice id, profile id, transaction id, it returns the subscription's id.
98
	 *
99
	 *
100
	 * @static
101
	 * @param string $value
102
	 * @param string $field Either invoice_id, transaction_id or profile_id.
103
	 * @since 1.0.19
104
	 * @return int
105
	 */
106
	public static function get_subscription_id_by_field( $value, $field = 'profile_id' ) {
107
        global $wpdb;
108
109
		// Trim the value.
110
		$value = trim( $value );
111
112
		if ( empty( $value ) ) {
113
			return 0;
114
		}
115
116
		if ( 'invoice_id' == $field ) {
117
			$field = 'parent_payment_id';
118
		}
119
120
        // Valid fields.
121
        $fields = array(
122
			'parent_payment_id',
123
			'transaction_id',
124
			'profile_id'
125
		);
126
127
		// Ensure a field has been passed.
128
		if ( empty( $field ) || ! in_array( $field, $fields ) ) {
129
			return 0;
130
		}
131
132
		// Maybe retrieve from the cache.
133
		$subscription_id   = wp_cache_get( $value, "getpaid_subscription_{$field}s_to_subscription_ids" );
134
		if ( ! empty( $subscription_id ) ) {
135
			return $subscription_id;
136
		}
137
138
        // Fetch from the db.
139
        $table            = $wpdb->prefix . 'wpinv_subscriptions';
140
        $subscription_id  = (int) $wpdb->get_var(
141
            $wpdb->prepare( "SELECT `id` FROM $table WHERE `$field`=%s LIMIT 1", $value )
142
        );
143
144
		if ( empty( $subscription_id ) ) {
145
			return 0;
146
		}
147
148
		// Update the cache with our data.
149
		wp_cache_set( $value, $subscription_id, "getpaid_subscription_{$field}s_to_subscription_ids" );
150
151
		return $subscription_id;
152
	}
153
154
	/**
155
     * Clears the subscription's cache.
156
     */
157
    public function clear_cache() {
158
		wp_cache_delete( $this->get_parent_payment_id(), 'getpaid_subscription_parent_payment_ids_to_subscription_ids' );
159
		wp_cache_delete( $this->get_transaction_id(), 'getpaid_subscription_transaction_ids_to_subscription_ids' );
160
		wp_cache_delete( $this->get_profile_id(), 'getpaid_subscription_profile_ids_to_subscription_ids' );
161
		wp_cache_delete( $this->get_id(), 'getpaid_subscriptions' );
162
	}
163
164
	/**
165
     * Checks if a subscription key is set.
166
     */
167
    public function _isset( $key ) {
168
        return isset( $this->data[$key] ) || method_exists( $this, "get_$key" );
169
	}
170
171
	/*
172
	|--------------------------------------------------------------------------
173
	| CRUD methods
174
	|--------------------------------------------------------------------------
175
	|
176
	| Methods which create, read, update and delete subscriptions from the database.
177
	|
178
    */
179
180
	/*
181
	|--------------------------------------------------------------------------
182
	| Getters
183
	|--------------------------------------------------------------------------
184
	*/
185
186
	/**
187
	 * Get customer id.
188
	 *
189
	 * @since 1.0.19
190
	 * @param  string $context View or edit context.
191
	 * @return int
192
	 */
193
	public function get_customer_id( $context = 'view' ) {
194
		return (int) $this->get_prop( 'customer_id', $context );
195
	}
196
197
	/**
198
	 * Get customer information.
199
	 *
200
	 * @since 1.0.19
201
	 * @param  string $context View or edit context.
202
	 * @return WP_User|false WP_User object on success, false on failure.
203
	 */
204
	public function get_customer( $context = 'view' ) {
205
		return get_userdata( $this->get_customer_id( $context ) );
206
	}
207
208
	/**
209
	 * Get parent invoice id.
210
	 *
211
	 * @since 1.0.19
212
	 * @param  string $context View or edit context.
213
	 * @return int
214
	 */
215
	public function get_parent_invoice_id( $context = 'view' ) {
216
		return (int) $this->get_prop( 'parent_payment_id', $context );
217
	}
218
219
	/**
220
	 * Alias for self::get_parent_invoice_id().
221
	 *
222
	 * @since 1.0.19
223
	 * @param  string $context View or edit context.
224
	 * @return int
225
	 */
226
    public function get_parent_payment_id( $context = 'view' ) {
227
        return $this->get_parent_invoice_id( $context );
228
	}
229
230
	/**
231
     * Alias for self::get_parent_invoice_id().
232
     *
233
     * @since  1.0.0
234
     * @return int
235
     */
236
    public function get_original_payment_id( $context = 'view' ) {
237
        return $this->get_parent_invoice_id( $context );
238
    }
239
240
	/**
241
	 * Get parent invoice.
242
	 *
243
	 * @since 1.0.19
244
	 * @param  string $context View or edit context.
245
	 * @return WPInv_Invoice
246
	 */
247
	public function get_parent_invoice( $context = 'view' ) {
248
		return new WPInv_Invoice( $this->get_parent_invoice_id( $context ) );
249
	}
250
251
	/**
252
	 * Alias for self::get_parent_invoice().
253
	 *
254
	 * @since 1.0.19
255
	 * @param  string $context View or edit context.
256
	 * @return WPInv_Invoice
257
	 */
258
    public function get_parent_payment( $context = 'view' ) {
259
        return $this->get_parent_invoice( $context );
260
	}
261
262
	/**
263
	 * Get subscription's product id.
264
	 *
265
	 * @since 1.0.19
266
	 * @param  string $context View or edit context.
267
	 * @return int
268
	 */
269
	public function get_product_id( $context = 'view' ) {
270
		return (int) $this->get_prop( 'product_id', $context );
271
	}
272
273
	/**
274
	 * Get the subscription product.
275
	 *
276
	 * @since 1.0.19
277
	 * @param  string $context View or edit context.
278
	 * @return WPInv_Item
279
	 */
280
	public function get_product( $context = 'view' ) {
281
		return new WPInv_Item( $this->get_product_id( $context ) );
282
	}
283
284
	/**
285
	 * Get parent invoice's gateway.
286
	 *
287
	 * Here for backwards compatibility.
288
	 *
289
	 * @since 1.0.19
290
	 * @param  string $context View or edit context.
291
	 * @return string
292
	 */
293
	public function get_gateway( $context = 'view' ) {
294
		return $this->get_parent_invoice( $context )->get_gateway();
295
	}
296
297
	/**
298
	 * Get the period of a renewal.
299
	 *
300
	 * @since 1.0.19
301
	 * @param  string $context View or edit context.
302
	 * @return string
303
	 */
304
	public function get_period( $context = 'view' ) {
305
		return $this->get_prop( 'period', $context );
306
	}
307
308
	/**
309
	 * Get number of periods each renewal is valid for.
310
	 *
311
	 * @since 1.0.19
312
	 * @param  string $context View or edit context.
313
	 * @return int
314
	 */
315
	public function get_frequency( $context = 'view' ) {
316
		return (int) $this->get_prop( 'frequency', $context );
317
	}
318
319
	/**
320
	 * Get the initial amount for the subscription.
321
	 *
322
	 * @since 1.0.19
323
	 * @param  string $context View or edit context.
324
	 * @return float
325
	 */
326
	public function get_initial_amount( $context = 'view' ) {
327
		return (float) wpinv_sanitize_amount( $this->get_prop( 'initial_amount', $context ) );
328
	}
329
330
	/**
331
	 * Get the recurring amount for the subscription.
332
	 *
333
	 * @since 1.0.19
334
	 * @param  string $context View or edit context.
335
	 * @return float
336
	 */
337
	public function get_recurring_amount( $context = 'view' ) {
338
		return (float) wpinv_sanitize_amount( $this->get_prop( 'recurring_amount', $context ) );
339
	}
340
341
	/**
342
	 * Get number of times that this subscription can be renewed.
343
	 *
344
	 * @since 1.0.19
345
	 * @param  string $context View or edit context.
346
	 * @return int
347
	 */
348
	public function get_bill_times( $context = 'view' ) {
349
		return (int) $this->get_prop( 'bill_times', $context );
350
	}
351
352
	/**
353
	 * Get transaction id of this subscription's parent invoice.
354
	 *
355
	 * @since 1.0.19
356
	 * @param  string $context View or edit context.
357
	 * @return string
358
	 */
359
	public function get_transaction_id( $context = 'view' ) {
360
		return $this->get_prop( 'transaction_id', $context );
361
	}
362
363
	/**
364
	 * Get the date that the subscription was created.
365
	 *
366
	 * @since 1.0.19
367
	 * @param  string $context View or edit context.
368
	 * @return string
369
	 */
370
	public function get_created( $context = 'view' ) {
371
		return $this->get_prop( 'created', $context );
372
	}
373
374
	/**
375
	 * Alias for self::get_created().
376
	 *
377
	 * @since 1.0.19
378
	 * @param  string $context View or edit context.
379
	 * @return string
380
	 */
381
	public function get_date_created( $context = 'view' ) {
382
		return $this->get_created( $context );
383
	}
384
385
	/**
386
	 * Retrieves the creation date in a timestamp
387
	 *
388
	 * @since  1.0.0
389
	 * @return int
390
	 */
391
	public function get_time_created() {
392
		$created = $this->get_date_created();
393
		return empty( $created ) ? current_time( 'timestamp' ) : strtotime( $created, current_time( 'timestamp' ) );
394
	}
395
396
	/**
397
	 * Get GMT date when the subscription was created.
398
	 *
399
	 * @since 1.0.19
400
	 * @param  string $context View or edit context.
401
	 * @return string
402
	 */
403
	public function get_date_created_gmt( $context = 'view' ) {
404
        $date = $this->get_date_created( $context );
405
406
        if ( $date ) {
407
            $date = get_gmt_from_date( $date );
408
        }
409
		return $date;
410
	}
411
412
	/**
413
	 * Get the date that the subscription will renew.
414
	 *
415
	 * @since 1.0.19
416
	 * @param  string $context View or edit context.
417
	 * @return string
418
	 */
419
	public function get_next_renewal_date( $context = 'view' ) {
420
		return $this->get_prop( 'expiration', $context );
421
	}
422
423
	/**
424
	 * Alias for self::get_next_renewal_date().
425
	 *
426
	 * @since 1.0.19
427
	 * @param  string $context View or edit context.
428
	 * @return string
429
	 */
430
	public function get_expiration( $context = 'view' ) {
431
		return $this->get_next_renewal_date( $context );
432
	}
433
434
	/**
435
	 * Retrieves the expiration date in a timestamp
436
	 *
437
	 * @since  1.0.0
438
	 * @return int
439
	 */
440
	public function get_expiration_time() {
441
		$expiration = $this->get_expiration();
442
443
		if ( empty( $expiration ) || '0000-00-00 00:00:00' == $expiration ) {
444
			return current_time( 'timestamp' );
445
		}
446
447
		$expiration = strtotime( $expiration, current_time( 'timestamp' ) );
448
		return $expiration < current_time( 'timestamp' ) ? current_time( 'timestamp' ) : $expiration;
449
	}
450
451
	/**
452
	 * Get GMT date when the subscription will renew.
453
	 *
454
	 * @since 1.0.19
455
	 * @param  string $context View or edit context.
456
	 * @return string
457
	 */
458
	public function get_next_renewal_date_gmt( $context = 'view' ) {
459
        $date = $this->get_next_renewal_date( $context );
460
461
        if ( $date ) {
462
            $date = get_gmt_from_date( $date );
463
        }
464
		return $date;
465
	}
466
467
	/**
468
	 * Get the subscription's trial period.
469
	 *
470
	 * @since 1.0.19
471
	 * @param  string $context View or edit context.
472
	 * @return string
473
	 */
474
	public function get_trial_period( $context = 'view' ) {
475
		return $this->get_prop( 'trial_period', $context );
476
	}
477
478
	/**
479
	 * Get the subscription's status.
480
	 *
481
	 * @since 1.0.19
482
	 * @param  string $context View or edit context.
483
	 * @return string
484
	 */
485
	public function get_status( $context = 'view' ) {
486
		return $this->get_prop( 'status', $context );
487
	}
488
489
	/**
490
	 * Get the subscription's profile id.
491
	 *
492
	 * @since 1.0.19
493
	 * @param  string $context View or edit context.
494
	 * @return string
495
	 */
496
	public function get_profile_id( $context = 'view' ) {
497
		return $this->get_prop( 'profile_id', $context );
498
	}
499
500
	/*
501
	|--------------------------------------------------------------------------
502
	| Setters
503
	|--------------------------------------------------------------------------
504
	*/
505
506
	/**
507
	 * Set customer id.
508
	 *
509
	 * @since 1.0.19
510
	 * @param  int $value The customer's id.
511
	 */
512
	public function set_customer_id( $value ) {
513
		$this->set_prop( 'customer_id', (int) $value );
514
	}
515
516
	/**
517
	 * Set parent invoice id.
518
	 *
519
	 * @since 1.0.19
520
	 * @param  int $value The parent invoice id.
521
	 */
522
	public function set_parent_invoice_id( $value ) {
523
		$this->set_prop( 'parent_payment_id', (int) $value );
524
	}
525
526
	/**
527
	 * Alias for self::set_parent_invoice_id().
528
	 *
529
	 * @since 1.0.19
530
	 * @param  int $value The parent invoice id.
531
	 */
532
    public function set_parent_payment_id( $value ) {
533
        $this->set_parent_invoice_id( $value );
534
	}
535
536
	/**
537
     * Alias for self::set_parent_invoice_id().
538
     *
539
     * @since 1.0.19
540
	 * @param  int $value The parent invoice id.
541
     */
542
    public function set_original_payment_id( $value ) {
543
        $this->set_parent_invoice_id( $value );
544
	}
545
546
	/**
547
	 * Set subscription's product id.
548
	 *
549
	 * @since 1.0.19
550
	 * @param  int $value The subscription product id.
551
	 */
552
	public function set_product_id( $value ) {
553
		$this->set_prop( 'product_id', (int) $value );
554
	}
555
556
	/**
557
	 * Set the period of a renewal.
558
	 *
559
	 * @since 1.0.19
560
	 * @param  string $value The renewal period.
561
	 */
562
	public function set_period( $value ) {
563
		$this->set_prop( 'period', $value );
564
	}
565
566
	/**
567
	 * Set number of periods each renewal is valid for.
568
	 *
569
	 * @since 1.0.19
570
	 * @param  int $value The subscription frequency.
571
	 */
572
	public function set_frequency( $value ) {
573
		$value = empty( $value ) ? 1 : (int) $value;
574
		$this->set_prop( 'frequency', absint( $value ) );
575
	}
576
577
	/**
578
	 * Set the initial amount for the subscription.
579
	 *
580
	 * @since 1.0.19
581
	 * @param  float $value The initial subcription amount.
582
	 */
583
	public function set_initial_amount( $value ) {
584
		$this->set_prop( 'initial_amount', wpinv_sanitize_amount( $value ) );
585
	}
586
587
	/**
588
	 * Set the recurring amount for the subscription.
589
	 *
590
	 * @since 1.0.19
591
	 * @param  float $value The recurring subcription amount.
592
	 */
593
	public function set_recurring_amount( $value ) {
594
		$this->set_prop( 'recurring_amount', wpinv_sanitize_amount( $value ) );
595
	}
596
597
	/**
598
	 * Set number of times that this subscription can be renewed.
599
	 *
600
	 * @since 1.0.19
601
	 * @param  int $value Bill times.
602
	 */
603
	public function set_bill_times( $value ) {
604
		$this->set_prop( 'bill_times', (int) $value );
605
	}
606
607
	/**
608
	 * Get transaction id of this subscription's parent invoice.
609
	 *
610
	 * @since 1.0.19
611
	 * @param string $value Bill times.
612
	 */
613
	public function set_transaction_id( $value ) {
614
		$this->set_prop( 'transaction_id', sanitize_text_field( $value ) );
615
	}
616
617
	/**
618
	 * Set date when this subscription started.
619
	 *
620
	 * @since 1.0.19
621
	 * @param string $value strtotime compliant date.
622
	 */
623
	public function set_created( $value ) {
624
        $date = strtotime( $value );
625
626
        if ( $date && $value !== '0000-00-00 00:00:00' ) {
627
            $this->set_prop( 'created', date( 'Y-m-d H:i:s', $date ) );
628
            return;
629
        }
630
631
		$this->set_prop( 'created', '' );
632
633
	}
634
635
	/**
636
	 * Alias for self::set_created().
637
	 *
638
	 * @since 1.0.19
639
	 * @param string $value strtotime compliant date.
640
	 */
641
	public function set_date_created( $value ) {
642
		$this->set_created( $value );
643
    }
644
645
	/**
646
	 * Set the date that the subscription will renew.
647
	 *
648
	 * @since 1.0.19
649
	 * @param string $value strtotime compliant date.
650
	 */
651
	public function set_next_renewal_date( $value ) {
652
		$date = strtotime( $value );
653
654
        if ( $date && $value !== '0000-00-00 00:00:00' ) {
655
            $this->set_prop( 'expiration', date( 'Y-m-d H:i:s', $date ) );
656
            return;
657
		}
658
659
		$this->set_prop( 'expiration', '' );
660
661
	}
662
663
	/**
664
	 * Alias for self::set_next_renewal_date().
665
	 *
666
	 * @since 1.0.19
667
	 * @param string $value strtotime compliant date.
668
	 */
669
	public function set_expiration( $value ) {
670
		$this->set_next_renewal_date( $value );
671
    }
672
673
	/**
674
	 * Set the subscription's trial period.
675
	 *
676
	 * @since 1.0.19
677
	 * @param string $value trial period e.g 1 year.
678
	 */
679
	public function set_trial_period( $value ) {
680
		$this->set_prop( 'trial_period', $value );
681
	}
682
683
	/**
684
	 * Set the subscription's status.
685
	 *
686
	 * @since 1.0.19
687
	 * @param string $new_status    New subscription status.
688
	 */
689
	public function set_status( $new_status ) {
690
691
		// Abort if this is not a valid status;
692
		if ( ! array_key_exists( $new_status, getpaid_get_subscription_statuses() ) ) {
693
			return;
694
		}
695
696
		$old_status = $this->get_status();
697
		$this->set_prop( 'status', $new_status );
698
699
		if ( true === $this->object_read && $old_status !== $new_status ) {
700
			$this->status_transition = array(
0 ignored issues
show
Documentation Bug introduced by
It seems like array('from' => ! empty(...s, 'to' => $new_status) of type array<string,mixed|string> is incompatible with the declared type boolean of property $status_transition.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
701
				'from'   => ! empty( $this->status_transition['from'] ) ? $this->status_transition['from'] : $old_status,
702
				'to'     => $new_status,
703
			);
704
		}
705
706
	}
707
708
	/**
709
	 * Set the subscription's (remote) profile id.
710
	 *
711
	 * @since 1.0.19
712
	 * @param  string $value the remote profile id.
713
	 */
714
	public function set_profile_id( $value ) {
715
		$this->set_prop( 'profile_id', sanitize_text_field( $value ) );
716
	}
717
718
	/*
719
	|--------------------------------------------------------------------------
720
	| Boolean methods
721
	|--------------------------------------------------------------------------
722
	|
723
	| Return true or false.
724
	|
725
	*/
726
727
	/**
728
     * Checks if the subscription has a given status.
729
	 *
730
	 * @param string|array String or array of strings to check for.
731
	 * @return bool
732
     */
733
    public function has_status( $status ) {
734
        return in_array( $this->get_status(), wpinv_clean( wpinv_parse_list( $status ) ) );
735
	}
736
737
	/**
738
     * Checks if the subscription has a trial period.
739
	 *
740
	 * @return bool
741
     */
742
    public function has_trial_period() {
743
		$period = $this->get_trial_period();
744
        return ! empty( $period );
745
	}
746
747
	/**
748
	 * Is the subscription active?
749
	 *
750
	 * @return bool
751
	 */
752
	public function is_active() {
753
		return $this->has_status( 'active trialling' ) && ! $this->is_expired();
754
	}
755
756
	/**
757
	 * Is the subscription expired?
758
	 *
759
	 * @return bool
760
	 */
761
	public function is_expired() {
762
		return $this->has_status( 'expired' ) || ( $this->has_status( 'active cancelled trialling' ) && $this->get_expiration_time() < current_time( 'mysql' ) );
763
	}
764
765
	/**
766
	 * Is this the last renewals?
767
	 *
768
	 * @return bool
769
	 */
770
	public function is_last_renewal() {
771
		$max_bills = $this->get_bill_times();
772
		return ! empty( $max_bills ) && $max_bills <= $this->get_times_billed();
773
	}
774
775
	/*
776
	|--------------------------------------------------------------------------
777
	| Additional methods
778
	|--------------------------------------------------------------------------
779
	|
780
	| Calculating subscription details.
781
	|
782
	*/
783
784
	/**
785
	 * Backwards compatibilty.
786
	 */
787
	public function create( $data = array() ) {
788
789
		// Set the properties.
790
		if ( is_array( $data ) ) {
791
			$this->set_props( $data );
792
		}
793
794
		// Save the item.
795
		return $this->save();
796
797
	}
798
799
	/**
800
	 * Backwards compatibilty.
801
	 */
802
	public function update( $args = array() ) {
803
		return $this->create( $args );
804
	}
805
806
    /**
807
     * Retrieve renewal payments for a subscription
808
     *
809
     * @since  1.0.0
810
     * @return WP_Post[]
811
     */
812
    public function get_child_payments( $hide_pending = true ) {
813
814
		$statuses = array( 'publish', 'wpi-processing', 'wpi-renewal' );
815
816
		if ( ! $hide_pending ) {
817
			$statuses = array_keys( wpinv_get_invoice_statuses() );
818
		}
819
820
        return get_posts(
0 ignored issues
show
Bug Best Practice introduced by
The expression return get_posts(array('...ype' => 'wpi_invoice')) returns an array which contains values of type integer which are incompatible with the documented value type WP_Post.
Loading history...
821
			array(
822
            	'post_parent'    => $this->get_parent_payment_id(),
823
            	'numberposts'    => -1,
824
            	'post_status'    => $statuses,
825
            	'orderby'        => 'ID',
826
            	'order'          => 'ASC',
827
            	'post_type'      => 'wpi_invoice'
828
			)
829
		);
830
    }
831
832
    /**
833
     * Counts the number of invoices generated for the subscription.
834
     *
835
     * @since  1.0.0
836
     * @return int
837
     */
838
    public function get_total_payments() {
839
		global $wpdb;
840
841
		$count = (int) $wpdb->get_var(
842
			$wpdb->prepare(
843
				"SELECT COUNT(ID) FROM $wpdb->posts WHERE post_parent=%d AND post_status IN ( 'publish', 'wpi-processing', 'wpi-renewal' )",
844
				$this->get_parent_invoice_id()
845
			)
846
		);
847
848
		// Maybe include parent invoice.
849
        if ( $this->get_parent_payment()->is_paid() ) {
850
            $count++;
851
        }
852
853
        return $count;
854
    }
855
856
    /**
857
     * Counts the number of payments for the subscription.
858
     *
859
     * @since  1.0.2
860
     * @return int
861
     */
862
    public function get_times_billed() {
863
        $times_billed = $this->get_total_payments();
864
865
        if ( (float) $this->get_initial_amount() == 0 && $times_billed > 0 ) {
866
            $times_billed--;
867
        }
868
869
        return (int) $times_billed;
870
    }
871
872
    /**
873
     * Records a new payment on the subscription
874
     *
875
     * @since  2.4
876
     * @param  array $args Array of values for the payment, including amount and transaction ID
877
	 * @param  WPInv_Invoice $invoice If adding an existing invoice.
878
     * @return bool
879
     */
880
    public function add_payment( $args = array(), $invoice = false ) {
881
882
		// Process each payment once.
883
        if ( ! empty( $args['transaction_id'] ) && $this->payment_exists( $args['transaction_id'] ) ) {
884
            return false;
885
        }
886
887
		// Are we creating a new invoice?
888
		if ( empty( $invoice ) ) {
889
			$invoice = $this->create_payment();
890
891
			if ( empty( $invoice ) ) {
892
				return false;
893
			}
894
895
		}
896
897
		$invoice->set_status( 'wpi-renewal' );
898
899
		// Maybe set a transaction id.
900
		if ( ! empty( $args['transaction_id'] ) ) {
901
			$invoice->set_transaction_id( $args['transaction_id'] );
902
		}
903
904
		// Set the completed date.
905
		$invoice->set_completed_date( current_time( 'mysql' ) );
906
907
		// And the gateway.
908
		if ( ! empty( $args['gateway'] ) ) {
909
			$invoice->set_gateway( $args['gateway'] );
910
		}
911
912
		$invoice->save();
913
914
		if ( ! $invoice->exists() ) {
915
			return false;
916
		}
917
918
		do_action( 'getpaid_after_create_subscription_renewal_invoice', $invoice, $this );
919
		do_action( 'wpinv_recurring_add_subscription_payment', $invoice, $this );
920
        do_action( 'wpinv_recurring_record_payment', $invoice->get_id(), $this->get_parent_invoice_id(), $invoice->get_recurring_total(), $invoice->get_transaction_id() );
921
922
        update_post_meta( $invoice->get_id(), '_wpinv_subscription_id', $this->id );
923
924
        return $invoice->get_id();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $invoice->get_id() returns the type integer which is incompatible with the documented return type boolean.
Loading history...
925
	}
926
927
	/**
928
     * Creates a new invoice and returns it.
929
     *
930
     * @since  1.0.19
931
     * @return WPInv_Invoice|bool
932
     */
933
    public function create_payment() {
934
935
		$parent_invoice = $this->get_parent_payment();
936
937
		if ( ! $parent_invoice->exists() ) {
938
			return false;
939
		}
940
941
		// Duplicate the parent invoice.
942
		$invoice = getpaid_duplicate_invoice( $parent_invoice );
943
		$invoice->set_parent_id( $parent_invoice->get_id() );
944
945
		// Set invoice items.
946
		$subscription_group = getpaid_get_invoice_subscription_group( $parent_invoice->get_id(), $this->get_id() );
947
		$allowed_items      = empty( $subscription_group ) ? array( $this->get_product_id() ) : array_keys( $subscription_group['items'] );
948
		$invoice_items      = array();
949
950
		foreach ( $invoice->get_items() as $item ) {
951
			if ( in_array( $item->get_id(), $allowed_items ) ) {
952
				$invoice_items[] = $item;
953
			}
954
		}
955
956
		$invoice->set_items( $invoice_items );
957
958
		if ( ! empty( $subscription_group['fees'] ) ) {
959
			$invoice->set_fees( $subscription_group['fees'] );
960
		}
961
962
		// Maybe recalculate discount (Pre-GetPaid Fix).
963
		$discount = new WPInv_Discount( $invoice->get_discount_code() );
964
		if ( $discount->exists() && $discount->is_recurring() && 0 == $invoice->get_total_discount() ) {
965
			$invoice->add_discount( getpaid_calculate_invoice_discount( $invoice, $discount ) );
966
		}
967
968
		$invoice->recalculate_total();
969
		$invoice->set_status( 'wpi-pending' );
970
		$invoice->save();
971
972
		return $invoice->exists() ? $invoice : false;
973
    }
974
975
	/**
976
	 * Renews or completes a subscription
977
	 *
978
	 * @since  1.0.0
979
	 * @return int The subscription's id
980
	 */
981
	public function renew() {
982
983
		// Complete subscription if applicable
984
		if ( $this->is_last_renewal() ) {
985
			return $this->complete();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->complete() could also return false which is incompatible with the documented return type integer. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
986
		}
987
988
		// Calculate new expiration
989
		$frequency      = $this->get_frequency();
990
		$period         = $this->get_period();
991
		$new_expiration = strtotime( "+ $frequency $period", $this->get_expiration_time() );
992
993
		$this->set_expiration( date( 'Y-m-d H:i:s',$new_expiration ) );
994
		$this->set_status( 'active' );
995
		$this->save();
996
997
		do_action( 'getpaid_subscription_renewed', $this );
998
999
		return $this->get_id();
1000
	}
1001
1002
	/**
1003
	 * Marks a subscription as completed
1004
	 *
1005
	 * Subscription is completed when the number of payments matches the billing_times field
1006
	 *
1007
	 * @since  1.0.0
1008
	 * @return int|bool Subscription id or false if the subscription is cancelled.
1009
	 */
1010
	public function complete() {
1011
1012
		// Only mark a subscription as complete if it's not already cancelled.
1013
		if ( $this->has_status( 'cancelled' ) ) {
1014
			return false;
1015
		}
1016
1017
		$this->set_status( 'completed' );
1018
		return $this->save();
1019
1020
	}
1021
1022
	/**
1023
	 * Marks a subscription as expired
1024
	 *
1025
	 * @since  1.0.0
1026
	 * @param  bool $check_expiration
1027
	 * @return int|bool Subscription id or false if $check_expiration is true and expiration date is in the future.
1028
	 */
1029
	public function expire( $check_expiration = false ) {
1030
1031
		if ( $check_expiration && $this->get_expiration_time() > current_time( 'timestamp' ) ) {
1032
			// Do not mark as expired since real expiration date is in the future
1033
			return false;
1034
		}
1035
1036
		$this->set_status( 'expired' );
1037
		return $this->save();
1038
1039
	}
1040
1041
	/**
1042
	 * Marks a subscription as failing
1043
	 *
1044
	 * @since  2.4.2
1045
	 * @return int Subscription id.
1046
	 */
1047
	public function failing() {
1048
		$this->set_status( 'failing' );
1049
		return $this->save();
1050
	}
1051
1052
    /**
1053
     * Marks a subscription as cancelled
1054
     *
1055
     * @since  1.0.0
1056
     * @return int Subscription id.
1057
     */
1058
    public function cancel() {
1059
		$this->set_status( 'cancelled' );
1060
		return $this->save();
1061
    }
1062
1063
	/**
1064
	 * Determines if a subscription can be cancelled both locally and with a payment processor.
1065
	 *
1066
	 * @since  1.0.0
1067
	 * @return bool
1068
	 */
1069
	public function can_cancel() {
1070
		return apply_filters( 'wpinv_subscription_can_cancel', $this->has_status( $this->get_cancellable_statuses() ), $this );
1071
	}
1072
1073
    /**
1074
     * Returns an array of subscription statuses that can be cancelled
1075
     *
1076
     * @access      public
1077
     * @since       1.0.0
1078
     * @return      array
1079
     */
1080
    public function get_cancellable_statuses() {
1081
        return apply_filters( 'wpinv_recurring_cancellable_statuses', array( 'active', 'trialling', 'failing' ) );
1082
    }
1083
1084
	/**
1085
	 * Retrieves the URL to cancel subscription
1086
	 *
1087
	 * @since  1.0.0
1088
	 * @return string
1089
	 */
1090
	public function get_cancel_url() {
1091
		$url = getpaid_get_authenticated_action_url( 'subscription_cancel', $this->get_view_url() );
1092
		return apply_filters( 'wpinv_subscription_cancel_url', $url, $this );
1093
	}
1094
1095
	/**
1096
	 * Retrieves the URL to view a subscription
1097
	 *
1098
	 * @since  1.0.19
1099
	 * @return string
1100
	 */
1101
	public function get_view_url() {
1102
1103
		$url = getpaid_get_tab_url( 'gp-subscriptions', get_permalink( (int) wpinv_get_option( 'invoice_subscription_page' ) ) );
0 ignored issues
show
Bug introduced by
It seems like get_permalink((int)wpinv...ce_subscription_page')) can also be of type false; however, parameter $default of getpaid_get_tab_url() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1103
		$url = getpaid_get_tab_url( 'gp-subscriptions', /** @scrutinizer ignore-type */ get_permalink( (int) wpinv_get_option( 'invoice_subscription_page' ) ) );
Loading history...
1104
		$url = add_query_arg( 'subscription', $this->get_id(), $url );
1105
1106
		return apply_filters( 'getpaid_get_subscription_view_url', $url, $this );
1107
	}
1108
1109
	/**
1110
	 * Determines if subscription can be manually renewed
1111
	 *
1112
	 * This method is filtered by payment gateways in order to return true on subscriptions
1113
	 * that can be renewed manually
1114
	 *
1115
	 * @since  2.5
1116
	 * @return bool
1117
	 */
1118
	public function can_renew() {
1119
		return apply_filters( 'wpinv_subscription_can_renew', true, $this );
1120
	}
1121
1122
	/**
1123
	 * Retrieves the URL to renew a subscription
1124
	 *
1125
	 * @since  2.5
1126
	 * @return string
1127
	 */
1128
	public function get_renew_url() {
1129
		$url = wp_nonce_url( add_query_arg( array( 'getpaid-action' => 'renew_subscription', 'sub_id' => $this->get_id ) ), 'getpaid-nonce' );
0 ignored issues
show
Bug Best Practice introduced by
The property get_id does not exist on WPInv_Subscription. Since you implemented __get, consider adding a @property annotation.
Loading history...
1130
		return apply_filters( 'wpinv_subscription_renew_url', $url, $this );
1131
	}
1132
1133
	/**
1134
	 * Determines if subscription can have their payment method updated
1135
	 *
1136
	 * @since  1.0.0
1137
	 * @return bool
1138
	 */
1139
	public function can_update() {
1140
		return apply_filters( 'wpinv_subscription_can_update', false, $this );
1141
	}
1142
1143
	/**
1144
	 * Retrieves the URL to update subscription
1145
	 *
1146
	 * @since  1.0.0
1147
	 * @return string
1148
	 */
1149
	public function get_update_url() {
1150
		$url = add_query_arg( array( 'action' => 'update', 'subscription_id' => $this->get_id() ) );
1151
		return apply_filters( 'wpinv_subscription_update_url', $url, $this );
1152
	}
1153
1154
	/**
1155
	 * Retrieves the subscription status label
1156
	 *
1157
	 * @since  1.0.0
1158
	 * @return string
1159
	 */
1160
	public function get_status_label() {
1161
		return getpaid_get_subscription_status_label( $this->get_status() );
1162
	}
1163
1164
	/**
1165
	 * Retrieves the subscription status class
1166
	 *
1167
	 * @since  1.0.19
1168
	 * @return string
1169
	 */
1170
	public function get_status_class() {
1171
		$statuses = getpaid_get_subscription_status_classes();
1172
		return isset( $statuses[ $this->get_status() ] ) ? $statuses[ $this->get_status() ] : 'badge-dark';
1173
	}
1174
1175
    /**
1176
     * Retrieves the subscription status label
1177
     *
1178
     * @since  1.0.0
1179
     * @return string
1180
     */
1181
    public function get_status_label_html() {
1182
1183
		$status_label = sanitize_text_field( $this->get_status_label() );
1184
		$class        = esc_attr( $this->get_status_class() );
1185
		$status       = sanitize_html_class( $this->get_status() );
1186
1187
		return "<span class='bsui'><span class='badge $class $status'>$status_label</span></span>";
1188
    }
1189
1190
    /**
1191
     * Determines if a payment exists with the specified transaction ID
1192
     *
1193
     * @since  2.4
1194
     * @param  string $txn_id The transaction ID from the merchant processor
1195
     * @return bool
1196
     */
1197
    public function payment_exists( $txn_id = '' ) {
1198
		$invoice_id = WPInv_Invoice::get_invoice_id_by_field( $txn_id, 'transaction_id' );
1199
        return ! empty( $invoice_id );
1200
	}
1201
1202
	/**
1203
	 * Handle the status transition.
1204
	 */
1205
	protected function status_transition() {
1206
		$status_transition = $this->status_transition;
1207
1208
		// Reset status transition variable.
1209
		$this->status_transition = false;
1210
1211
		if ( $status_transition ) {
1212
			try {
1213
1214
				// Fire a hook for the status change.
1215
				do_action( 'wpinv_subscription_' . $status_transition['to'], $this->get_id(), $this, $status_transition );
1216
				do_action( 'getpaid_subscription_' . $status_transition['to'], $this, $status_transition );
1217
1218
				if ( ! empty( $status_transition['from'] ) ) {
1219
1220
					/* translators: 1: old subscription status 2: new subscription status */
1221
					$transition_note = sprintf( __( 'Subscription status changed from %1$s to %2$s.', 'invoicing' ), getpaid_get_subscription_status_label( $status_transition['from'] ), getpaid_get_subscription_status_label( $status_transition['to'] ) );
1222
1223
					// Note the transition occurred.
1224
					$this->get_parent_payment()->add_note( $transition_note, false, false, true );
1225
1226
					// Fire another hook.
1227
					do_action( 'getpaid_subscription_status_' . $status_transition['from'] . '_to_' . $status_transition['to'], $this->get_id(), $this );
1228
					do_action( 'getpaid_subscription_status_changed', $this, $status_transition['from'], $status_transition['to'] );
1229
1230
				} else {
1231
					/* translators: %s: new invoice status */
1232
					$transition_note = sprintf( __( 'Subscription status set to %s.', 'invoicing' ), getpaid_get_subscription_status_label( $status_transition['to'] ) );
1233
1234
					// Note the transition occurred.
1235
					$this->get_parent_payment()->add_note( $transition_note, false, false, true );
1236
1237
				}
1238
			} catch ( Exception $e ) {
1239
				$this->get_parent_payment()->add_note( __( 'Error during subscription status transition.', 'invoicing' ) . ' ' . $e->getMessage() );
1240
			}
1241
		}
1242
1243
	}
1244
1245
	/**
1246
	 * Save data to the database.
1247
	 *
1248
	 * @since 1.0.19
1249
	 * @return int subscription ID
1250
	 */
1251
	public function save() {
1252
		parent::save();
1253
		$this->status_transition();
1254
		return $this->get_id();
1255
	}
1256
1257
	/**
1258
	 * Activates a subscription.
1259
	 *
1260
	 * @since 1.0.19
1261
	 * @return int subscription ID
1262
	 */
1263
	public function activate() {
1264
		$status = 'trialling' == $this->get_status() ? 'trialling' : 'active';
1265
		$this->set_status( $status );
1266
		return $this->save();
1267
	}
1268
1269
}
1270