/**
  * If table key (id) is NULL : inserts a new row
  * otherwise updates existing row in the database table
  *
  * Can be overridden or overloaded by the child class
  *
  * @param  boolean  $updateNulls  TRUE: null object variables are also updated, FALSE: not.
  * @return boolean                TRUE if successful otherwise FALSE
  */
 public function store($updateNulls = false)
 {
     if (!cbpaidApp::authoriseAction('cbsubs.refunds')) {
         $this->setError(CBPTXT::T("Not authorized"));
         return false;
     }
     // 1) check:
     if (!in_array($this->payment_status, array('Completed', 'Pending', 'Partially-Refunded'))) {
         $this->setError(CBPTXT::T("This payment is not completed, pending or partially refunded."));
         return false;
     }
     if ($this->txn_id == '') {
         $this->txn_id = 'None';
         // needed for updatePayment to generate payment record.
     }
     $payment = new cbpaidPayment();
     if (!$payment->load((int) $this->id)) {
         $this->setError(CBPTXT::T("This payment does not exist."));
         return false;
     }
     $paymentBasket = new cbpaidPaymentBasket();
     if (!$paymentBasket->load($this->payment_basket_id)) {
         $this->setError(CBPTXT::T("This payment has no associated payment basket and cannot be refunded from here. Maybe from your PSP online terminal ?"));
         return false;
     }
     if (!$this->gateway_account) {
         $this->setError(CBPTXT::T("This payment has no gateway associated so can not be refunded."));
         return false;
     }
     $payAccount = cbpaidControllerPaychoices::getInstance()->getPayAccount($this->gateway_account);
     if (!$payAccount) {
         $this->setError(CBPTXT::T("This payment's payment basket's associated gateway account is not active, so can not be refunded from here."));
         return false;
     }
     $payClass = $payAccount->getPayMean();
     $returnText = null;
     $amount = sprintf('%.2f', (double) $this->refund_gross);
     if (is_callable(array($payClass, 'refundPayment'))) {
         $success = $payClass->refundPayment($paymentBasket, $payment, null, $this->refund_is_last, $amount, $this->refund_reason, $returnText);
     } else {
         $success = false;
     }
     $user = CBuser::getUserDataInstance($paymentBasket->user_id);
     $username = $user ? $user->username : '******';
     $replacements = array('[REFUNDAMOUNT]' => $payment->mc_currency . ' ' . $amount, '[PAYMENTID]' => $payment->id, '[PAYMENTAMOUNT]' => $payment->mc_currency . ' ' . $payment->mc_gross, '[BASKETID]' => $paymentBasket->id, '[ORDERID]' => $paymentBasket->sale_id, '[FULLNAME]' => $paymentBasket->first_name . ' ' . $paymentBasket->last_name, '[USERNAME]' => $username, '[USERID]' => $paymentBasket->user_id, '[PAYMENTMETHOD]' => $payClass->getPayName(), '[TXNID]' => $payment->txn_id, '[AUTHID]' => $payment->auth_id, '[ERRORREASON]' => $paymentBasket->reason_code);
     if ($success) {
         // Success Message ?
         // $returnText	=	CBPTXT::P("Refunded [REFUNDAMOUNT] for payment id [PAYMENTID] of [PAYMENTAMOUNT] for basket id [BASKETID], Order id [ORDERID] of [FULLNAME] (username [USERNAME] - user id [USERID]) using [PAYMENTMETHOD] with txn_id [TXNID] and auth_id [AUTHID].", $replacements );
     } else {
         $this->setError(CBPTXT::T($payClass->getErrorMSG()) . '. ' . CBPTXT::P("Refund request of [REFUNDAMOUNT] for payment id [PAYMENTID] of [PAYMENTAMOUNT] for basket id [BASKETID], Order id [ORDERID] of [FULLNAME] (username [USERNAME] - user id [USERID]) using [PAYMENTMETHOD] with txn_id [TXNID] and auth_id [AUTHID] failed for reason: [ERRORREASON].", $replacements));
         return false;
     }
     return true;
 }
 /**
  * Private utility function for function updatePaymentStatus(): Stores only once the completed payment for a given transaction id of a $notification
  *
  * @param  cbpaidPaymentBasket        $paymentBasket
  * @param  cbpaidPaymentNotification  $notification               notification object of the payment
  * @param  int                        $now
  * @param  boolean|string             $txnIdMultiplePaymentDates  FALSE: unique txn_id for each payment, TRUE: same txn_id can have multiple payment dates, additionally: 'singlepayment' will not look at txn_id at all
  * @param  string                     $updateMessage              Message for the history log for the payment when storing to database
  * @return boolean                                          TRUE: This is really a new payment, FALSE: no, we already received and recorded it (e.g. IPN before PDT)
  */
 private function _storePaymentOnce(&$paymentBasket, &$notification, $now, $txnIdMultiplePaymentDates, $updateMessage)
 {
     global $_CB_database;
     $payment = new cbpaidPayment($_CB_database);
     $whereArray = array('payment_basket_id' => (int) $paymentBasket->id, 'mc_gross' => (string) $notification->mc_gross);
     if ($notification->txn_id !== null && $txnIdMultiplePaymentDates !== 'singlepayment') {
         $whereArray['txn_id'] = (string) $notification->txn_id;
     }
     if ($txnIdMultiplePaymentDates === true) {
         $whereArray['payment_date'] = (string) $notification->payment_date;
     }
     $entry_exists = $payment->loadThisMatching($whereArray, array('id' => 'ASC'));
     $iAmReferencePayment = !$entry_exists || !in_array($payment->payment_status, array('Completed', 'Processed'));
     if ($iAmReferencePayment) {
         // now here we could be at this same place with 2 processes ! if IPN and PDT happen exactly same time (it happens!)
         if ($notification->payment_date) {
             $paymentDate = Application::Database()->getUtcDateTime(cbpaidTimes::getInstance()->gmStrToTime($notification->payment_date));
         } else {
             $paymentDate = $paymentBasket->time_completed;
         }
         $payment->bindPayment($paymentBasket, $notification, $paymentDate, $now);
         $payment->historySetMessage($updateMessage);
         $payment->store();
     }
     if (!$entry_exists && $iAmReferencePayment) {
         // we had to insert a new payment entry: check that it's not duplicate due to simultaneous asynchronous IPN with PDT
         $allPayments = $payment->loadThisMatchingList($whereArray);
         if (count($allPayments) !== 1) {
             if ($payment->id != min(array_keys($allPayments))) {
                 // oops! it really happened: return that we are not the one which should change anything, and delete our duplicate record:
                 $iAmReferencePayment = false;
                 $payment->delete();
             }
             // it happened, but we are the wining payment...
         }
     }
     return $iAmReferencePayment;
 }