Completed
Branch FET-9795-new-interfaces (4c886e)
by
unknown
55:47 queued 40:48
created

EE_Payment_Processor::reset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 3
c 1
b 0
f 1
nc 1
nop 0
dl 0
loc 4
rs 10
1
<?php if ( ! defined('EVENT_ESPRESSO_VERSION')) { exit('No direct script access allowed'); }
2
EE_Registry::instance()->load_class( 'Processor_Base' );
3
/**
4
 *
5
 * EE_Payment_Processor
6
 *
7
 * Class for handling processing of payments for transactions.
8
 *
9
 * @package			Event Espresso
10
 * @subpackage		core/libraries/payment_methods
11
 * @author			Mike Nelson
12
 *
13
 */
14
class EE_Payment_Processor extends EE_Processor_Base implements EventEspresso\core\interfaces\ResettableInterface {
15
	/**
16
     * 	@var EE_Payment_Processor $_instance
17
	 * 	@access 	private
18
     */
19
	private static $_instance;
20
21
22
23
	/**
24
	 *@singleton method used to instantiate class object
25
	 *@access public
26
	 *@return EE_Payment_Processor instance
27
	 */
28
	public static function instance() {
29
		// check if class object is instantiated
30
		if ( ! self::$_instance instanceof EE_Payment_Processor ) {
31
			self::$_instance = new self();
32
		}
33
		return self::$_instance;
34
	}
35
36
37
38
	/**
39
	 * @return EE_Payment_Processor
40
	 */
41
	public static function reset() {
42
		self::$_instance = null;
43
		return self::instance();
44
	}
45
46
47
48
	/**
49
	 *private constructor to prevent direct creation
50
	 *@Constructor
51
	 *@access private
52
	 *@return EE_Payment_Processor
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
53
	 */
54
	private function __construct() {
55
		do_action( 'AHEE__EE_Payment_Processor__construct' );
56
	}
57
58
59
60
	/**
61
	 * Using the selected gateway, processes the payment for that transaction, and updates the transaction appropriately.
62
	 * Saves the payment that is generated
63
64
	 *
65
*@param EE_Payment_Method    $payment_method
66
	 * @param EE_Transaction       $transaction
67
	 * @param float                $amount       if only part of the transaction is to be paid for, how much.
68
	 *                                           Leave null if payment is for the full amount owing
69
	 * @param EE_Billing_Info_Form $billing_form (or probably null, if it's an offline or offsite payment method).
70
	 *                                           Receive_form_submission() should have
71
	 *                                           already been called on the billing form
72
	 *                                           (ie, its inputs should have their normalized values set).
73
	 * @param string               $return_url   string used mostly by offsite gateways to specify
74
	 *                                           where to go AFTER the offsite gateway
75
	 * @param string               $method       like 'CART', indicates who the client who called this was
76
	 * @param bool                 $by_admin     TRUE if payment is being attempted from the admin
77
	 * @param boolean              $update_txn   whether or not to call
78
	 *                                           EE_Transaction_Processor::update_transaction_and_registrations_after_checkout_or_payment()
79
	 * @param string               $cancel_url   URL to return to if off-site payments are cancelled
80
	 * @return \EE_Payment
81
	 * @throws \EE_Error
82
	 */
83
	public function process_payment(
84
		EE_Payment_Method $payment_method,
85
		EE_Transaction $transaction,
86
		$amount = null,
87
		$billing_form = null,
88
		$return_url = null,
89
		$method = 'CART',
90
		$by_admin = false,
91
		$update_txn = true,
92
		$cancel_url = ''
93
	) {
94
		if( (float)$amount < 0 ) {
95
			throw new EE_Error(
96
				sprintf(
97
					__(
98
						'Attempting to make a payment for a negative amount of %1$d for transaction %2$d. That should be a refund',
99
						'event_espresso'
100
					),
101
					$amount,
102
					$transaction->ID()
103
				)
104
			);
105
		}
106
		// verify payment method
107
		$payment_method = EEM_Payment_Method::instance()->ensure_is_obj( $payment_method, TRUE );
108
		// verify transaction
109
		EEM_Transaction::instance()->ensure_is_obj( $transaction );
110
		$transaction->set_payment_method_ID( $payment_method->ID() );
111
		// verify payment method type
112
		if ( $payment_method->type_obj() instanceof EE_PMT_Base ) {
113
			$payment = $payment_method->type_obj()->process_payment(
114
				$transaction,
115
				min( $amount, $transaction->remaining() ),//make sure we don't overcharge
116
				$billing_form,
117
				$return_url,
118
				add_query_arg( array( 'ee_cancel_payment' => true ), $cancel_url ),
119
				$method,
120
				$by_admin
121
			);
122
			// check if payment method uses an off-site gateway
123
			if ( $payment_method->type_obj()->payment_occurs() !== EE_PMT_Base::offsite ) {
124
				// don't process payments for off-site gateways yet because no payment has occurred yet
125
				$this->update_txn_based_on_payment( $transaction, $payment, $update_txn );
126
			}
127
			return $payment;
128
		} else {
129
			EE_Error::add_error(
130
				sprintf(
131
					__( 'A valid payment method could not be determined due to a technical issue.%sPlease try again or contact %s for assistance.', 'event_espresso' ),
132
					'<br/>',
133
					EE_Registry::instance()->CFG->organization->get_pretty( 'email' )
134
				), __FILE__, __FUNCTION__, __LINE__
135
			);
136
			return NULL;
137
		}
138
	}
139
140
141
142
	/**
143
144
	 * @param EE_Transaction|int $transaction
145
	 * @param EE_Payment_Method $payment_method
146
	 * @throws EE_Error
147
	 * @return string
148
	 */
149
	public function get_ipn_url_for_payment_method( $transaction, $payment_method ){
150
		/** @type \EE_Transaction $transaction */
151
		$transaction = EEM_Transaction::instance()->ensure_is_obj( $transaction );
152
		$primary_reg = $transaction->primary_registration();
153
		if( ! $primary_reg instanceof EE_Registration ){
154
			throw new EE_Error(
155
				sprintf(
156
					__(
157
						"Cannot get IPN URL for transaction with ID %d because it has no primary registration",
158
						"event_espresso"
159
					),
160
					$transaction->ID()
161
				)
162
			);
163
		}
164
		$payment_method = EEM_Payment_Method::instance()->ensure_is_obj($payment_method,true);
165
		$url = add_query_arg(
166
			array(
167
				'e_reg_url_link'=>$primary_reg->reg_url_link(),
168
				'ee_payment_method'=>$payment_method->slug()
169
			),
170
			EE_Registry::instance()->CFG->core->txn_page_url()
171
		);
172
		return $url;
173
	}
174
175
176
177
	/**
178
	 * Process the IPN. Firstly, we'll hope we put the standard args into the IPN URL so
179
	 * we can easily find what registration the IPN is for and what payment method.
180
	 * However, if not, we'll give all payment methods a chance to claim it and process it.
181
	 * If a payment is found for the IPN info, it is saved.
182
	 *
183
	 * @param                   $_req_data
184
	 * @param EE_Transaction|int $transaction          optional (or a transactions id)
185
	 * @param EE_Payment_Method $payment_method       (or a slug or id of one)
186
	 * @param boolean           $update_txn           whether or not to call
187
	 *                                                EE_Transaction_Processor::update_transaction_and_registrations_after_checkout_or_payment()
188
	 * @param bool              $separate_IPN_request whether the IPN uses a separate request ( true like PayPal )
189
	 *                                                or is processed manually ( false like Mijireh )
190
	 * @throws EE_Error
191
	 * @throws Exception
192
	 * @return EE_Payment
193
	 */
194
	public function process_ipn(
195
		$_req_data,
196
		$transaction = null,
197
		$payment_method = null,
198
		$update_txn = true,
199
		$separate_IPN_request = true
200
	) {
201
		EE_Registry::instance()->load_model( 'Change_Log' );
202
		$_req_data = $this->_remove_unusable_characters_from_array( $_req_data );
203
		EE_Processor_Base::set_IPN( $separate_IPN_request );
204
		$obj_for_log = null;
205
		if( $transaction instanceof EE_Transaction ){
206
			$obj_for_log = $transaction;
207
			if( $payment_method instanceof EE_Payment_Method ) {
208
				$obj_for_log = EEM_Payment::instance()->get_one(
209
					array(
210
						array( 'TXN_ID' => $transaction->ID(), 'PMD_ID' => $payment_method->ID() ),
211
						'order_by' => array( 'PAY_timestamp' => 'desc' )
212
					)
213
				);
214
			}
215
		} else if( $payment_method instanceof EE_Payment ) {
216
			$obj_for_log = $payment_method;
217
		}
218
		$log = EEM_Change_Log::instance()->log(
219
			EEM_Change_Log::type_gateway,
220
			array( 'IPN data received' => $_req_data ),
221
			$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...
222
		);
223
		try{
224
			/**
225
			 * @var EE_Payment $payment
226
			 */
227
			$payment = NULL;
228
			if($transaction && $payment_method){
229
				/** @type EE_Transaction $transaction */
230
				$transaction = EEM_Transaction::instance()->ensure_is_obj($transaction);
231
				/** @type EE_Payment_Method $payment_method */
232
				$payment_method = EEM_Payment_Method::instance()->ensure_is_obj($payment_method);
233
				if ( $payment_method->type_obj() instanceof EE_PMT_Base ) {
234
						$payment = $payment_method->type_obj()->handle_ipn( $_req_data, $transaction );
235
						$log->set_object($payment);
0 ignored issues
show
Documentation Bug introduced by
The method set_object does not exist on object<EE_Attendee>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
236
				} else {
237
					// not a payment
238
					EE_Error::add_error(
239
						sprintf(
240
							__( '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' ),
241
							'<br/>',
242
							EE_Registry::instance()->CFG->organization->get_pretty( 'email' )
243
						),
244
						__FILE__, __FUNCTION__, __LINE__
245
					);
246
				}
247
			}else{
248
				//that's actually pretty ok. The IPN just wasn't able
249
				//to identify which transaction or payment method this was for
250
				// give all active payment methods a chance to claim it
251
				$active_payment_methods = EEM_Payment_Method::instance()->get_all_active();
252
				foreach( $active_payment_methods as $active_payment_method ){
253
					try{
254
						$payment = $active_payment_method->type_obj()->handle_unclaimed_ipn( $_req_data );
0 ignored issues
show
Documentation Bug introduced by
The method type_obj does not exist on object<EE_Base_Class>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
255
						$payment_method = $active_payment_method;
256
						EEM_Change_Log::instance()->log(
257
							EEM_Change_Log::type_gateway, array('IPN data'=>$_req_data), $payment
258
						);
259
						break;
260
					} catch( EE_Error $e ) {
261
						//that's fine- it apparently couldn't handle the IPN
262
					}
263
				}
264
265
			}
266
// 			EEM_Payment_Log::instance()->log("got to 7",$transaction,$payment_method);
267
			if( $payment instanceof EE_Payment){
268
				$payment->save();
269
				//  update the TXN
270
				$this->update_txn_based_on_payment( $transaction, $payment, $update_txn, $separate_IPN_request );
0 ignored issues
show
Bug introduced by
It seems like $transaction can also be of type null; however, EE_Payment_Processor::up..._txn_based_on_payment() does only seem to accept object<EE_Transaction>|integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
271
			}else{
272
				//we couldn't find the payment for this IPN... let's try and log at least SOMETHING
273
				if($payment_method){
274
					EEM_Change_Log::instance()->log(EEM_Change_Log::type_gateway, array('IPN data'=>$_req_data), $payment_method);
275
				}elseif($transaction){
276
					EEM_Change_Log::instance()->log(EEM_Change_Log::type_gateway, array('IPN data'=>$_req_data), $transaction);
0 ignored issues
show
Bug introduced by
It seems like $transaction can also be of type integer; however, EEM_Change_Log::log() does only seem to accept object<EE_Base_Class>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

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