Completed
Branch BUG-10851-events-shortcode (6a6497)
by
unknown
44:06 queued 32:50
created

EE_Payment_Processor::reset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
1
<?php use EventEspresso\core\interfaces\ResettableInterface;
2
3
if ( ! defined('EVENT_ESPRESSO_VERSION')) {
4
    exit('No direct script access allowed');
5
}
6
EE_Registry::instance()->load_class('Processor_Base');
7
8
9
10
/**
11
 * EE_Payment_Processor
12
 * Class for handling processing of payments for transactions.
13
 *
14
 * @package            Event Espresso
15
 * @subpackage         core/libraries/payment_methods
16
 * @author             Mike Nelson
17
 */
18
class EE_Payment_Processor extends EE_Processor_Base implements ResettableInterface
19
{
20
21
    /**
22
     * @var EE_Payment_Processor $_instance
23
     * @access    private
24
     */
25
    private static $_instance;
26
27
28
29
    /**
30
     * @singleton method used to instantiate class object
31
     * @access    public
32
     * @return EE_Payment_Processor instance
33
     */
34
    public static function instance()
35
    {
36
        // check if class object is instantiated
37
        if ( ! self::$_instance instanceof EE_Payment_Processor) {
38
            self::$_instance = new self();
39
        }
40
        return self::$_instance;
41
    }
42
43
44
45
    /**
46
     * @return EE_Payment_Processor
47
     */
48
    public static function reset()
49
    {
50
        self::$_instance = null;
51
        return self::instance();
52
    }
53
54
55
56
    /**
57
     *private constructor to prevent direct creation
58
     *
59
     * @Constructor
60
     * @access private
61
     */
62
    private function __construct()
63
    {
64
        do_action('AHEE__EE_Payment_Processor__construct');
65
        add_action('http_api_curl', array($this, '_curl_requests_to_paypal_use_tls'), 10, 3);
66
    }
67
68
69
70
    /**
71
     * Using the selected gateway, processes the payment for that transaction, and updates the transaction
72
     * appropriately. Saves the payment that is generated
73
     *
74
     * @param EE_Payment_Method    $payment_method
75
     * @param EE_Transaction       $transaction
76
     * @param float                $amount       if only part of the transaction is to be paid for, how much.
77
     *                                           Leave null if payment is for the full amount owing
78
     * @param EE_Billing_Info_Form $billing_form (or probably null, if it's an offline or offsite payment method).
79
     *                                           Receive_form_submission() should have
80
     *                                           already been called on the billing form
81
     *                                           (ie, its inputs should have their normalized values set).
82
     * @param string               $return_url   string used mostly by offsite gateways to specify
83
     *                                           where to go AFTER the offsite gateway
84
     * @param string               $method       like 'CART', indicates who the client who called this was
85
     * @param bool                 $by_admin     TRUE if payment is being attempted from the admin
86
     * @param boolean              $update_txn   whether or not to call
87
     *                                           EE_Transaction_Processor::update_transaction_and_registrations_after_checkout_or_payment()
88
     * @param string               $cancel_url   URL to return to if off-site payments are cancelled
89
     * @return \EE_Payment
90
     * @throws \EE_Error
91
     */
92
    public function process_payment(
93
        EE_Payment_Method $payment_method,
94
        EE_Transaction $transaction,
95
        $amount = null,
96
        $billing_form = null,
97
        $return_url = null,
98
        $method = 'CART',
99
        $by_admin = false,
100
        $update_txn = true,
101
        $cancel_url = ''
102
    ) {
103
        if ((float)$amount < 0) {
104
            throw new EE_Error(
105
                sprintf(
106
                    __(
107
                        'Attempting to make a payment for a negative amount of %1$d for transaction %2$d. That should be a refund',
108
                        'event_espresso'
109
                    ),
110
                    $amount,
111
                    $transaction->ID()
112
                )
113
            );
114
        }
115
        // verify payment method
116
        $payment_method = EEM_Payment_Method::instance()->ensure_is_obj($payment_method, true);
117
        // verify transaction
118
        EEM_Transaction::instance()->ensure_is_obj($transaction);
119
        $transaction->set_payment_method_ID($payment_method->ID());
120
        // verify payment method type
121
        if ($payment_method->type_obj() instanceof EE_PMT_Base) {
122
            $payment = $payment_method->type_obj()->process_payment(
123
                $transaction,
124
                min($amount, $transaction->remaining()),//make sure we don't overcharge
125
                $billing_form,
126
                $return_url,
127
                add_query_arg(array('ee_cancel_payment' => true), $cancel_url),
128
                $method,
129
                $by_admin
130
            );
131
            // check if payment method uses an off-site gateway
132
            if ($payment_method->type_obj()->payment_occurs() !== EE_PMT_Base::offsite) {
133
                // don't process payments for off-site gateways yet because no payment has occurred yet
134
                $this->update_txn_based_on_payment($transaction, $payment, $update_txn);
135
            }
136
            return $payment;
137
        } else {
138
            EE_Error::add_error(
139
                sprintf(
140
                    __('A valid payment method could not be determined due to a technical issue.%sPlease try again or contact %s for assistance.', 'event_espresso'),
141
                    '<br/>',
142
                    EE_Registry::instance()->CFG->organization->get_pretty('email')
143
                ), __FILE__, __FUNCTION__, __LINE__
144
            );
145
            return null;
146
        }
147
    }
148
149
150
151
    /**
152
     * @param EE_Transaction|int $transaction
153
     * @param EE_Payment_Method  $payment_method
154
     * @throws EE_Error
155
     * @return string
156
     */
157
    public function get_ipn_url_for_payment_method($transaction, $payment_method)
158
    {
159
        /** @type \EE_Transaction $transaction */
160
        $transaction = EEM_Transaction::instance()->ensure_is_obj($transaction);
161
        $primary_reg = $transaction->primary_registration();
162
        if ( ! $primary_reg instanceof EE_Registration) {
163
            throw new EE_Error(
164
                sprintf(
165
                    __(
166
                        "Cannot get IPN URL for transaction with ID %d because it has no primary registration",
167
                        "event_espresso"
168
                    ),
169
                    $transaction->ID()
170
                )
171
            );
172
        }
173
        $payment_method = EEM_Payment_Method::instance()->ensure_is_obj($payment_method, true);
174
        $url = add_query_arg(
175
            array(
176
                'e_reg_url_link'    => $primary_reg->reg_url_link(),
177
                'ee_payment_method' => $payment_method->slug(),
178
            ),
179
            EE_Registry::instance()->CFG->core->txn_page_url()
180
        );
181
        return $url;
182
    }
183
184
185
186
    /**
187
     * Process the IPN. Firstly, we'll hope we put the standard args into the IPN URL so
188
     * we can easily find what registration the IPN is for and what payment method.
189
     * However, if not, we'll give all payment methods a chance to claim it and process it.
190
     * If a payment is found for the IPN info, it is saved.
191
     *
192
     * @param array              $_req_data            eg $_REQUEST
193
     * @param EE_Transaction|int $transaction          optional (or a transactions id)
194
     * @param EE_Payment_Method  $payment_method       (or a slug or id of one)
195
     * @param boolean            $update_txn           whether or not to call
196
     *                                                 EE_Transaction_Processor::update_transaction_and_registrations_after_checkout_or_payment()
197
     * @param bool               $separate_IPN_request whether the IPN uses a separate request ( true like PayPal )
198
     *                                                 or is processed manually ( false like Mijireh )
199
     * @throws EE_Error
200
     * @throws Exception
201
     * @return EE_Payment
202
     */
203
    public function process_ipn(
204
        $_req_data,
205
        $transaction = null,
206
        $payment_method = null,
207
        $update_txn = true,
208
        $separate_IPN_request = true
209
    ) {
210
        EE_Registry::instance()->load_model('Change_Log');
211
        $_req_data = $this->_remove_unusable_characters_from_array((array)$_req_data);
212
        EE_Processor_Base::set_IPN($separate_IPN_request);
213
        $obj_for_log = null;
214
        if ($transaction instanceof EE_Transaction) {
215
            $obj_for_log = $transaction;
216
            if ($payment_method instanceof EE_Payment_Method) {
217
                $obj_for_log = EEM_Payment::instance()->get_one(
218
                    array(
219
                        array('TXN_ID' => $transaction->ID(), 'PMD_ID' => $payment_method->ID()),
220
                        'order_by' => array('PAY_timestamp' => 'desc'),
221
                    )
222
                );
223
            }
224
        } else if ($payment_method instanceof EE_Payment) {
225
            $obj_for_log = $payment_method;
226
        }
227
        $log = EEM_Change_Log::instance()->log(
228
            EEM_Change_Log::type_gateway,
229
            array('IPN data received' => $_req_data),
230
            $obj_for_log
0 ignored issues
show
Bug introduced by
It seems like $obj_for_log can be null; however, log() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
231
        );
232
        try {
233
            /**
234
             * @var EE_Payment $payment
235
             */
236
            $payment = null;
237
            if ($transaction && $payment_method) {
238
                /** @type EE_Transaction $transaction */
239
                $transaction = EEM_Transaction::instance()->ensure_is_obj($transaction);
240
                /** @type EE_Payment_Method $payment_method */
241
                $payment_method = EEM_Payment_Method::instance()->ensure_is_obj($payment_method);
242
                if ($payment_method->type_obj() instanceof EE_PMT_Base) {
243
                    try {
244
                        $payment = $payment_method->type_obj()->handle_ipn($_req_data, $transaction);
245
                        $log->set_object($payment);
246
                    } catch (EventEspresso\core\exceptions\IpnException $e) {
247
                        EEM_Change_Log::instance()->log(
248
                            EEM_Change_Log::type_gateway,
249
                            array(
250
                                'message'     => 'IPN Exception: ' . $e->getMessage(),
251
                                'current_url' => EEH_URL::current_url(),
252
                                'payment'     => $e->getPaymentProperties(),
253
                                'IPN_data'    => $e->getIpnData(),
254
                            ),
255
                            $obj_for_log
0 ignored issues
show
Bug introduced by
It seems like $obj_for_log can be null; however, log() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
256
                        );
257
                        return $e->getPayment();
258
                    }
259
                } else {
260
                    // not a payment
261
                    EE_Error::add_error(
262
                        sprintf(
263
                            __('A valid payment method could not be determined due to a technical issue.%sPlease refresh your browser and try again or contact %s for assistance.', 'event_espresso'),
264
                            '<br/>',
265
                            EE_Registry::instance()->CFG->organization->get_pretty('email')
266
                        ),
267
                        __FILE__, __FUNCTION__, __LINE__
268
                    );
269
                }
270
            } else {
271
                //that's actually pretty ok. The IPN just wasn't able
272
                //to identify which transaction or payment method this was for
273
                // give all active payment methods a chance to claim it
274
                $active_payment_methods = EEM_Payment_Method::instance()->get_all_active();
275
                foreach ($active_payment_methods as $active_payment_method) {
276
                    try {
277
                        $payment = $active_payment_method->type_obj()->handle_unclaimed_ipn($_req_data);
278
                        $payment_method = $active_payment_method;
279
                        EEM_Change_Log::instance()->log(
280
                            EEM_Change_Log::type_gateway, array('IPN data' => $_req_data), $payment
281
                        );
282
                        break;
283
                    } catch (EventEspresso\core\exceptions\IpnException $e) {
284
                        EEM_Change_Log::instance()->log(
285
                            EEM_Change_Log::type_gateway,
286
                            array(
287
                                'message'     => 'IPN Exception: ' . $e->getMessage(),
288
                                'current_url' => EEH_URL::current_url(),
289
                                'payment'     => $e->getPaymentProperties(),
290
                                'IPN_data'    => $e->getIpnData(),
291
                            ),
292
                            $obj_for_log
0 ignored issues
show
Bug introduced by
It seems like $obj_for_log can be null; however, log() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
293
                        );
294
                        return $e->getPayment();
295
                    } catch (EE_Error $e) {
296
                        //that's fine- it apparently couldn't handle the IPN
297
                    }
298
                }
299
            }
300
            // 			EEM_Payment_Log::instance()->log("got to 7",$transaction,$payment_method);
301
            if ($payment instanceof EE_Payment) {
302
                $payment->save();
303
                //  update the TXN
304
                $this->update_txn_based_on_payment($transaction, $payment, $update_txn, $separate_IPN_request);
305
            } else {
306
                //we couldn't find the payment for this IPN... let's try and log at least SOMETHING
307
                if ($payment_method) {
308
                    EEM_Change_Log::instance()->log(EEM_Change_Log::type_gateway, array('IPN data' => $_req_data), $payment_method);
309
                } elseif ($transaction) {
310
                    EEM_Change_Log::instance()->log(EEM_Change_Log::type_gateway, array('IPN data' => $_req_data), $transaction);
311
                }
312
            }
313
            return $payment;
314
        } catch (EE_Error $e) {
315
            do_action(
316
                'AHEE__log', __FILE__, __FUNCTION__, sprintf(
317
                    __('Error occurred while receiving IPN. Transaction: %1$s, req data: %2$s. The error was "%3$s"', 'event_espresso'),
318
                    print_r($transaction, true),
319
                    print_r($_req_data, true),
320
                    $e->getMessage()
321
                )
322
            );
323
            throw $e;
324
        }
325
    }
326
327
328
329
    /**
330
     * Removes any non-printable illegal characters from the input,
331
     * which might cause a raucous when trying to insert into the database
332
     *
333
     * @param  array $request_data
334
     * @return array
335
     */
336
    protected function _remove_unusable_characters_from_array(array $request_data)
337
    {
338
        $return_data = array();
339
        foreach ($request_data as $key => $value) {
340
            $return_data[$this->_remove_unusable_characters($key)] = $this->_remove_unusable_characters($value);
341
        }
342
        return $return_data;
343
    }
344
345
346
347
    /**
348
     * Removes any non-printable illegal characters from the input,
349
     * which might cause a raucous when trying to insert into the database
350
     *
351
     * @param string $request_data
352
     * @return string
353
     */
354
    protected function _remove_unusable_characters($request_data)
355
    {
356
        return preg_replace('/[^[:print:]]/', '', $request_data);
357
    }
358
359
360
361
    /**
362
     * Should be called just before displaying the payment attempt results to the user,
363
     * when the payment attempt has finished. Some payment methods may have special
364
     * logic to perform here. For example, if process_payment() happens on a special request
365
     * and then the user is redirected to a page that displays the payment's status, this
366
     * should be called while loading the page that displays the payment's status. If the user is
367
     * sent to an offsite payment provider, this should be called upon returning from that offsite payment
368
     * provider.
369
     *
370
     * @param EE_Transaction|int $transaction
371
     * @param bool               $update_txn whether or not to call
372
     *                                       EE_Transaction_Processor::update_transaction_and_registrations_after_checkout_or_payment()
373
     * @throws \EE_Error
374
     * @return EE_Payment
375
     * @deprecated 4.6.24 method is no longer used. Instead it is up to client code, like SPCO,
376
     *                                       to call handle_ipn() for offsite gateways that don't receive separate IPNs
377
     */
378
    public function finalize_payment_for($transaction, $update_txn = true)
379
    {
380
        /** @var $transaction EE_Transaction */
381
        $transaction = EEM_Transaction::instance()->ensure_is_obj($transaction);
382
        $last_payment_method = $transaction->payment_method();
383
        if ($last_payment_method instanceof EE_Payment_Method) {
384
            $payment = $last_payment_method->type_obj()->finalize_payment_for($transaction);
385
            $this->update_txn_based_on_payment($transaction, $payment, $update_txn);
386
            return $payment;
387
        } else {
388
            return null;
389
        }
390
    }
391
392
393
394
    /**
395
     * Processes a direct refund request, saves the payment, and updates the transaction appropriately.
396
     *
397
     * @param EE_Payment_Method $payment_method
398
     * @param EE_Payment        $payment_to_refund
399
     * @param array             $refund_info
400
     * @return EE_Payment
401
     * @throws \EE_Error
402
     */
403
    public function process_refund(
404
        EE_Payment_Method $payment_method,
405
        EE_Payment $payment_to_refund,
406
        $refund_info = array()
407
    ) {
408
        if ($payment_method instanceof EE_Payment_Method && $payment_method->type_obj()->supports_sending_refunds()) {
409
            $payment_method->type_obj()->process_refund($payment_to_refund, $refund_info);
410
            $this->update_txn_based_on_payment($payment_to_refund->transaction(), $payment_to_refund);
411
        }
412
        return $payment_to_refund;
413
    }
414
415
416
417
    /**
418
     * This should be called each time there may have been an update to a
419
     * payment on a transaction (ie, we asked for a payment to process a
420
     * payment for a transaction, or we told a payment method about an IPN, or
421
     * we told a payment method to
422
     * "finalize_payment_for" (a transaction), or we told a payment method to
423
     * process a refund. This should handle firing the correct hooks to
424
     * indicate
425
     * what exactly happened and updating the transaction appropriately). This
426
     * could be integrated directly into EE_Transaction upon save, but we want
427
     * this logic to be separate from 'normal' plain-jane saving and updating
428
     * of transactions and payments, and to be tied to payment processing.
429
     * Note: this method DOES NOT save the payment passed into it. It is the responsibility
430
     * of previous code to decide whether or not to save (because the payment passed into
431
     * this method might be a temporary, never-to-be-saved payment from an offline gateway,
432
     * in which case we only want that payment object for some temporary usage during this request,
433
     * but we don't want it to be saved).
434
     *
435
     * @param EE_Transaction|int $transaction
436
     * @param EE_Payment         $payment
437
     * @param boolean            $update_txn
438
     *                        whether or not to call
439
     *                        EE_Transaction_Processor::
440
     *                        update_transaction_and_registrations_after_checkout_or_payment()
441
     *                        (you can save 1 DB query if you know you're going
442
     *                        to save it later instead)
443
     * @param bool               $IPN
444
     *                        if processing IPNs or other similar payment
445
     *                        related activities that occur in alternate
446
     *                        requests than the main one that is processing the
447
     *                        TXN, then set this to true to check whether the
448
     *                        TXN is locked before updating
449
     * @throws \EE_Error
450
     */
451
    public function update_txn_based_on_payment($transaction, $payment, $update_txn = true, $IPN = false)
452
    {
453
        $do_action = 'AHEE__EE_Payment_Processor__update_txn_based_on_payment__not_successful';
454
        /** @type EE_Transaction $transaction */
455
        $transaction = EEM_Transaction::instance()->ensure_is_obj($transaction);
456
        // can we freely update the TXN at this moment?
457
        if ($IPN && $transaction->is_locked()) {
458
            // don't update the transaction at this exact moment
459
            // because the TXN is active in another request
460
            EE_Cron_Tasks::schedule_update_transaction_with_payment(
461
                time(),
462
                $transaction->ID(),
463
                $payment->ID()
464
            );
465
        } else {
466
            // verify payment and that it has been saved
467
            if ($payment instanceof EE_Payment && $payment->ID()) {
468
                if (
469
                    $payment->payment_method() instanceof EE_Payment_Method
470
                    && $payment->payment_method()->type_obj() instanceof EE_PMT_Base
471
                ) {
472
                    $payment->payment_method()->type_obj()->update_txn_based_on_payment($payment);
473
                    // update TXN registrations with payment info
474
                    $this->process_registration_payments($transaction, $payment);
475
                }
476
                $do_action = $payment->just_approved()
477
                    ? 'AHEE__EE_Payment_Processor__update_txn_based_on_payment__successful'
478
                    : $do_action;
479
            } else {
480
                // send out notifications
481
                add_filter('FHEE__EED_Messages___maybe_registration__deliver_notifications', '__return_true');
482
                $do_action = 'AHEE__EE_Payment_Processor__update_txn_based_on_payment__no_payment_made';
483
            }
484
            if ($payment instanceof EE_Payment && $payment->status() !== EEM_Payment::status_id_failed) {
485
                /** @type EE_Transaction_Payments $transaction_payments */
486
                $transaction_payments = EE_Registry::instance()->load_class('Transaction_Payments');
487
                // set new value for total paid
488
                $transaction_payments->calculate_total_payments_and_update_status($transaction);
489
                // call EE_Transaction_Processor::update_transaction_and_registrations_after_checkout_or_payment() ???
490
                if ($update_txn) {
491
                    $this->_post_payment_processing($transaction, $payment, $IPN);
492
                }
493
            }
494
            // granular hook for others to use.
495
            do_action($do_action, $transaction, $payment);
496
            do_action('AHEE_log', __CLASS__, __FUNCTION__, $do_action, '$do_action');
497
            //global hook for others to use.
498
            do_action('AHEE__EE_Payment_Processor__update_txn_based_on_payment', $transaction, $payment);
499
        }
500
    }
501
502
503
504
    /**
505
     * update registrations REG_paid field after successful payment and link registrations with payment
506
     *
507
     * @param EE_Transaction    $transaction
508
     * @param EE_Payment        $payment
509
     * @param EE_Registration[] $registrations
510
     * @throws \EE_Error
511
     */
512
    public function process_registration_payments(
513
        EE_Transaction $transaction,
514
        EE_Payment $payment,
515
        $registrations = array()
516
    ) {
517
        // only process if payment was successful
518
        if ($payment->status() !== EEM_Payment::status_id_approved) {
519
            return;
520
        }
521
        //EEM_Registration::instance()->show_next_x_db_queries();
522
        if (empty($registrations)) {
523
            // find registrations with monies owing that can receive a payment
524
            $registrations = $transaction->registrations(
525
                array(
526
                    array(
527
                        // only these reg statuses can receive payments
528
                        'STS_ID'           => array('IN', EEM_Registration::reg_statuses_that_allow_payment()),
529
                        'REG_final_price'  => array('!=', 0),
530
                        'REG_final_price*' => array('!=', 'REG_paid', true),
531
                    ),
532
                )
533
            );
534
        }
535
        // still nothing ??!??
536
        if (empty($registrations)) {
537
            return;
538
        }
539
        // todo: break out the following logic into a separate strategy class
540
        // todo: named something like "Sequential_Reg_Payment_Strategy"
541
        // todo: which would apply payments using the capitalist "first come first paid" approach
542
        // todo: then have another strategy class like "Distributed_Reg_Payment_Strategy"
543
        // todo: which would be the socialist "everybody gets a piece of pie" approach,
544
        // todo: which would be better for deposits, where you want a bit of the payment applied to each registration
545
        $refund = $payment->is_a_refund();
546
        // how much is available to apply to registrations?
547
        $available_payment_amount = abs($payment->amount());
548
        foreach ($registrations as $registration) {
549
            if ($registration instanceof EE_Registration) {
550
                // nothing left?
551
                if ($available_payment_amount <= 0) {
552
                    break;
553
                }
554
                if ($refund) {
555
                    $available_payment_amount = $this->process_registration_refund($registration, $payment, $available_payment_amount);
556
                } else {
557
                    $available_payment_amount = $this->process_registration_payment($registration, $payment, $available_payment_amount);
558
                }
559
            }
560
        }
561
        if ($available_payment_amount > 0 && apply_filters('FHEE__EE_Payment_Processor__process_registration_payments__display_notifications', false)) {
562
            EE_Error::add_attention(
563
                sprintf(
564
                    __('A remainder of %1$s exists after applying this payment to Registration(s) %2$s.%3$sPlease verify that the original payment amount of %4$s is correct. If so, you should edit this payment and select at least one additional registration in the "Registrations to Apply Payment to" section, so that the remainder of this payment can be applied to the additional registration(s).',
565
                        'event_espresso'),
566
                    EEH_Template::format_currency($available_payment_amount),
567
                    implode(', ', array_keys($registrations)),
568
                    '<br/>',
569
                    EEH_Template::format_currency($payment->amount())
570
                ),
571
                __FILE__, __FUNCTION__, __LINE__
572
            );
573
        }
574
    }
575
576
577
578
    /**
579
     * update registration REG_paid field after successful payment and link registration with payment
580
     *
581
     * @param EE_Registration $registration
582
     * @param EE_Payment      $payment
583
     * @param float           $available_payment_amount
584
     * @return float
585
     * @throws \EE_Error
586
     */
587
    public function process_registration_payment(
588
        EE_Registration $registration,
589
        EE_Payment $payment,
590
        $available_payment_amount = 0.00
591
    ) {
592
        $owing = $registration->final_price() - $registration->paid();
593
        if ($owing > 0) {
594
            // don't allow payment amount to exceed the available payment amount, OR the amount owing
595
            $payment_amount = min($available_payment_amount, $owing);
596
            // update $available_payment_amount
597
            $available_payment_amount -= $payment_amount;
598
            //calculate and set new REG_paid
599
            $registration->set_paid($registration->paid() + $payment_amount);
600
            // now save it
601
            $this->_apply_registration_payment($registration, $payment, $payment_amount);
602
        }
603
        return $available_payment_amount;
604
    }
605
606
607
608
    /**
609
     * update registration REG_paid field after successful payment and link registration with payment
610
     *
611
     * @param EE_Registration $registration
612
     * @param EE_Payment      $payment
613
     * @param float           $payment_amount
614
     * @return void
615
     * @throws \EE_Error
616
     */
617
    protected function _apply_registration_payment(
618
        EE_Registration $registration,
619
        EE_Payment $payment,
620
        $payment_amount = 0.00
621
    ) {
622
        // find any existing reg payment records for this registration and payment
623
        $existing_reg_payment = EEM_Registration_Payment::instance()->get_one(
624
            array(array('REG_ID' => $registration->ID(), 'PAY_ID' => $payment->ID()))
625
        );
626
        // if existing registration payment exists
627
        if ($existing_reg_payment instanceof EE_Registration_Payment) {
628
            // then update that record
629
            $existing_reg_payment->set_amount($payment_amount);
630
            $existing_reg_payment->save();
631
        } else {
632
            // or add new relation between registration and payment and set amount
633
            $registration->_add_relation_to($payment, 'Payment', array('RPY_amount' => $payment_amount));
634
            // make it stick
635
            $registration->save();
636
        }
637
    }
638
639
640
641
    /**
642
     * update registration REG_paid field after refund and link registration with payment
643
     *
644
     * @param EE_Registration $registration
645
     * @param EE_Payment      $payment
646
     * @param float           $available_refund_amount - IMPORTANT !!! SEND AVAILABLE REFUND AMOUNT AS A POSITIVE NUMBER
647
     * @return float
648
     * @throws \EE_Error
649
     */
650
    public function process_registration_refund(
651
        EE_Registration $registration,
652
        EE_Payment $payment,
653
        $available_refund_amount = 0.00
654
    ) {
655
        //EEH_Debug_Tools::printr( $payment->amount(), '$payment->amount()', __FILE__, __LINE__ );
656
        if ($registration->paid() > 0) {
657
            // ensure $available_refund_amount is NOT negative
658
            $available_refund_amount = (float)abs($available_refund_amount);
659
            // don't allow refund amount to exceed the available payment amount, OR the amount paid
660
            $refund_amount = min($available_refund_amount, (float)$registration->paid());
661
            // update $available_payment_amount
662
            $available_refund_amount -= $refund_amount;
663
            //calculate and set new REG_paid
664
            $registration->set_paid($registration->paid() - $refund_amount);
665
            // convert payment amount back to a negative value for storage in the db
666
            $refund_amount = (float)abs($refund_amount) * -1;
667
            // now save it
668
            $this->_apply_registration_payment($registration, $payment, $refund_amount);
669
        }
670
        return $available_refund_amount;
671
    }
672
673
674
675
    /**
676
     * Process payments and transaction after payment process completed.
677
     * ultimately this will send the TXN and payment details off so that notifications can be sent out.
678
     * if this request happens to be processing an IPN,
679
     * then we will also set the Payment Options Reg Step to completed,
680
     * and attempt to completely finalize the TXN if all of the other Reg Steps are completed as well.
681
     *
682
     * @param EE_Transaction $transaction
683
     * @param EE_Payment     $payment
684
     * @param bool           $IPN
685
     * @throws \EE_Error
686
     */
687
    protected function _post_payment_processing(EE_Transaction $transaction, EE_Payment $payment, $IPN = false)
688
    {
689
        /** @type EE_Transaction_Processor $transaction_processor */
690
        $transaction_processor = EE_Registry::instance()->load_class('Transaction_Processor');
691
        // is the Payment Options Reg Step completed ?
692
        $payment_options_step_completed = $transaction->reg_step_completed('payment_options');
693
        // if the Payment Options Reg Step is completed...
694
        $revisit = $payment_options_step_completed === true ? true : false;
695
        // then this is kinda sorta a revisit with regards to payments at least
696
        $transaction_processor->set_revisit($revisit);
697
        // if this is an IPN, let's consider the Payment Options Reg Step completed if not already
698
        if (
699
            $IPN
700
            && $payment_options_step_completed !== true
701
            && ($payment->is_approved() || $payment->is_pending())
702
        ) {
703
            $payment_options_step_completed = $transaction->set_reg_step_completed(
704
                'payment_options'
705
            );
706
        }
707
        // maybe update status, but don't save transaction just yet
708
        $transaction->update_status_based_on_total_paid(false);
709
        // check if 'finalize_registration' step has been completed...
710
        $finalized = $transaction->reg_step_completed('finalize_registration');
711
        //  if this is an IPN and the final step has not been initiated
712
        if ($IPN && $payment_options_step_completed && $finalized === false) {
713
            // and if it hasn't already been set as being started...
714
            $finalized = $transaction->set_reg_step_initiated('finalize_registration');
715
        }
716
        $transaction->save();
717
        // because the above will return false if the final step was not fully completed, we need to check again...
718
        if ($IPN && $finalized !== false) {
719
            // and if we are all good to go, then send out notifications
720
            add_filter('FHEE__EED_Messages___maybe_registration__deliver_notifications', '__return_true');
721
            //ok, now process the transaction according to the payment
722
            $transaction_processor->update_transaction_and_registrations_after_checkout_or_payment($transaction, $payment);
723
        }
724
        // DEBUG LOG
725
        $payment_method = $payment->payment_method();
726
        if ($payment_method instanceof EE_Payment_Method) {
727
            $payment_method_type_obj = $payment_method->type_obj();
728
            if ($payment_method_type_obj instanceof EE_PMT_Base) {
729
                $gateway = $payment_method_type_obj->get_gateway();
730
                if ($gateway instanceof EE_Gateway) {
731
                    $gateway->log(
732
                        array(
733
                            'message'               => __('Post Payment Transaction Details', 'event_espresso'),
734
                            'transaction'           => $transaction->model_field_array(),
735
                            'finalized'             => $finalized,
736
                            'IPN'                   => $IPN,
737
                            'deliver_notifications' => has_filter(
738
                                'FHEE__EED_Messages___maybe_registration__deliver_notifications'
739
                            ),
740
                        ),
741
                        $payment
742
                    );
743
                }
744
            }
745
        }
746
    }
747
748
749
750
    /**
751
     * Force posts to PayPal to use TLS v1.2. See:
752
     * https://core.trac.wordpress.org/ticket/36320
753
     * https://core.trac.wordpress.org/ticket/34924#comment:15
754
     * https://www.paypal-knowledge.com/infocenter/index?page=content&widgetview=true&id=FAQ1914&viewlocale=en_US
755
     * This will affect paypal standard, pro, express, and payflow.
756
     */
757
    public static function _curl_requests_to_paypal_use_tls($handle, $r, $url)
758
    {
759
        if (strstr($url, 'https://') && strstr($url, '.paypal.com')) {
760
            //Use the value of the constant CURL_SSLVERSION_TLSv1 = 1
761
            //instead of the constant because it might not be defined
762
            curl_setopt($handle, CURLOPT_SSLVERSION, 1);
763
        }
764
    }
765
}
766