/**
  * Save transaction data.
  *
  * @param array     $transactionData
  *
  * @throws \RuntimeException
  * @throws \InvalidArgumentException
  * @throws \UnexpectedValueException
  *
  * @return Transaction|null
  */
 protected function storeTransaction($transactionData)
 {
     // Get transaction object by transaction ID
     $keys = array('txn_id' => ArrayHelper::getValue($transactionData, 'txn_id'));
     $transaction = new Transaction(JFactory::getDbo());
     $transaction->load($keys);
     // DEBUG DATA
     JDEBUG ? $this->log->add(JText::_($this->textPrefix . '_DEBUG_TRANSACTION_OBJECT'), $this->debugType, $transaction->getProperties()) : null;
     // Check for existed transaction
     // If the current status if completed, stop the payment process.
     if ($transaction->getId() and $transaction->isCompleted()) {
         return null;
     }
     // Add extra data.
     if (array_key_exists('extra_data', $transactionData)) {
         if (!empty($transactionData['extra_data'])) {
             $transaction->addExtraData($transactionData['extra_data']);
         }
         unset($transactionData['extra_data']);
     }
     // IMPORTANT: It must be before ->bind();
     $options = array('old_status' => $transaction->getStatus(), 'new_status' => $transactionData['txn_status']);
     // Create the new transaction record if there is not record.
     // If there is new record, store new data with new status.
     // Example: It has been 'pending' and now is 'completed'.
     // Example2: It has been 'pending' and now is 'failed'.
     $transaction->bind($transactionData);
     // Start database transaction.
     $db = JFactory::getDbo();
     $db->transactionStart();
     try {
         $transactionManager = new TransactionManager($db);
         $transactionManager->setTransaction($transaction);
         $transactionManager->process('com_crowdfunding.payment', $options);
     } catch (Exception $e) {
         $db->transactionRollback();
         $this->log->add(JText::_($this->textPrefix . '_ERROR_TRANSACTION_PROCESS'), $this->errorType, $e->getMessage());
         return null;
     }
     // Commit database transaction.
     $db->transactionCommit();
     return $transaction;
 }
 /**
  * Pre-processor for $transactionManager->process($context, $options)
  *
  * @param   string        $context
  * @param   Transaction   $transaction
  * @param   array         $options
  *
  * @throws  \RuntimeException
  * @throws  \InvalidArgumentException
  * @throws  \UnexpectedValueException
  * @throws  \OutOfBoundsException
  *
  * @return  void
  */
 public function onAfterProcessTransaction($context, Transaction $transaction, array $options = array())
 {
     // Check for allowed context.
     if (!in_array($context, $this->allowedContext, true)) {
         return;
     }
     $completedOrPending = Constants::PAYMENT_STATUS_COMPLETED | Constants::PAYMENT_STATUS_PENDING;
     $canceledOrRefundedOrFialed = Constants::PAYMENT_STATUS_CANCELED | Constants::PAYMENT_STATUS_REFUNDED | Constants::PAYMENT_STATUS_FAILED;
     $statuses = array('completed' => Constants::PAYMENT_STATUS_COMPLETED, 'pending' => Constants::PAYMENT_STATUS_PENDING, 'canceled' => Constants::PAYMENT_STATUS_CANCELED, 'refunded' => Constants::PAYMENT_STATUS_REFUNDED, 'failed' => Constants::PAYMENT_STATUS_FAILED);
     $oldStatus = ArrayHelper::getValue($options, 'old_status');
     $newStatus = ArrayHelper::getValue($options, 'new_status');
     $oldStatusBit = ($oldStatus and array_key_exists($oldStatus, $statuses)) ? $statuses[$oldStatus] : null;
     $newStatusBit = ($newStatus and array_key_exists($newStatus, $statuses)) ? $statuses[$newStatus] : null;
     // Check if it is new record.
     $isNew = false;
     if ($oldStatusBit === null and $newStatusBit !== null) {
         $isNew = true;
     }
     $container = Container::getContainer();
     $containerHelper = new Helper();
     // Add funds when create new transaction record, and it is completed and pending.
     if ($isNew and $transaction->getProjectId() > 0 and ($transaction->isCompleted() or $transaction->isPending())) {
         $project = $containerHelper->fetchProject($container, $transaction->getProjectId());
         $project->addFunds($transaction->getAmount());
         $project->storeFunds();
         if ($transaction->getRewardId()) {
             $reward = $containerHelper->fetchReward($container, $transaction->getRewardId(), $transaction->getProjectId());
             $this->increaseDistributedReward($transaction, $reward);
         }
     } else {
         // If someone change the status from completed/pending to another one, remove funds.
         if ($completedOrPending & $oldStatusBit and $canceledOrRefundedOrFialed & $newStatusBit) {
             $project = $containerHelper->fetchProject($container, $transaction->getProjectId());
             $project->removeFunds($transaction->getAmount());
             $project->storeFunds();
             if ($transaction->getRewardId()) {
                 $reward = $containerHelper->fetchReward($container, $transaction->getRewardId(), $transaction->getProjectId());
                 $this->decreaseDistributedReward($transaction, $reward);
             }
         } elseif ($canceledOrRefundedOrFialed & $oldStatusBit and $completedOrPending & $newStatusBit) {
             $project = $containerHelper->fetchProject($container, $transaction->getProjectId());
             $project->addFunds($transaction->getAmount());
             $project->storeFunds();
             if ($transaction->getRewardId()) {
                 $reward = $containerHelper->fetchReward($container, $transaction->getRewardId(), $transaction->getProjectId());
                 $this->increaseDistributedReward($transaction, $reward);
             }
         }
     }
 }