/**
  *
  * @param EE_Transaction       $transaction
  * @param float                $amount
  * @param EE_Billing_Info_Form $billing_info
  * @param string               $return_url
  * @param string                 $fail_url
  * @param string               $method
  * @param bool           $by_admin
  * @return EE_Payment
  * @throws EE_Error
  */
 function process_payment(EE_Transaction $transaction, $amount = null, $billing_info = null, $return_url = null, $fail_url = '', $method = 'CART', $by_admin = false)
 {
     // @todo: add surcharge for the payment method, if any
     if ($this->_gateway) {
         //there is a gateway, so we're going to make a payment object
         //but wait! do they already have a payment in progress that we thought was failed?
         $duplicate_properties = array('STS_ID' => EEM_Payment::status_id_failed, 'TXN_ID' => $transaction->ID(), 'PMD_ID' => $this->_pm_instance->ID(), 'PAY_source' => $method, 'PAY_amount' => $amount !== null ? $amount : $transaction->remaining(), 'PAY_gateway_response' => null);
         $payment = EEM_Payment::instance()->get_one(array($duplicate_properties));
         //if we didn't already have a payment in progress for the same thing,
         //then we actually want to make a new payment
         if (!$payment instanceof EE_Payment) {
             $payment = EE_Payment::new_instance(array_merge($duplicate_properties, array('PAY_timestamp' => time(), 'PAY_txn_id_chq_nmbr' => null, 'PAY_po_number' => null, 'PAY_extra_accntng' => null, 'PAY_details' => null)));
         }
         //make sure the payment has been saved to show we started it, and so it has an ID should the gateway try to log it
         $payment->save();
         $billing_values = $this->_get_billing_values_from_form($billing_info);
         //  Offsite Gateway
         if ($this->_gateway instanceof EE_Offsite_Gateway) {
             $payment = $this->_gateway->set_redirection_info($payment, $billing_values, $return_url, EE_Config::instance()->core->txn_page_url(array('e_reg_url_link' => $transaction->primary_registration()->reg_url_link(), 'ee_payment_method' => $this->_pm_instance->slug())), $fail_url);
             $payment->save();
             //  Onsite Gateway
         } elseif ($this->_gateway instanceof EE_Onsite_Gateway) {
             $payment = $this->_gateway->do_direct_payment($payment, $billing_values);
             $payment->save();
         } else {
             throw new EE_Error(sprintf(__('Gateway for payment method type "%s" is "%s", not a subclass of either EE_Offsite_Gateway or EE_Onsite_Gateway, or null (to indicate NO gateway)', 'event_espresso'), get_class($this), gettype($this->_gateway)));
         }
     } else {
         // no gateway provided
         // there is no payment. Must be an offline gateway
         // create a payment object anyways, but dont save it
         $payment = EE_Payment::new_instance(array('STS_ID' => EEM_Payment::status_id_pending, 'TXN_ID' => $transaction->ID(), 'PMD_ID' => $transaction->payment_method_ID(), 'PAY_amount' => 0.0, 'PAY_timestamp' => time()));
     }
     // if there is billing info, clean it and save it now
     if ($billing_info instanceof EE_Billing_Attendee_Info_Form) {
         $this->_save_billing_info_to_attendee($billing_info, $transaction);
     }
     return $payment;
 }
 /**
  * Process the IPN. Firstly, we'll hope we put the standard args into the IPN URL so
  * we can easily find what registration the IPN is for and what payment method.
  * However, if not, we'll give all payment methods a chance to claim it and process it.
  * If a payment is found for the IPN info, it is saved.
  *
  * @param array             $_req_data eg $_REQUEST
  * @param EE_Transaction|int $transaction          optional (or a transactions id)
  * @param EE_Payment_Method $payment_method       (or a slug or id of one)
  * @param boolean           $update_txn           whether or not to call
  *                                                EE_Transaction_Processor::update_transaction_and_registrations_after_checkout_or_payment()
  * @param bool              $separate_IPN_request whether the IPN uses a separate request ( true like PayPal )
  *                                                or is processed manually ( false like Mijireh )
  * @throws EE_Error
  * @throws Exception
  * @return EE_Payment
  */
 public function process_ipn($_req_data, $transaction = null, $payment_method = null, $update_txn = true, $separate_IPN_request = true)
 {
     EE_Registry::instance()->load_model('Change_Log');
     $_req_data = $this->_remove_unusable_characters_from_array((array) $_req_data);
     EE_Processor_Base::set_IPN($separate_IPN_request);
     $obj_for_log = null;
     if ($transaction instanceof EE_Transaction) {
         $obj_for_log = $transaction;
         if ($payment_method instanceof EE_Payment_Method) {
             $obj_for_log = EEM_Payment::instance()->get_one(array(array('TXN_ID' => $transaction->ID(), 'PMD_ID' => $payment_method->ID()), 'order_by' => array('PAY_timestamp' => 'desc')));
         }
     } else {
         if ($payment_method instanceof EE_Payment) {
             $obj_for_log = $payment_method;
         }
     }
     $log = EEM_Change_Log::instance()->log(EEM_Change_Log::type_gateway, array('IPN data received' => $_req_data), $obj_for_log);
     try {
         /**
          * @var EE_Payment $payment
          */
         $payment = NULL;
         if ($transaction && $payment_method) {
             /** @type EE_Transaction $transaction */
             $transaction = EEM_Transaction::instance()->ensure_is_obj($transaction);
             /** @type EE_Payment_Method $payment_method */
             $payment_method = EEM_Payment_Method::instance()->ensure_is_obj($payment_method);
             if ($payment_method->type_obj() instanceof EE_PMT_Base) {
                 $payment = $payment_method->type_obj()->handle_ipn($_req_data, $transaction);
                 $log->set_object($payment);
             } else {
                 // not a payment
                 EE_Error::add_error(sprintf(__('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'), '<br/>', EE_Registry::instance()->CFG->organization->get_pretty('email')), __FILE__, __FUNCTION__, __LINE__);
             }
         } else {
             //that's actually pretty ok. The IPN just wasn't able
             //to identify which transaction or payment method this was for
             // give all active payment methods a chance to claim it
             $active_payment_methods = EEM_Payment_Method::instance()->get_all_active();
             foreach ($active_payment_methods as $active_payment_method) {
                 try {
                     $payment = $active_payment_method->type_obj()->handle_unclaimed_ipn($_req_data);
                     $payment_method = $active_payment_method;
                     EEM_Change_Log::instance()->log(EEM_Change_Log::type_gateway, array('IPN data' => $_req_data), $payment);
                     break;
                 } catch (EE_Error $e) {
                     //that's fine- it apparently couldn't handle the IPN
                 }
             }
         }
         // 			EEM_Payment_Log::instance()->log("got to 7",$transaction,$payment_method);
         if ($payment instanceof EE_Payment) {
             $payment->save();
             //  update the TXN
             $this->update_txn_based_on_payment($transaction, $payment, $update_txn, $separate_IPN_request);
         } else {
             //we couldn't find the payment for this IPN... let's try and log at least SOMETHING
             if ($payment_method) {
                 EEM_Change_Log::instance()->log(EEM_Change_Log::type_gateway, array('IPN data' => $_req_data), $payment_method);
             } elseif ($transaction) {
                 EEM_Change_Log::instance()->log(EEM_Change_Log::type_gateway, array('IPN data' => $_req_data), $transaction);
             }
         }
         return $payment;
     } catch (EE_Error $e) {
         do_action('AHEE__log', __FILE__, __FUNCTION__, sprintf(__('Error occurred while receiving IPN. Transaction: %1$s, req data: %2$s. The error was "%3$s"', 'event_espresso'), print_r($transaction, TRUE), print_r($_req_data, TRUE), $e->getMessage()));
         throw $e;
     }
 }
 /**
  * Sets the initial payment method properties (including extra meta)
  *
  * @param EE_Payment_Method $payment_method
  * @return EE_Payment_Method
  * @throws \EE_Error
  */
 public function initialize_payment_method($payment_method)
 {
     $pm_type_obj = $payment_method->type_obj();
     $payment_method->set_description($pm_type_obj->default_description());
     if (!$payment_method->button_url()) {
         $payment_method->set_button_url($pm_type_obj->default_button_url());
     }
     //now add setup its default extra meta properties
     $extra_metas = $pm_type_obj->settings_form()->extra_meta_inputs();
     if (!empty($extra_metas)) {
         //verify the payment method has an ID before adding extra meta
         if (!$payment_method->ID()) {
             $payment_method->save();
         }
         foreach ($extra_metas as $meta_name => $input) {
             $payment_method->update_extra_meta($meta_name, $input->raw_value());
         }
     }
     return $payment_method;
 }