Passed
Pull Request — master (#375)
by Brian
121:12
created

GetPaid_Payment_Form_Submission::get_tax()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 1
c 1
b 0
f 1
dl 0
loc 2
rs 10
cc 2
nc 2
nop 1
1
<?php
2
if ( ! defined( 'ABSPATH' ) ) {
3
	exit;
4
}
5
6
/**
7
 * Payment form submission class
8
 *
9
 */
10
class GetPaid_Payment_Form_Submission {
11
12
    /**
13
	 * Submission ID
14
	 *
15
	 * @var string
16
	 */
17
	public $id = null;
18
19
	/**
20
	 * Sets the associated payment form.
21
	 *
22
	 * @var GetPaid_Payment_Form
23
	 */
24
    protected $payment_form = null;
25
26
    /**
27
	 * The country for the submission.
28
	 *
29
	 * @var string
30
	 */
31
	public $country = null;
32
33
    /**
34
	 * The state for the submission.
35
	 *
36
	 * @since 1.0.19
37
	 * @var string
38
	 */
39
	public $state = null;
40
41
	/**
42
	 * The invoice associated with the submission.
43
	 *
44
	 * @var WPInv_Invoice
45
	 */
46
	protected $invoice = null;
47
48
	/**
49
	 * The discount associated with the submission.
50
	 *
51
	 * @var WPInv_Discount
52
	 */
53
	protected $discount = null;
54
55
	/**
56
	 * The raw submission data.
57
	 *
58
	 * @var array
59
	 */
60
	protected $data = null;
61
62
	/**
63
	 * Whether this submission contains a recurring item.
64
	 *
65
	 * @var bool
66
	 */
67
	public $has_recurring = false;
68
69
	/**
70
	 * The sub total amount for the submission.
71
	 *
72
	 * @var float
73
	 */
74
	public $subtotal_amount = 0;
75
76
	/**
77
	 * The total discount amount for the submission.
78
	 *
79
	 * @var float
80
	 */
81
	protected $total_discount_amount = 0;
82
83
	/**
84
	 * The total tax amount for the submission.
85
	 *
86
	 * @var float
87
	 */
88
	protected $total_tax_amount = 0;
89
90
	/**
91
	 * The total fees amount for the submission.
92
	 *
93
	 * @var float
94
	 */
95
	protected $total_fees_amount = 0;
96
97
	/**
98
	 * An array of fees for the submission.
99
	 *
100
	 * @var array
101
	 */
102
	protected $fees = array();
103
104
	/**
105
	 * An array of discounts for the submission.
106
	 *
107
	 * @var array
108
	 */
109
	protected $discounts = array();
110
111
	/**
112
	 * An array of taxes for the submission.
113
	 *
114
	 * @var array
115
	 */
116
	protected $taxes = array();
117
118
	/**
119
	 * An array of items for the submission.
120
	 *
121
	 * @var GetPaid_Form_Item[]
122
	 */
123
	protected $items = array();
124
125
	/**
126
	 * The last error.
127
	 *
128
	 * @var string
129
	 */
130
	public $last_error = null;
131
132
	/**
133
	 * Is the discount valid?
134
	 *
135
	 * @var string
136
	 */
137
    public $is_discount_valid = true;
138
139
    /**
140
	 * Class constructor.
141
	 *
142
	 */
143
	public function __construct() {
144
145
		// Set the state and country to the default state and country.
146
		$this->country = wpinv_default_billing_country();
147
		$this->state = wpinv_get_default_state();
0 ignored issues
show
Documentation Bug introduced by
It seems like wpinv_get_default_state() can also be of type false. However, the property $state is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
148
149
		// Do we have an actual submission?
150
		if ( isset( $_POST['getpaid_payment_form_submission'] ) ) {
151
			$this->load_data( $_POST );
152
		}
153
	}
154
155
	/**
156
	 * Loads submission data.
157
	 *
158
	 * @param array $data
159
	 */
160
	public function load_data( $data ) {
161
162
		// Prepare submitted data...
163
		$data = wp_unslash( $data );
164
165
		// Fitter the data.
166
		$data = apply_filters( 'getpaid_submission_data', $data, $this );
167
168
		$this->data = $data;
0 ignored issues
show
Documentation Bug introduced by
It seems like $data can also be of type string. However, the property $data is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
169
170
		$this->id = md5( wp_json_encode( $data ) );
0 ignored issues
show
Bug introduced by
It seems like wp_json_encode($data) can also be of type false; however, parameter $str of md5() 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

170
		$this->id = md5( /** @scrutinizer ignore-type */ wp_json_encode( $data ) );
Loading history...
171
172
		// Every submission needs an active payment form.
173
		if ( empty( $data['form_id'] ) ) {
174
			$this->last_error = __( 'Missing payment form', 'invoicing' );
175
            return;
176
		}
177
178
		// Fetch the payment form.
179
		$form = new GetPaid_Payment_Form( $data['form_id'] );
180
181
		if ( ! $form->is_active() ) {
182
			$this->last_error = __( 'Payment form not active', 'invoicing' );
183
			return;
184
		}
185
186
		// Fetch the payment form.
187
		$this->payment_form = $form;
188
189
		// For existing invoices, make sure that it is valid.
190
        if ( ! empty( $data['invoice_id'] ) ) {
191
            $invoice = wpinv_get_invoice( $data['invoice_id'] );
192
193
            if ( empty( $invoice ) ) {
194
				$this->last_error = __( 'Invalid invoice', 'invoicing' );
195
                return;
196
            }
197
198
			$this->payment_form->set_items( $invoice->cart_details );
199
200
			$this->country = $invoice->country;
201
			$this->state = $invoice->state;
202
203
		// Default forms do not have items.
204
        } else if ( $form->is_default() && isset( $data['form_items'] ) ) {
205
			$this->payment_form->set_items( $data['form_items'] );
206
		}
207
208
		// User's country.
209
		if ( ! empty( $data['wpinv_country'] ) ) {
210
			$this->country = $data['wpinv_country'];
211
		}
212
213
		// User's state.
214
		if ( ! empty( $data['wpinv_state'] ) ) {
215
			$this->country = $data['wpinv_state'];
216
		}
217
218
		// Handle discounts.
219
		$this->maybe_prepare_discount();
220
221
		// Handle items.
222
		$selected_items = array();
223
		if ( ! empty( $data['getpaid-items'] ) ) {
224
			$selected_items = wpinv_clean( $data['getpaid-items'] );
225
		}
226
227
		foreach ( $this->payment_form->get_items() as $item ) {
228
229
			// Continue if this is an optional item and it has not been selected.
230
			if ( ! $item->is_required() && ! isset( $selected_items[ $item->get_id() ] ) ) {
231
				continue;
232
			}
233
234
			// (maybe) let customers change the quantities and prices.
235
			if ( isset( $selected_items[ $item->get_id() ] ) ) {
236
237
				// Maybe change the quantities.
238
				if ( $item->allows_quantities() && is_numeric( $selected_items[ $item->get_id() ]['quantity'] ) ) {
239
					$item->set_quantity( (int) $selected_items[ $item->get_id() ]['quantity'] );
240
				}
241
242
				// Maybe change the price.
243
				if ( $item->user_can_set_their_price() ) {
244
					$price = (float) wpinv_sanitize_amount( $selected_items[ $item->get_id() ]['price'] );
245
246
					// But don't get lower than the minimum price.
247
					if ( $price < $item->get_minimum_price() ) {
248
						$price = $item->get_minimum_price();
249
					}
250
251
					$item->set_price( $price );
252
				}
253
254
			}
255
256
			// Add the item to the form.
257
			$this->add_item( $item );
258
259
		}
260
261
		// Fired when we are done processing a submission.
262
		do_action( 'getpaid_process_submission', $this );
263
264
		// Remove invalid discount.
265
		$this->maybe_remove_discount();
266
267
	}
268
269
    /**
270
	 * Returns the payment form.
271
	 *
272
	 * @since 1.0.19
273
	 * @return GetPaid_Payment_Form
274
	 */
275
	public function get_payment_form() {
276
		return $this->payment_form;
277
	}
278
279
	/**
280
	 * Returns the associated invoice.
281
	 *
282
	 * @since 1.0.19
283
	 * @return WPInv_Invoice
284
	 */
285
	public function get_invoice() {
286
		return $this->invoice;
287
	}
288
289
	/**
290
	 * Checks whether there is an invoice associated with this submission.
291
	 *
292
	 * @since 1.0.19
293
	 * @return bool
294
	 */
295
	public function has_invoice() {
296
		return ! empty( $this->invoice );
297
	}
298
	
299
	/**
300
	 * Returns the appropriate currency for the submission.
301
	 *
302
	 * @since 1.0.19
303
	 * @return string
304
	 */
305
	public function get_currency() {
306
		if ( $this->has_invoice() ) {
307
			return $this->invoice->get_currency();
308
		}
309
		return wpinv_get_currency();
310
    }
311
312
    /**
313
	 * Returns the raw submission data.
314
	 *
315
	 * @since 1.0.19
316
	 * @return array
317
	 */
318
	public function get_data() {
319
		return $this->data;
320
	}
321
322
	///////// Items //////////////
323
324
	/**
325
	 * Adds an item to the submission.
326
	 *
327
	 * @since 1.0.19
328
	 * @param GetPaid_Form_Item $item
329
	 */
330
	public function add_item( $item ) {
331
332
		// Make sure that it is available for purchase.
333
		if ( ! $item->can_purchase() ) {
334
			return;
335
		}
336
337
		// Do we have a recurring item?
338
		if ( $item->is_recurring() ) {
339
			$this->has_recurring = true;
340
		}
341
342
		$this->items[ $item->get_id() ] = $item;
343
344
		$this->subtotal_amount += $item->get_price();
345
346
		$this->process_item_discount( $item );
347
	}
348
349
	/**
350
	 * Retrieves a specific item.
351
	 *
352
	 * @since 1.0.19
353
	 */
354
	public function get_item( $item_id ) {
355
		return isset( $this->items[ $item_id ] ) ? $this->items[ $item_id ] : null;
356
	}
357
358
	/**
359
	 * Returns all items.
360
	 *
361
	 * @since 1.0.19
362
	 */
363
	public function get_items() {
364
		return $this->items;
365
	}
366
367
	///////// TAXES //////////////
368
369
	/**
370
	 * Adds a tax to the submission.
371
	 *
372
	 * @since 1.0.19
373
	 */
374
	public function add_tax( $name, $amount ) {
375
		$amount = wpinv_format_amount( wpinv_sanitize_amount( $amount ) );
376
377
		$this->total_tax_amount += $amount;
378
379
		if ( isset( $this->taxes[ $name ] ) ) {
380
			$this->taxes[ $name ] += $amount;
381
		} else {
382
			$this->taxes[ $name ] = $amount;
383
		}
384
385
	}
386
387
	/**
388
	 * Returns the total tax amount.
389
	 *
390
	 * @since 1.0.19
391
	 */
392
	public function get_total_tax() {
393
		return $this->total_tax_amount;
394
	}
395
396
	/**
397
	 * Retrieves a specific tax.
398
	 *
399
	 * @since 1.0.19
400
	 */
401
	public function get_tax( $name ) {
402
		return isset( $this->taxes[ $name ] ) ? $this->taxes[ $name ] : 0;
403
	}
404
405
	/**
406
	 * Returns all taxes.
407
	 *
408
	 * @since 1.0.19
409
	 */
410
	public function get_taxes() {
411
		return $this->taxes;
412
	}
413
414
	///////// DISCOUNTS //////////////
415
416
	/**
417
	 * Adds a discount to the submission.
418
	 *
419
	 * @since 1.0.19
420
	 */
421
	public function add_discount( $name, $amount ) {
422
		$amount = wpinv_format_amount( wpinv_sanitize_amount( $amount ) );
423
424
		$this->total_discount_amount += $amount;
425
426
		if ( isset( $this->discounts[ $name ] ) ) {
427
			$this->discounts[ $name ] += $amount;
428
		} else {
429
			$this->discounts[ $name ] = $amount;
430
		}
431
432
	}
433
434
	/**
435
	 * Removes a discount from the submission.
436
	 *
437
	 * @since 1.0.19
438
	 */
439
	public function remove_discount( $name ) {
440
441
		if ( isset( $this->discounts[ $name ] ) ) {
442
			$this->total_discount_amount -= $this->discounts[ $name ];
443
			unset( $this->discounts[ $name ] );
444
		}
445
446
	}
447
448
	/**
449
	 * Checks whether there is a discount code associated with this submission.
450
	 *
451
	 * @since 1.0.19
452
	 * @return bool
453
	 */
454
	public function has_discount_code() {
455
		return ! empty( $this->discount );
456
	}
457
458
	/**
459
	 * Returns the discount code.
460
	 *
461
	 * @since 1.0.19
462
	 * @return bool
463
	 */
464
	public function get_discount_code() {
465
		return $this->has_discount_code() ? $this->discount->code : '';
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->has_discou...is->discount->code : '' returns the type string which is incompatible with the documented return type boolean.
Loading history...
466
	}
467
468
	/**
469
	 * Prepares an item discount.
470
	 *
471
	 * @since 1.0.19
472
	 */
473
	public function maybe_prepare_discount() {
474
475
		// Do we have a discount?
476
		if ( empty( $this->data['discount'] ) ) {
477
			return;
478
		}
479
480
		// Fetch the discount.
481
		$discount = wpinv_get_discount_obj( $this->data['discount'] );
482
483
		// Ensure it is active.
484
        if ( ! $discount->exists() || ! $discount->is_active() || ! $discount->has_started() || $discount->is_expired() ) {
485
			$this->is_discount_valid = false;
0 ignored issues
show
Documentation Bug introduced by
The property $is_discount_valid was declared of type string, but false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
486
			$this->last_error = __( 'Invalid or expired discount code', 'invoicing' );
487
			return;
488
		}
489
490
		// For single use discounts...
491
		if ( $discount->is_single_use ) {
492
493
			if ( ! $this->has_billing_email() ) {
494
				$this->is_discount_valid = false;
495
				$this->last_error = __( 'You need to enter your billing email before applying this discount', 'invoicing' );
496
				return;
497
			}
498
499
			if ( ! $discount->is_valid_for_user( $this->get_billing_email() ) ) {
500
				$this->is_discount_valid = false;
501
				$this->last_error = __( 'You have already used this discount', 'invoicing' );
502
				return;
503
			}
504
		}
505
506
		// Set the discount.
507
		$this->discount = $discount;
508
509
	}
510
511
	/**
512
	 * Removes an invalid discount code.
513
	 *
514
	 * @since 1.0.19
515
	 */
516
	public function maybe_remove_discount() {
517
518
		// Do we have a discount?
519
		if ( empty( $this->has_discount_code() ) ) {
520
			return;
521
		}
522
523
		// Fetch the discount amount.
524
		$amount = $this->get_discount( 'Discount' );
525
526
		// Abort early if this is a "zero" discount.
527
		if ( empty( $amount ) ) {
528
			return;
529
		}
530
531
		$total = $this->subtotal_amount + $this->get_total_fees() + $this->get_total_tax();
532
533
		if ( ! $this->discount->is_minimum_amount_met( $total ) ) {
534
			$this->is_discount_valid = false;
0 ignored issues
show
Documentation Bug introduced by
The property $is_discount_valid was declared of type string, but false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
535
            $min = wpinv_price( wpinv_format_amount( $$this->discount->min_total ) );
536
			$this->last_error = sprintf( __( 'The minimum total for using this discount is %s', 'invoicing' ), $min );
537
        }
538
539
        if ( ! $$this->discount->is_maximum_amount_met( $total ) ) {
540
			$this->is_discount_valid = false;
541
            $max = wpinv_price( wpinv_format_amount( $$this->discount->max_total ) );
542
			$this->last_error = sprintf( __( 'The maximum total for using this discount is %s', 'invoicing' ), $max );
543
		}
544
545
		if ( ! $this->is_discount_valid ) {
546
			$this->discount = null;
547
			$this->remove_discount( 'Discount' );
548
		}
549
550
    }
551
552
	/**
553
	 * Maybe process discount.
554
	 *
555
	 * @since 1.0.19
556
	 * @param GetPaid_Form_Item $item
557
	 */
558
	public function process_item_discount( $item ) {
559
560
		// Abort early if there is no discount.
561
		if ( ! $this->has_discount_code() ) {
562
			return;
563
		}
564
565
		// Ensure that it is valid for this item.
566
		if ( ! $this->discount->is_valid_for_items( array( $item->get_id() ) ) ) {
567
			return;
568
		}
569
570
		// Fetch the discounted amount.
571
		$discount = $this->discount->get_discounted_amount( $item->get_price() );
572
573
		$this->add_discount( 'Discount', $discount );
574
575
	}
576
577
	/**
578
	 * Returns the total discount amount.
579
	 *
580
	 * @since 1.0.19
581
	 */
582
	public function get_total_discount() {
583
		return $this->total_discount_amount;
584
	}
585
586
	/**
587
	 * Gets a specific discount.
588
	 *
589
	 * @since 1.0.19
590
	 */
591
	public function get_discount( $name ) {
592
		return isset( $this->discounts[ $name ] ) ? $this->discounts[ $name ] : 0;
593
	}
594
595
	/**
596
	 * Returns all discounts.
597
	 *
598
	 * @since 1.0.19
599
	 */
600
	public function get_discounts() {
601
		return $this->discounts;
602
	}
603
604
	///////// FEES //////////////
605
606
	/**
607
	 * Adds a fee to the submission.
608
	 *
609
	 * @since 1.0.19
610
	 */
611
	public function add_fee( $name, $amount ) {
612
		$amount = wpinv_format_amount( wpinv_sanitize_amount( $amount ) );
613
614
		$this->total_fees_amount += $amount;
615
616
		if ( isset( $this->fees[ $name ] ) ) {
617
			$this->fees[ $name ] += $amount;
618
		} else {
619
			$this->fees[ $name ] = $amount;
620
		}
621
622
	}
623
624
	/**
625
	 * Returns the total fees amount.
626
	 *
627
	 * @since 1.0.19
628
	 */
629
	public function get_total_fees() {
630
		return $this->total_fees_amount;
631
	}
632
633
	/**
634
	 * Retrieves a specific fee.
635
	 *
636
	 * @since 1.0.19
637
	 */
638
	public function get_fee( $name ) {
639
		return isset( $this->fees[ $name ] ) ? $this->fees[ $name ] : 0;
640
	}
641
642
	/**
643
	 * Returns all fees.
644
	 *
645
	 * @since 1.0.19
646
	 */
647
	public function get_fees() {
648
		return $this->fees;
649
	}
650
651
	// MISC //
652
653
	/**
654
	 * Returns the total amount to collect for this submission.
655
	 *
656
	 * @since 1.0.19
657
	 */
658
	public function get_total() {
659
		$total = $this->subtotal_amount + $this->get_total_fees() - $this->get_total_discount() + $this->get_total_tax();
660
		$total = apply_filters( 'getpaid_get_submission_total_amount', $total, $this  );
661
		return wpinv_format_amount( wpinv_sanitize_amount( $total ) );
662
	}
663
664
	/**
665
	 * Returns the billing email of the user.
666
	 *
667
	 * @since 1.0.19
668
	 */
669
	public function get_billing_email() {
670
		$billing_email = empty( $this->data['billing_email'] ) ? '' : $this->data['billing_email'];
671
		return apply_filters( 'getpaid_get_submission_billing_email', $billing_email, $this  );
672
	}
673
674
	/**
675
	 * Checks if the submitter has a billing email.
676
	 *
677
	 * @since 1.0.19
678
	 */
679
	public function has_billing_email() {
680
		$billing_email = $this->get_billing_email();
681
		return ! empty( $billing_email );
682
	}
683
684
}
685