/** * 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; }