Completed
Branch FET-Wait-List (1a4c1b)
by
unknown
164:09 queued 153:47
created

EE_Transaction::tax_total()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 0
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
use EventEspresso\core\exceptions\InvalidDataTypeException;
4
use EventEspresso\core\exceptions\InvalidInterfaceException;
5
6
defined('EVENT_ESPRESSO_VERSION') || exit('No direct script access allowed');
7
8
9
10
/**
11
 * EE_Transaction class
12
 *
13
 * @package       Event Espresso
14
 * @subpackage    includes/classes/EE_Transaction.class.php
15
 * @author        Brent Christensen
16
 */
17
class EE_Transaction extends EE_Base_Class implements EEI_Transaction
18
{
19
20
    /**
21
     * The length of time in seconds that a lock is applied before being considered expired.
22
     * It is not long because a transaction should only be locked for the duration of the request that locked it
23
     */
24
    const LOCK_EXPIRATION = 2;
25
26
    /**
27
     * txn status upon initial construction.
28
     *
29
     * @var string
30
     */
31
    protected $_old_txn_status;
32
33
34
35
    /**
36
     * @param array  $props_n_values          incoming values
37
     * @param string $timezone                incoming timezone
38
     *                                        (if not set the timezone set for the website will be used.)
39
     * @param array  $date_formats            incoming date_formats in an array where the first value is the
40
     *                                        date_format and the second value is the time format
41
     * @return EE_Transaction
42
     * @throws EE_Error
43
     */
44
    public static function new_instance($props_n_values = array(), $timezone = null, $date_formats = array())
45
    {
46
        $has_object = parent::_check_for_object($props_n_values, __CLASS__, $timezone, $date_formats);
47
        $txn        = $has_object
48
            ? $has_object
49
            : new self($props_n_values, false, $timezone, $date_formats);
50
        if (! $has_object) {
51
            $txn->set_old_txn_status($txn->status_ID());
52
        }
53
        return $txn;
54
    }
55
56
57
58
    /**
59
     * @param array  $props_n_values  incoming values from the database
60
     * @param string $timezone        incoming timezone as set by the model.  If not set the timezone for
61
     *                                the website will be used.
62
     * @return EE_Transaction
63
     * @throws EE_Error
64
     */
65
    public static function new_instance_from_db($props_n_values = array(), $timezone = null)
66
    {
67
        $txn = new self($props_n_values, true, $timezone);
68
        $txn->set_old_txn_status($txn->status_ID());
69
        return $txn;
70
    }
71
72
73
74
    /**
75
     * Sets a meta field indicating that this TXN is locked and should not be updated in the db.
76
     * If a lock has already been set, then we will attempt to remove it in case it has expired.
77
     * If that also fails, then an exception is thrown.
78
     *
79
     * @throws EE_Error
80
     */
81
    public function lock()
82
    {
83
        // attempt to set lock, but if that fails...
84
        if (! $this->add_extra_meta('lock', time(), true)) {
85
            // then attempt to remove the lock in case it is expired
86
            if ($this->_remove_expired_lock()) {
87
                // if removal was successful, then try setting lock again
88
                $this->lock();
89
            } else {
90
                // but if the lock can not be removed, then throw an exception
91
                throw new EE_Error(
92
                    sprintf(
93
                        __(
94
                            'Could not lock Transaction %1$d because it is already locked, meaning another part of the system is currently editing it. It should already be unlocked by the time you read this, so please refresh the page and try again.',
95
                            'event_espresso'
96
                        ),
97
                        $this->ID()
98
                    )
99
                );
100
            }
101
        }
102
    }
103
104
105
106
    /**
107
     * removes transaction lock applied in EE_Transaction::lock()
108
     *
109
     * @return int
110
     * @throws EE_Error
111
     */
112
    public function unlock()
113
    {
114
        return $this->delete_extra_meta('lock');
115
    }
116
117
118
119
    /**
120
     * Decides whether or not now is the right time to update the transaction.
121
     * This is useful because we don't always know if it is safe to update the transaction
122
     * and its related data. why?
123
     * because it's possible that the transaction is being used in another
124
     * request and could overwrite anything we save.
125
     * So we want to only update the txn once we know that won't happen.
126
     * We also check that the lock isn't expired, and remove it if it is
127
     *
128
     * @return boolean
129
     * @throws EE_Error
130
     */
131
    public function is_locked()
132
    {
133
        // if TXN is not locked, then return false immediately
134
        if (! $this->_get_lock()) {
135
            return false;
136
        }
137
        // if not, then let's try and remove the lock in case it's expired...
138
        // _remove_expired_lock() returns 0 when lock is valid (ie: removed = false)
139
        // and a positive number if the lock was removed (ie: number of locks deleted),
140
        // so we need to return the opposite
141
        return ! $this->_remove_expired_lock() ? true : false;
142
    }
143
144
145
146
    /**
147
     * Gets the meta field indicating that this TXN is locked
148
     *
149
     * @return int
150
     * @throws EE_Error
151
     */
152
    protected function _get_lock()
153
    {
154
        return (int)$this->get_extra_meta('lock', true, 0);
155
    }
156
157
158
159
    /**
160
     * If the lock on this transaction is expired, then we want to remove it so that the transaction can be updated
161
     *
162
     * @return int
163
     * @throws EE_Error
164
     */
165
    protected function _remove_expired_lock()
166
    {
167
        $locked = $this->_get_lock();
168
        if ($locked && time() - EE_Transaction::LOCK_EXPIRATION > $locked) {
169
            return $this->unlock();
170
        }
171
        return 0;
172
    }
173
174
175
176
    /**
177
     * Set transaction total
178
     *
179
     * @param float $total total value of transaction
180
     * @throws EE_Error
181
     */
182
    public function set_total($total = 0.00)
183
    {
184
        $this->set('TXN_total', (float)$total);
185
    }
186
187
188
189
    /**
190
     * Set Total Amount Paid to Date
191
     *
192
     * @param float $total_paid total amount paid to date (sum of all payments)
193
     * @throws EE_Error
194
     */
195
    public function set_paid($total_paid = 0.00)
196
    {
197
        $this->set('TXN_paid', (float)$total_paid);
198
    }
199
200
201
202
    /**
203
     * Set transaction status
204
     *
205
     * @param string $status        whether the transaction is open, declined, accepted,
206
     *                              or any number of custom values that can be set
207
     * @throws EE_Error
208
     */
209
    public function set_status($status = '')
210
    {
211
        $this->set('STS_ID', $status);
212
    }
213
214
215
216
    /**
217
     * Set hash salt
218
     *
219
     * @param string $hash_salt required for some payment gateways
220
     * @throws EE_Error
221
     */
222
    public function set_hash_salt($hash_salt = '')
223
    {
224
        $this->set('TXN_hash_salt', $hash_salt);
225
    }
226
227
228
229
    /**
230
     * Sets TXN_reg_steps array
231
     *
232
     * @param array $txn_reg_steps
233
     * @throws EE_Error
234
     */
235
    public function set_reg_steps(array $txn_reg_steps)
236
    {
237
        $this->set('TXN_reg_steps', $txn_reg_steps);
238
    }
239
240
241
242
    /**
243
     * Gets TXN_reg_steps
244
     *
245
     * @return array
246
     * @throws EE_Error
247
     */
248
    public function reg_steps()
249
    {
250
        $TXN_reg_steps = $this->get('TXN_reg_steps');
251
        return is_array($TXN_reg_steps) ? (array)$TXN_reg_steps : array();
252
    }
253
254
255
256
    /**
257
     * @return string of transaction's total cost, with currency symbol and decimal
258
     * @throws EE_Error
259
     */
260
    public function pretty_total()
261
    {
262
        return $this->get_pretty('TXN_total');
263
    }
264
265
266
267
    /**
268
     * Gets the amount paid in a pretty string (formatted and with currency symbol)
269
     *
270
     * @return string
271
     * @throws EE_Error
272
     */
273
    public function pretty_paid()
274
    {
275
        return $this->get_pretty('TXN_paid');
276
    }
277
278
279
280
    /**
281
     * calculate the amount remaining for this transaction and return;
282
     *
283
     * @return float amount remaining
284
     * @throws EE_Error
285
     */
286
    public function remaining()
287
    {
288
        return $this->total() - $this->paid();
289
    }
290
291
292
293
    /**
294
     * get Transaction Total
295
     *
296
     * @return float
297
     * @throws EE_Error
298
     */
299
    public function total()
300
    {
301
        return (float)$this->get('TXN_total');
302
    }
303
304
305
306
    /**
307
     * get Total Amount Paid to Date
308
     *
309
     * @return float
310
     * @throws EE_Error
311
     */
312
    public function paid()
313
    {
314
        return (float)$this->get('TXN_paid');
315
    }
316
317
318
319
    /**
320
     * @throws EE_Error
321
     */
322
    public function get_cart_session()
323
    {
324
        $session_data = (array)$this->get('TXN_session_data');
325
        return isset($session_data['cart']) && $session_data['cart'] instanceof EE_Cart
326
            ? $session_data['cart']
327
            : null;
328
    }
329
330
331
332
    /**
333
     * get Transaction session data
334
     *
335
     * @throws EE_Error
336
     */
337
    public function session_data()
338
    {
339
        $session_data = $this->get('TXN_session_data');
340
        if (empty($session_data)) {
341
            $session_data = array(
342
                'id'            => null,
343
                'user_id'       => null,
344
                'ip_address'    => null,
345
                'user_agent'    => null,
346
                'init_access'   => null,
347
                'last_access'   => null,
348
                'pages_visited' => array(),
349
            );
350
        }
351
        return $session_data;
352
    }
353
354
355
356
    /**
357
     * Set session data within the TXN object
358
     *
359
     * @param EE_Session|array $session_data
360
     * @throws EE_Error
361
     */
362
    public function set_txn_session_data($session_data)
363
    {
364
        if ($session_data instanceof EE_Session) {
365
            $this->set('TXN_session_data', $session_data->get_session_data(null, true));
366
        } else {
367
            $this->set('TXN_session_data', $session_data);
368
        }
369
    }
370
371
372
373
    /**
374
     * get Transaction hash salt
375
     *
376
     * @throws EE_Error
377
     */
378
    public function hash_salt_()
379
    {
380
        return $this->get('TXN_hash_salt');
381
    }
382
383
384
385
    /**
386
     * Returns the transaction datetime as either:
387
     *            - unix timestamp format ($format = false, $gmt = true)
388
     *            - formatted date string including the UTC (timezone) offset ($format = true ($gmt
389
     *              has no affect with this option)), this also may include a timezone abbreviation if the
390
     *              set timezone in this class differs from what the timezone is on the blog.
391
     *            - formatted date string including the UTC (timezone) offset (default).
392
     *
393
     * @param boolean $format - whether to return a unix timestamp (default) or formatted date string
394
     * @param boolean $gmt    - whether to return a unix timestamp with UTC offset applied (default)
395
     *                          or no UTC offset applied
396
     * @return string | int
397
     * @throws EE_Error
398
     */
399
    public function datetime($format = false, $gmt = false)
400
    {
401
        if ($format) {
402
            return $this->get_pretty('TXN_timestamp');
403
        }
404
        if ($gmt) {
405
            return $this->get_raw('TXN_timestamp');
406
        }
407
        return $this->get('TXN_timestamp');
408
    }
409
410
411
412
    /**
413
     * Gets registrations on this transaction
414
     *
415
     * @param array   $query_params array of query parameters
416
     * @param boolean $get_cached   TRUE to retrieve cached registrations or FALSE to pull from the db
417
     * @return EE_Base_Class[]|EE_Registration[]
418
     * @throws EE_Error
419
     */
420
    public function registrations($query_params = array(), $get_cached = false)
421
    {
422
        $query_params = (empty($query_params) || ! is_array($query_params))
423
            ? array(
424
                'order_by' => array(
425
                    'Event.EVT_name'     => 'ASC',
426
                    'Attendee.ATT_lname' => 'ASC',
427
                    'Attendee.ATT_fname' => 'ASC',
428
                ),
429
            )
430
            : $query_params;
431
        $query_params = $get_cached ? array() : $query_params;
432
        return $this->get_many_related('Registration', $query_params);
433
    }
434
435
436
437
    /**
438
     * Gets all the attendees for this transaction (handy for use with EE_Attendee's get_registrations_for_event
439
     * function for getting attendees and how many registrations they each have for an event)
440
     *
441
     * @return mixed EE_Attendee[] by default, int if $output is set to 'COUNT'
442
     * @throws EE_Error
443
     */
444
    public function attendees()
445
    {
446
        return $this->get_many_related('Attendee', array(array('Registration.Transaction.TXN_ID' => $this->ID())));
447
    }
448
449
450
451
    /**
452
     * Gets payments for this transaction. Unlike other such functions, order by 'DESC' by default
453
     *
454
     * @param array $query_params like EEM_Base::get_all
455
     * @return EE_Base_Class[]|EE_Payment[]
456
     * @throws EE_Error
457
     */
458
    public function payments($query_params = array())
459
    {
460
        return $this->get_many_related('Payment', $query_params);
461
    }
462
463
464
465
    /**
466
     * gets only approved payments for this transaction
467
     *
468
     * @return EE_Base_Class[]|EE_Payment[]
469
     * @throws EE_Error
470
     * @throws InvalidArgumentException
471
     * @throws ReflectionException
472
     * @throws InvalidDataTypeException
473
     * @throws InvalidInterfaceException
474
     */
475
    public function approved_payments()
476
    {
477
        EE_Registry::instance()->load_model('Payment');
478
        return $this->get_many_related(
479
            'Payment',
480
            array(
481
                array('STS_ID' => EEM_Payment::status_id_approved),
482
                'order_by' => array('PAY_timestamp' => 'DESC'),
483
            )
484
        );
485
    }
486
487
488
489
    /**
490
     * Gets all payments which have not been approved
491
     *
492
     * @return EE_Base_Class[]|EEI_Payment[]
493
     * @throws EE_Error if a model is misconfigured somehow
494
     */
495
    public function pending_payments()
496
    {
497
        return $this->get_many_related(
498
            'Payment',
499
            array(
500
                array(
501
                    'STS_ID' => EEM_Payment::status_id_pending,
502
                ),
503
                'order_by' => array(
504
                    'PAY_timestamp' => 'DESC',
505
                ),
506
            )
507
        );
508
    }
509
510
511
512
    /**
513
     * echoes $this->pretty_status()
514
     *
515
     * @param bool $show_icons
516
     * @throws EE_Error
517
     * @throws InvalidArgumentException
518
     * @throws InvalidDataTypeException
519
     * @throws InvalidInterfaceException
520
     */
521
    public function e_pretty_status($show_icons = false)
522
    {
523
        echo $this->pretty_status($show_icons);
524
    }
525
526
527
528
    /**
529
     * returns a pretty version of the status, good for displaying to users
530
     *
531
     * @param bool $show_icons
532
     * @return string
533
     * @throws EE_Error
534
     * @throws InvalidArgumentException
535
     * @throws InvalidDataTypeException
536
     * @throws InvalidInterfaceException
537
     */
538
    public function pretty_status($show_icons = false)
539
    {
540
        $status = EEM_Status::instance()->localized_status(
541
            array($this->status_ID() => __('unknown', 'event_espresso')),
542
            false,
543
            'sentence'
544
        );
545
        $icon   = '';
546
        switch ($this->status_ID()) {
547
            case EEM_Transaction::complete_status_code:
548
                $icon = $show_icons ? '<span class="dashicons dashicons-yes ee-icon-size-24 green-text"></span>' : '';
549
                break;
550
            case EEM_Transaction::incomplete_status_code:
551
                $icon = $show_icons ? '<span class="dashicons dashicons-marker ee-icon-size-16 lt-blue-text"></span>'
552
                    : '';
553
                break;
554
            case EEM_Transaction::abandoned_status_code:
555
                $icon = $show_icons ? '<span class="dashicons dashicons-marker ee-icon-size-16 red-text"></span>' : '';
556
                break;
557
            case EEM_Transaction::failed_status_code:
558
                $icon = $show_icons ? '<span class="dashicons dashicons-no ee-icon-size-16 red-text"></span>' : '';
559
                break;
560
            case EEM_Transaction::overpaid_status_code:
561
                $icon = $show_icons ? '<span class="dashicons dashicons-plus ee-icon-size-16 orange-text"></span>' : '';
562
                break;
563
        }
564
        return $icon . $status[$this->status_ID()];
565
    }
566
567
568
569
    /**
570
     * get Transaction Status
571
     *
572
     * @throws EE_Error
573
     */
574
    public function status_ID()
575
    {
576
        return $this->get('STS_ID');
577
    }
578
579
580
581
    /**
582
     * Returns TRUE or FALSE for whether or not this transaction cost any money
583
     *
584
     * @return boolean
585
     * @throws EE_Error
586
     */
587
    public function is_free()
588
    {
589
        return EEH_Money::compare_floats($this->get('TXN_total'), 0, '==');
590
    }
591
592
593
594
    /**
595
     * Returns whether this transaction is complete
596
     * Useful in templates and other logic for deciding if we should ask for another payment...
597
     *
598
     * @return boolean
599
     * @throws EE_Error
600
     */
601
    public function is_completed()
602
    {
603
        return $this->status_ID() === EEM_Transaction::complete_status_code;
604
    }
605
606
607
608
    /**
609
     * Returns whether this transaction is incomplete
610
     * Useful in templates and other logic for deciding if we should ask for another payment...
611
     *
612
     * @return boolean
613
     * @throws EE_Error
614
     */
615
    public function is_incomplete()
616
    {
617
        return $this->status_ID() === EEM_Transaction::incomplete_status_code;
618
    }
619
620
621
622
    /**
623
     * Returns whether this transaction is overpaid
624
     * Useful in templates and other logic for deciding if monies need to be refunded
625
     *
626
     * @return boolean
627
     * @throws EE_Error
628
     */
629
    public function is_overpaid()
630
    {
631
        return $this->status_ID() === EEM_Transaction::overpaid_status_code;
632
    }
633
634
635
636
    /**
637
     * Returns whether this transaction was abandoned
638
     * meaning that the transaction/registration process was somehow interrupted and never completed
639
     * but that contact information exists for at least one registrant
640
     *
641
     * @return boolean
642
     * @throws EE_Error
643
     */
644
    public function is_abandoned()
645
    {
646
        return $this->status_ID() === EEM_Transaction::abandoned_status_code;
647
    }
648
649
650
651
    /**
652
     * Returns whether this transaction failed
653
     * meaning that the transaction/registration process was somehow interrupted and never completed
654
     * and that NO contact information exists for any registrants
655
     *
656
     * @return boolean
657
     * @throws EE_Error
658
     */
659
    public function failed()
660
    {
661
        return $this->status_ID() === EEM_Transaction::failed_status_code;
662
    }
663
664
665
666
    /**
667
     * This returns the url for the invoice of this transaction
668
     *
669
     * @param string $type 'html' or 'pdf' (default is pdf)
670
     * @return string
671
     * @throws EE_Error
672
     */
673
    public function invoice_url($type = 'html')
674
    {
675
        $REG = $this->primary_registration();
676
        if (! $REG instanceof EE_Registration) {
677
            return '';
678
        }
679
        return $REG->invoice_url($type);
680
    }
681
682
683
684
    /**
685
     * Gets the primary registration only
686
     *
687
     * @return EE_Base_Class|EE_Registration
688
     * @throws EE_Error
689
     */
690
    public function primary_registration()
691
    {
692
        $registrations = (array)$this->get_many_related(
693
            'Registration',
694
            array(array('REG_count' => EEM_Registration::PRIMARY_REGISTRANT_COUNT))
695
        );
696
        foreach ($registrations as $registration) {
697
            // valid registration that is NOT cancelled or declined ?
698
            if (
699
                $registration instanceof EE_Registration
700
                && ! in_array($registration->status_ID(), EEM_Registration::closed_reg_statuses(), true)
701
            ) {
702
                return $registration;
703
            }
704
        }
705
        // nothing valid found, so just return first thing from array of results
706
        return reset($registrations);
707
    }
708
709
710
711
    /**
712
     * Gets the URL for viewing the receipt
713
     *
714
     * @param string $type 'pdf' or 'html' (default is 'html')
715
     * @return string
716
     * @throws EE_Error
717
     */
718
    public function receipt_url($type = 'html')
719
    {
720
        $REG = $this->primary_registration();
721
        if (! $REG instanceof EE_Registration) {
722
            return '';
723
        }
724
        return $REG->receipt_url($type);
725
    }
726
727
728
729
    /**
730
     * Gets the URL of the thank you page with this registration REG_url_link added as
731
     * a query parameter
732
     *
733
     * @return string
734
     * @throws EE_Error
735
     */
736
    public function payment_overview_url()
737
    {
738
        $primary_registration = $this->primary_registration();
739
        return $primary_registration instanceof EE_Registration ? $primary_registration->payment_overview_url() : false;
740
    }
741
742
743
744
    /**
745
     * @return string
746
     * @throws EE_Error
747
     */
748
    public function gateway_response_on_transaction()
749
    {
750
        $payment = $this->get_first_related('Payment');
751
        return $payment instanceof EE_Payment ? $payment->gateway_response() : '';
752
    }
753
754
755
756
    /**
757
     * Get the status object of this object
758
     *
759
     * @return EE_Base_Class|EE_Status
760
     * @throws EE_Error
761
     */
762
    public function status_obj()
763
    {
764
        return $this->get_first_related('Status');
765
    }
766
767
768
769
    /**
770
     * Gets all the extra meta info on this payment
771
     *
772
     * @param array $query_params like EEM_Base::get_all
773
     * @return EE_Base_Class[]|EE_Extra_Meta
774
     * @throws EE_Error
775
     */
776
    public function extra_meta($query_params = array())
777
    {
778
        return $this->get_many_related('Extra_Meta', $query_params);
779
    }
780
781
782
783
    /**
784
     * Wrapper for _add_relation_to
785
     *
786
     * @param EE_Registration $registration
787
     * @return EE_Base_Class the relation was added to
788
     * @throws EE_Error
789
     */
790
    public function add_registration(EE_Registration $registration)
791
    {
792
        return $this->_add_relation_to($registration, 'Registration');
793
    }
794
795
796
797
    /**
798
     * Removes the given registration from being related (even before saving this transaction).
799
     * If an ID/index is provided and this transaction isn't saved yet, removes it from list of cached relations
800
     *
801
     * @param int $registration_or_id
802
     * @return EE_Base_Class that was removed from being related
803
     * @throws EE_Error
804
     */
805
    public function remove_registration_with_id($registration_or_id)
806
    {
807
        return $this->_remove_relation_to($registration_or_id, 'Registration');
808
    }
809
810
811
812
    /**
813
     * Gets all the line items which are for ACTUAL items
814
     *
815
     * @return EE_Line_Item[]
816
     * @throws EE_Error
817
     */
818
    public function items_purchased()
819
    {
820
        return $this->line_items(array(array('LIN_type' => EEM_Line_Item::type_line_item)));
821
    }
822
823
824
825
    /**
826
     * Wrapper for _add_relation_to
827
     *
828
     * @param EE_Line_Item $line_item
829
     * @return EE_Base_Class the relation was added to
830
     * @throws EE_Error
831
     */
832
    public function add_line_item(EE_Line_Item $line_item)
833
    {
834
        return $this->_add_relation_to($line_item, 'Line_Item');
835
    }
836
837
838
839
    /**
840
     * Gets ALL the line items related to this transaction (unstructured)
841
     *
842
     * @param array $query_params
843
     * @return EE_Base_Class[]|EE_Line_Item[]
844
     * @throws EE_Error
845
     */
846
    public function line_items($query_params = array())
847
    {
848
        return $this->get_many_related('Line_Item', $query_params);
849
    }
850
851
852
853
    /**
854
     * Gets all the line items which are taxes on the total
855
     *
856
     * @return EE_Line_Item[]
857
     * @throws EE_Error
858
     */
859
    public function tax_items()
860
    {
861
        return $this->line_items(array(array('LIN_type' => EEM_Line_Item::type_tax)));
862
    }
863
864
865
866
    /**
867
     * Gets the total line item (which is a parent of all other related line items,
868
     * meaning it takes them all into account on its total)
869
     *
870
     * @param bool $create_if_not_found
871
     * @return \EE_Line_Item
872
     * @throws EE_Error
873
     */
874
    public function total_line_item($create_if_not_found = true)
875
    {
876
        $item = $this->get_first_related('Line_Item', array(array('LIN_type' => EEM_Line_Item::type_total)));
877
        if (! $item && $create_if_not_found) {
878
            $item = EEH_Line_Item::create_total_line_item($this);
879
        }
880
        return $item;
881
    }
882
883
884
885
    /**
886
     * Returns the total amount of tax on this transaction
887
     * (assumes there's only one tax subtotal line item)
888
     *
889
     * @return float
890
     * @throws EE_Error
891
     */
892
    public function tax_total()
893
    {
894
        $tax_line_item = $this->tax_total_line_item();
895
        if ($tax_line_item) {
896
            return (float)$tax_line_item->total();
897
        }
898
        return (float)0;
899
    }
900
901
902
903
    /**
904
     * Gets the tax subtotal line item (assumes there's only one)
905
     *
906
     * @return EE_Line_Item
907
     * @throws EE_Error
908
     */
909
    public function tax_total_line_item()
910
    {
911
        return EEH_Line_Item::get_taxes_subtotal($this->total_line_item());
912
    }
913
914
915
916
    /**
917
     * Gets the array of billing info for the gateway and for this transaction's primary registration's attendee.
918
     *
919
     * @return EE_Form_Section_Proper
920
     * @throws EE_Error
921
     */
922
    public function billing_info()
923
    {
924
        $payment_method = $this->payment_method();
925
        if (! $payment_method) {
926
            EE_Error::add_error(
927
                __(
928
                    'Could not find billing info for transaction because no gateway has been used for it yet',
929
                    'event_espresso'
930
                ),
931
                __FILE__,
932
                __FUNCTION__,
933
                __LINE__
934
            );
935
            return null;
936
        }
937
        $primary_reg = $this->primary_registration();
938
        if (! $primary_reg) {
939
            EE_Error::add_error(
940
                __(
941
                    'Cannot get billing info for gateway %s on transaction because no primary registration exists',
942
                    'event_espresso'
943
                ),
944
                __FILE__,
945
                __FUNCTION__,
946
                __LINE__
947
            );
948
            return null;
949
        }
950
        $attendee = $primary_reg->attendee();
951
        if (! $attendee) {
952
            EE_Error::add_error(
953
                __(
954
                    'Cannot get billing info for gateway %s on transaction because the primary registration has no attendee exists',
955
                    'event_espresso'
956
                ),
957
                __FILE__,
958
                __FUNCTION__,
959
                __LINE__
960
            );
961
            return null;
962
        }
963
        return $attendee->billing_info_for_payment_method($payment_method);
964
    }
965
966
967
968
    /**
969
     * Gets PMD_ID
970
     *
971
     * @return int
972
     * @throws EE_Error
973
     */
974
    public function payment_method_ID()
975
    {
976
        return $this->get('PMD_ID');
977
    }
978
979
980
981
    /**
982
     * Sets PMD_ID
983
     *
984
     * @param int $PMD_ID
985
     * @throws EE_Error
986
     */
987
    public function set_payment_method_ID($PMD_ID)
988
    {
989
        $this->set('PMD_ID', $PMD_ID);
990
    }
991
992
993
994
    /**
995
     * Gets the last-used payment method on this transaction
996
     * (we COULD just use the last-made payment, but some payment methods, namely
997
     * offline ones, dont' create payments)
998
     *
999
     * @return EE_Payment_Method
1000
     * @throws EE_Error
1001
     */
1002
    public function payment_method()
1003
    {
1004
        $pm = $this->get_first_related('Payment_Method');
1005
        if ($pm instanceof EE_Payment_Method) {
1006
            return $pm;
1007
        }
1008
        $last_payment = $this->last_payment();
1009
        if ($last_payment instanceof EE_Payment && $last_payment->payment_method()) {
1010
            return $last_payment->payment_method();
1011
        }
1012
        return null;
1013
    }
1014
1015
1016
1017
    /**
1018
     * Gets the last payment made
1019
     *
1020
     * @return EE_Base_Class|EE_Payment
1021
     * @throws EE_Error
1022
     */
1023
    public function last_payment()
1024
    {
1025
        return $this->get_first_related('Payment', array('order_by' => array('PAY_ID' => 'desc')));
1026
    }
1027
1028
1029
1030
    /**
1031
     * Gets all the line items which are unrelated to tickets on this transaction
1032
     *
1033
     * @return EE_Line_Item[]
1034
     * @throws EE_Error
1035
     * @throws InvalidArgumentException
1036
     * @throws InvalidDataTypeException
1037
     * @throws InvalidInterfaceException
1038
     */
1039
    public function non_ticket_line_items()
1040
    {
1041
        return EEM_Line_Item::instance()->get_all_non_ticket_line_items_for_transaction($this->ID());
1042
    }
1043
1044
1045
1046
    /**
1047
     * possibly toggles TXN status
1048
     *
1049
     * @param  boolean $update whether to save the TXN
1050
     * @return bool whether the TXN was saved
1051
     * @throws EE_Error
1052
     * @throws RuntimeException
1053
     */
1054
    public function update_status_based_on_total_paid($update = true)
1055
    {
1056
        // set transaction status based on comparison of TXN_paid vs TXN_total
1057
        if (EEH_Money::compare_floats($this->paid(), $this->total(), '>')) {
1058
            $new_txn_status = EEM_Transaction::overpaid_status_code;
1059
        } elseif (EEH_Money::compare_floats($this->paid(), $this->total())) {
1060
            $new_txn_status = EEM_Transaction::complete_status_code;
1061
        } elseif (EEH_Money::compare_floats($this->paid(), $this->total(), '<')) {
1062
            $new_txn_status = EEM_Transaction::incomplete_status_code;
1063
        } else {
1064
            throw new RuntimeException(
1065
                __('The total paid calculation for this transaction is inaccurate.', 'event_espresso')
1066
            );
1067
        }
1068
        if ($new_txn_status !== $this->status_ID()) {
1069
            $this->set_status($new_txn_status);
1070
            if ($update) {
1071
                return $this->save() ? true : false;
1072
            }
1073
        }
1074
        return false;
1075
    }
1076
1077
1078
1079
    /**
1080
     * Updates the transaction's status and total_paid based on all the payments
1081
     * that apply to it
1082
     *
1083
     * @deprecated
1084
     * @return array|bool
1085
     * @throws EE_Error
1086
     * @throws InvalidArgumentException
1087
     * @throws ReflectionException
1088
     * @throws InvalidDataTypeException
1089
     * @throws InvalidInterfaceException
1090
     */
1091 View Code Duplication
    public function update_based_on_payments()
1092
    {
1093
        EE_Error::doing_it_wrong(
1094
            __CLASS__ . '::' . __FUNCTION__,
1095
            sprintf(
1096
                __('This method is deprecated. Please use "%s" instead', 'event_espresso'),
1097
                'EE_Transaction_Processor::update_transaction_and_registrations_after_checkout_or_payment()'
1098
            ),
1099
            '4.6.0'
1100
        );
1101
        /** @type EE_Transaction_Processor $transaction_processor */
1102
        $transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
1103
        return $transaction_processor->update_transaction_and_registrations_after_checkout_or_payment($this);
1104
    }
1105
1106
1107
1108
    /**
1109
     * @return string
1110
     */
1111
    public function old_txn_status()
1112
    {
1113
        return $this->_old_txn_status;
1114
    }
1115
1116
1117
1118
    /**
1119
     * @param string $old_txn_status
1120
     */
1121
    public function set_old_txn_status($old_txn_status)
1122
    {
1123
        // only set the first time
1124
        if ($this->_old_txn_status === null) {
1125
            $this->_old_txn_status = $old_txn_status;
1126
        }
1127
    }
1128
1129
1130
1131
    /**
1132
     * reg_status_updated
1133
     *
1134
     * @return bool
1135
     * @throws EE_Error
1136
     */
1137
    public function txn_status_updated()
1138
    {
1139
        return $this->status_ID() !== $this->_old_txn_status && $this->_old_txn_status !== null;
1140
    }
1141
1142
1143
1144
    /**
1145
     * _reg_steps_completed
1146
     * if $check_all is TRUE, then returns TRUE if ALL reg steps have been marked as completed,
1147
     * if a $reg_step_slug is provided, then this step will be skipped when testing for completion
1148
     * if $check_all is FALSE and a $reg_step_slug is provided, then ONLY that reg step will be tested for completion
1149
     *
1150
     * @param string $reg_step_slug
1151
     * @param bool   $check_all
1152
     * @return bool|int
1153
     * @throws EE_Error
1154
     */
1155
    private function _reg_steps_completed($reg_step_slug = '', $check_all = true)
1156
    {
1157
        $reg_steps = $this->reg_steps();
1158
        if (! is_array($reg_steps) || empty($reg_steps)) {
1159
            return false;
1160
        }
1161
        // loop thru reg steps array)
1162
        foreach ($reg_steps as $slug => $reg_step_completed) {
1163
            // if NOT checking ALL steps (only checking one step)
1164
            if (! $check_all) {
1165
                // and this is the one
1166
                if ($slug === $reg_step_slug) {
1167
                    return $reg_step_completed;
1168
                }
1169
                // skip to next reg step in loop
1170
                continue;
1171
            }
1172
            // $check_all must be true, else we would never have gotten to this point
1173
            if ($slug === $reg_step_slug) {
1174
                // if we reach this point, then we are testing either:
1175
                // all_reg_steps_completed_except() or
1176
                // all_reg_steps_completed_except_final_step(),
1177
                // and since this is the reg step EXCEPTION being tested
1178
                // we want to return true (yes true) if this reg step is NOT completed
1179
                // ie: "is everything completed except the final step?"
1180
                // "that is correct... the final step is not completed, but all others are."
1181
                return $reg_step_completed !== true;
1182
            }
1183
            if ($reg_step_completed !== true) {
1184
                // if any reg step is NOT completed, then ALL steps are not completed
1185
                return false;
1186
            }
1187
        }
1188
        return true;
1189
    }
1190
1191
1192
1193
    /**
1194
     * all_reg_steps_completed
1195
     * returns:
1196
     *    true if ALL reg steps have been marked as completed
1197
     *        or false if any step is not completed
1198
     *
1199
     * @return bool
1200
     * @throws EE_Error
1201
     */
1202
    public function all_reg_steps_completed()
1203
    {
1204
        return $this->_reg_steps_completed();
1205
    }
1206
1207
1208
1209
    /**
1210
     * all_reg_steps_completed_except
1211
     * returns:
1212
     *        true if ALL reg steps, except a particular step that you wish to skip over, have been marked as completed
1213
     *        or false if any other step is not completed
1214
     *        or false if ALL steps are completed including the exception you are testing !!!
1215
     *
1216
     * @param string $exception
1217
     * @return bool
1218
     * @throws EE_Error
1219
     */
1220
    public function all_reg_steps_completed_except($exception = '')
1221
    {
1222
        return $this->_reg_steps_completed($exception);
1223
    }
1224
1225
1226
1227
    /**
1228
     * all_reg_steps_completed_except
1229
     * returns:
1230
     *        true if ALL reg steps, except the final step, have been marked as completed
1231
     *        or false if any step is not completed
1232
     *    or false if ALL steps are completed including the final step !!!
1233
     *
1234
     * @return bool
1235
     * @throws EE_Error
1236
     */
1237
    public function all_reg_steps_completed_except_final_step()
1238
    {
1239
        return $this->_reg_steps_completed('finalize_registration');
1240
    }
1241
1242
1243
1244
    /**
1245
     * reg_step_completed
1246
     * returns:
1247
     *    true if a specific reg step has been marked as completed
1248
     *    a Unix timestamp if it has been initialized but not yet completed,
1249
     *    or false if it has not yet been initialized
1250
     *
1251
     * @param string $reg_step_slug
1252
     * @return bool|int
1253
     * @throws EE_Error
1254
     */
1255
    public function reg_step_completed($reg_step_slug)
1256
    {
1257
        return $this->_reg_steps_completed($reg_step_slug, false);
1258
    }
1259
1260
1261
1262
    /**
1263
     * completed_final_reg_step
1264
     * returns:
1265
     *    true if the finalize_registration reg step has been marked as completed
1266
     *    a Unix timestamp if it has been initialized but not yet completed,
1267
     *    or false if it has not yet been initialized
1268
     *
1269
     * @return bool|int
1270
     * @throws EE_Error
1271
     */
1272
    public function final_reg_step_completed()
1273
    {
1274
        return $this->_reg_steps_completed('finalize_registration', false);
1275
    }
1276
1277
1278
1279
    /**
1280
     * set_reg_step_initiated
1281
     * given a valid TXN_reg_step, this sets it's value to a unix timestamp
1282
     *
1283
     * @param string $reg_step_slug
1284
     * @return boolean
1285
     * @throws EE_Error
1286
     */
1287
    public function set_reg_step_initiated($reg_step_slug)
1288
    {
1289
        return $this->_set_reg_step_completed_status($reg_step_slug, time());
1290
    }
1291
1292
1293
1294
    /**
1295
     * set_reg_step_completed
1296
     * given a valid TXN_reg_step, this sets the step as completed
1297
     *
1298
     * @param string $reg_step_slug
1299
     * @return boolean
1300
     * @throws EE_Error
1301
     */
1302
    public function set_reg_step_completed($reg_step_slug)
1303
    {
1304
        return $this->_set_reg_step_completed_status($reg_step_slug, true);
1305
    }
1306
1307
1308
1309
    /**
1310
     * set_reg_step_completed
1311
     * given a valid TXN_reg_step slug, this sets the step as NOT completed
1312
     *
1313
     * @param string $reg_step_slug
1314
     * @return boolean
1315
     * @throws EE_Error
1316
     */
1317
    public function set_reg_step_not_completed($reg_step_slug)
1318
    {
1319
        return $this->_set_reg_step_completed_status($reg_step_slug, false);
1320
    }
1321
1322
1323
1324
    /**
1325
     * set_reg_step_completed
1326
     * given a valid reg step slug, this sets the TXN_reg_step completed status which is either:
1327
     *
1328
     * @param  string      $reg_step_slug
1329
     * @param  boolean|int $status
1330
     * @return boolean
1331
     * @throws EE_Error
1332
     */
1333
    private function _set_reg_step_completed_status($reg_step_slug, $status)
1334
    {
1335
        // validate status
1336
        $status = is_bool($status) || is_int($status) ? $status : false;
1337
        // get reg steps array
1338
        $txn_reg_steps = $this->reg_steps();
1339
        // if reg step does NOT exist
1340
        if (! isset($txn_reg_steps[$reg_step_slug])) {
1341
            return false;
1342
        }
1343
        // if  we're trying to complete a step that is already completed
1344
        if ($txn_reg_steps[$reg_step_slug] === true) {
1345
            return true;
1346
        }
1347
        // if  we're trying to complete a step that hasn't even started
1348
        if ($status === true && $txn_reg_steps[$reg_step_slug] === false) {
1349
            return false;
1350
        }
1351
        // if current status value matches the incoming value (no change)
1352
        // type casting as int means values should collapse to either 0, 1, or a timestamp like 1234567890
1353
        if ((int)$txn_reg_steps[$reg_step_slug] === (int)$status) {
1354
            // this will happen in cases where multiple AJAX requests occur during the same step
1355
            return true;
1356
        }
1357
        // if we're trying to set a start time, but it has already been set...
1358
        if (is_numeric($status) && is_numeric($txn_reg_steps[$reg_step_slug])) {
1359
            // skip the update below, but don't return FALSE so that errors won't be displayed
1360
            return true;
1361
        }
1362
        // update completed status
1363
        $txn_reg_steps[$reg_step_slug] = $status;
1364
        $this->set_reg_steps($txn_reg_steps);
1365
        $this->save();
1366
        return true;
1367
    }
1368
1369
1370
1371
    /**
1372
     * remove_reg_step
1373
     * given a valid TXN_reg_step slug, this will remove (unset)
1374
     * the reg step from the TXN reg step array
1375
     *
1376
     * @param string $reg_step_slug
1377
     * @return void
1378
     * @throws EE_Error
1379
     */
1380
    public function remove_reg_step($reg_step_slug)
1381
    {
1382
        // get reg steps array
1383
        $txn_reg_steps = $this->reg_steps();
1384
        unset($txn_reg_steps[$reg_step_slug]);
1385
        $this->set_reg_steps($txn_reg_steps);
1386
    }
1387
1388
1389
1390
    /**
1391
     * toggle_failed_transaction_status
1392
     * upgrades a TXNs status from failed to abandoned,
1393
     * meaning that contact information has been captured for at least one registrant
1394
     *
1395
     * @param bool $save
1396
     * @return bool
1397
     * @throws EE_Error
1398
     */
1399
    public function toggle_failed_transaction_status($save = true)
1400
    {
1401
        // if TXN status is still set as "failed"...
1402
        if ($this->status_ID() === EEM_Transaction::failed_status_code) {
1403
            $this->set_status(EEM_Transaction::abandoned_status_code);
1404
            if ($save) {
1405
                $this->save();
1406
            }
1407
            return true;
1408
        }
1409
        return false;
1410
    }
1411
1412
1413
1414
    /**
1415
     * toggle_abandoned_transaction_status
1416
     * upgrades a TXNs status from failed or abandoned to incomplete
1417
     *
1418
     * @return bool
1419
     * @throws EE_Error
1420
     */
1421
    public function toggle_abandoned_transaction_status()
1422
    {
1423
        // if TXN status has not been updated already due to a payment, and is still set as "failed" or "abandoned"...
1424
        $txn_status = $this->status_ID();
1425
        if (
1426
            $txn_status === EEM_Transaction::failed_status_code
1427
            || $txn_status === EEM_Transaction::abandoned_status_code
1428
        ) {
1429
            // if a contact record for the primary registrant has been created
1430
            if (
1431
                $this->primary_registration() instanceof EE_Registration
1432
                && $this->primary_registration()->attendee() instanceof EE_Attendee
1433
            ) {
1434
                $this->set_status(EEM_Transaction::incomplete_status_code);
1435
            } else {
1436
                // no contact record? yer abandoned!
1437
                $this->set_status(EEM_Transaction::abandoned_status_code);
1438
            }
1439
            return true;
1440
        }
1441
        return false;
1442
    }
1443
1444
1445
1446
    /**
1447
     * checks if an Abandoned TXN has any related payments, and if so,
1448
     * updates the TXN status based on the amount paid
1449
     *
1450
     * @throws EE_Error
1451
     * @throws InvalidDataTypeException
1452
     * @throws InvalidInterfaceException
1453
     * @throws InvalidArgumentException
1454
     * @throws RuntimeException
1455
     */
1456
    public function verify_abandoned_transaction_status()
1457
    {
1458
        if ($this->status_ID() !== EEM_Transaction::abandoned_status_code) {
1459
            return;
1460
        }
1461
        $payments = $this->get_many_related('Payment');
1462
        if (! empty($payments)) {
1463
            foreach ($payments as $payment) {
1464
                if ($payment instanceof EE_Payment) {
1465
                    // kk this TXN should NOT be abandoned
1466
                    $this->update_status_based_on_total_paid();
1467
                    if (! (defined('DOING_AJAX') && DOING_AJAX) && is_admin()) {
1468
                        EE_Error::add_attention(
1469
                            sprintf(
1470
                                esc_html__(
1471
                                    'The status for Transaction #%1$d has been updated from "Abandoned" to "%2$s", because at least one payment has been made towards it. If the payment appears in the "Payment Details" table below, you may need to edit its status and/or other details as well.',
1472
                                    'event_espresso'
1473
                                ),
1474
                                $this->ID(),
1475
                                $this->pretty_status()
1476
                            )
1477
                        );
1478
                    }
1479
                    // get final reg step status
1480
                    $finalized = $this->final_reg_step_completed();
1481
                    // if the 'finalize_registration' step has been initiated (has a timestamp)
1482
                    // but has not yet been fully completed (TRUE)
1483
                    if (is_int($finalized) && $finalized !== false && $finalized !== true) {
1484
                        $this->set_reg_step_completed('finalize_registration');
1485
                        $this->save();
1486
                    }
1487
                }
1488
            }
1489
        }
1490
    }
1491
1492
}/* End of file EE_Transaction.class.php */
1493
/* Location: includes/classes/EE_Transaction.class.php */
1494