CRM-15374 - exclude fee-related financial_trxn rows from balance calculations and...
[civicrm-core.git] / CRM / Contribute / BAO / Contribution.php
index a80ac0fef646db1a92b395f6a6afcd827cc27ea2..fde8e741e67d224f747121fe397a66556b3fa19e 100644 (file)
@@ -1,9 +1,9 @@
 <?php
 /*
  +--------------------------------------------------------------------+
- | CiviCRM version 4.4                                                |
+ | CiviCRM version 4.5                                                |
  +--------------------------------------------------------------------+
- | Copyright CiviCRM LLC (c) 2004-2013                                |
+ | Copyright CiviCRM LLC (c) 2004-2014                                |
  +--------------------------------------------------------------------+
  | This file is a part of CiviCRM.                                    |
  |                                                                    |
@@ -28,7 +28,7 @@
 /**
  *
  * @package CRM
- * @copyright CiviCRM LLC (c) 2004-2013
+ * @copyright CiviCRM LLC (c) 2004-2014
  * $Id$
  *
  */
@@ -69,6 +69,15 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution {
   /*
    * construct method
    */
+  /**
+   * class constructor
+   *
+   * @access public
+   * @return \CRM_Contribute_DAO_Contribution
+   */
+  /**
+   *
+   */
   function __construct() {
     parent::__construct();
   }
@@ -115,7 +124,7 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution {
     );
 
     //if priceset is used, no need to cleanup money
-    if (CRM_Utils_Array::value('skipCleanMoney', $params)) {
+    if (!empty($params['skipCleanMoney'])) {
       unset($moneyFields[0]);
     }
 
@@ -126,12 +135,12 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution {
     }
 
     // CRM-13420, set payment instrument to default if payment_instrument_id is empty
-    if (!$contributionID && !CRM_Utils_Array::value('payment_instrument_id', $params)) {
+    if (!$contributionID && empty($params['payment_instrument_id'])) {
       $params['payment_instrument_id'] = key(CRM_Core_OptionGroup::values('payment_instrument',
         FALSE, FALSE, FALSE, 'AND is_default = 1'));
     }
 
-    if (CRM_Utils_Array::value('payment_instrument_id', $params)) {
+    if (!empty($params['payment_instrument_id'])) {
       $paymentInstruments = CRM_Contribute_PseudoConstant::paymentInstrument('name');
       if ($params['payment_instrument_id'] != array_search('Check', $paymentInstruments)) {
         $params['check_number'] = 'null';
@@ -140,9 +149,21 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution {
 
     // contribution status is missing, choose Completed as default status
     // do this for create mode only
-    if (!$contributionID && !CRM_Utils_Array::value('contribution_status_id', $params)) {
+    if (!$contributionID && empty($params['contribution_status_id'])) {
       $params['contribution_status_id'] = CRM_Core_OptionGroup::getValue('contribution_status', 'Completed', 'name');
     }
+    $setPrevContribution = TRUE;
+    // CRM-13964 partial payment
+    if (!empty($params['partial_payment_total']) && !empty($params['partial_amount_pay'])) {
+      $partialAmtTotal = $params['partial_payment_total'];
+      $partialAmtPay = $params['partial_amount_pay'];
+      $params['total_amount'] = $partialAmtTotal;
+      if ($partialAmtPay < $partialAmtTotal) {
+        $params['contribution_status_id'] = CRM_Core_OptionGroup::getValue('contribution_status', 'Partially paid', 'name');
+        $params['is_pay_later'] = 0;
+        $setPrevContribution = FALSE;
+      }
+    }
 
     if ($contributionID) {
       CRM_Utils_Hook::pre('edit', 'Contribution', $contributionID, $params);
@@ -150,7 +171,6 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution {
     else {
       CRM_Utils_Hook::pre('create', 'Contribution', NULL, $params);
     }
-
     $contribution = new CRM_Contribute_BAO_Contribution();
     $contribution->copyValues($params);
 
@@ -161,7 +181,7 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution {
       $contribution->currency = $config->defaultCurrency;
     }
 
-    if ($contributionID) {
+    if ($contributionID && $setPrevContribution) {
       $params['prevContribution'] = self::getValues(array('id' => $contributionID), CRM_Core_DAO::$_nullArray, CRM_Core_DAO::$_nullArray);
     }
 
@@ -218,6 +238,31 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution {
     return NULL;
   }
 
+  /**
+   * Get the number of terms for this contribution for a given membership type
+   * based on querying the line item table and relevant price field values
+   * Note that any one contribution should only be able to have one line item relating to a particular membership
+   * type
+   * @param int $membershipTypeID
+   *
+   * @return int
+   */
+  public function getNumTermsByContributionAndMembershipType($membershipTypeID) {
+    if (!is_numeric($membershipTypeID)) {
+      //precautionary measure - this is being introduced to a mature release hence adding extra checks that
+      // might be removed later
+      return 1;
+    }
+    $numTerms = CRM_Core_DAO::singleValueQuery("
+      SELECT membership_num_terms FROM civicrm_line_item li
+      LEFT JOIN civicrm_price_field_value v ON li.price_field_value_id = v.id
+      WHERE contribution_id = %1 AND membership_type_id = %2",
+      array(1 => array($this->id, 'Integer') , 2 => array($membershipTypeID, 'Integer'))
+    );
+    // default of 1 is precautionary
+    return empty($numTerms) ? 1 : $numTerms;
+  }
+
   /**
    * takes an associative array and creates a contribution object
    *
@@ -228,7 +273,7 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution {
    * @access public
    * @static
    */
-  static function &create(&$params, $ids = array()) {
+  static function create(&$params, $ids = array()) {
     $dateFields = array('receive_date', 'cancel_date', 'receipt_date', 'thankyou_date');
     foreach ($dateFields as $df) {
       if (isset($params[$df])) {
@@ -247,7 +292,7 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution {
 
     $params['contribution_id'] = $contribution->id;
 
-    if (CRM_Utils_Array::value('custom', $params) &&
+    if (!empty($params['custom']) &&
       is_array($params['custom'])
     ) {
       CRM_Core_BAO_CustomValueTable::store($params['custom'], 'civicrm_contribution', $contribution->id);
@@ -255,7 +300,7 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution {
 
     $session = CRM_Core_Session::singleton();
 
-    if (CRM_Utils_Array::value('note', $params)) {
+    if (!empty($params['note'])) {
       $noteParams = array(
         'entity_table' => 'civicrm_contribution',
         'note' => $params['note'],
@@ -270,7 +315,7 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution {
     }
 
     // make entry in batch entity batch table
-    if (CRM_Utils_Array::value('batch_id', $params)) {
+    if (!empty($params['batch_id'])) {
       // in some update cases we need to get extra fields - ie an update that doesn't pass in all these params
       $titleFields = array(
         'contact_id',
@@ -307,12 +352,15 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution {
     }
 
     // Handle soft credit and / or link to personal campaign page
-    list($type, $softIDs) = CRM_Contribute_BAO_ContributionSoft::getSoftCreditType($contribution->id);
+    $softIDs = CRM_Contribute_BAO_ContributionSoft::getSoftCreditIds($contribution->id);
+
+    //Delete PCP against this contribution and create new on submitted PCP information
+    $pcpId = CRM_Contribute_BAO_ContributionSoft::getSoftCreditIds($contribution->id, TRUE);
+    if ($pcpId) {
+      $deleteParams = array('id' => $pcpId);
+      CRM_Contribute_BAO_ContributionSoft::del($deleteParams);
+    }
     if ($pcp = CRM_Utils_Array::value('pcp', $params)) {
-      if (!empty($type) && $type == 'soft') {
-        $deleteParams = array('contribution_id' => $contribution->id);
-        CRM_Contribute_BAO_ContributionSoft::del($deleteParams);
-      }
       $softParams = array();
       $softParams['contribution_id'] = $contribution->id;
       $softParams['pcp_id'] = $pcp['pcp_made_through_id'];
@@ -324,9 +372,10 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution {
       $softParams['pcp_display_in_roll'] = CRM_Utils_Array::value('pcp_display_in_roll', $pcp);
       $softParams['pcp_roll_nickname'] = CRM_Utils_Array::value('pcp_roll_nickname', $pcp);
       $softParams['pcp_personal_note'] = CRM_Utils_Array::value('pcp_personal_note', $pcp);
+      $softParams['soft_credit_type_id'] = CRM_Core_OptionGroup::getValue('soft_credit_type', 'pcp', 'name');
       CRM_Contribute_BAO_ContributionSoft::add($softParams);
     }
-    elseif (CRM_Utils_Array::value('soft_credit', $params)) {
+    if (isset($params['soft_credit'])) {
       $softParams = $params['soft_credit'];
 
       if (!empty($softIDs)) {
@@ -341,10 +390,10 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution {
       foreach ($softParams as $softParam) {
         $softParam['contribution_id'] = $contribution->id;
         $softParam['currency'] = $contribution->currency;
-        $softParam['pcp_id'] = 'null';
-        $softParam['pcp_display_in_roll'] = 'null';
-        $softParam['pcp_roll_nickname'] = 'null';
-        $softParam['pcp_personal_note'] = 'NULL';
+        //case during Contribution Import when we assign soft contribution amount as contribution's total_amount by default
+        if (empty($softParam['amount'])) {
+          $softParam['amount'] = $contribution->total_amount;
+        }
         CRM_Contribute_BAO_ContributionSoft::add($softParam);
       }
     }
@@ -352,7 +401,7 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution {
     $transaction->commit();
 
     // do not add to recent items for import, CRM-4399
-    if (!CRM_Utils_Array::value('skipRecentView', $params)) {
+    if (empty($params['skipRecentView'])) {
       $url = CRM_Utils_System::url('civicrm/contact/view/contribution',
         "action=view&reset=1&id={$contribution->id}&cid={$contribution->contact_id}&context=home"
       );
@@ -476,6 +525,9 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution {
    * scheme. Adding weight is super important and should be done in the
    * next week or so, before this can be called complete.
    *
+   * @param string $contactType
+   * @param bool $status
+   *
    * @return array array of importable Fields
    * @access public
    * @static
@@ -543,6 +595,9 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution {
     return self::$_importableFields;
   }
 
+  /**
+   * @return array
+   */
   static function &exportableFields() {
     if (!self::$_exportableFields) {
       if (!self::$_exportableFields) {
@@ -586,8 +641,35 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution {
         )
       );
 
+      $softCreditFields = array(
+        'contribution_soft_credit_name' => array(
+          'name' => 'contribution_soft_credit_name',
+          'title' => 'Soft Credit For',
+          'where' => 'civicrm_contact_d.display_name',
+          'data_type' => CRM_Utils_Type::T_STRING
+        ),
+        'contribution_soft_credit_amount' => array(
+          'name' => 'contribution_soft_credit_amount',
+          'title' => 'Soft Credit Amount',
+          'where' => 'civicrm_contribution_soft.amount',
+          'data_type' => CRM_Utils_Type::T_MONEY
+        ),
+        'contribution_soft_credit_type' => array(
+          'name' => 'contribution_soft_credit_type',
+          'title' => 'Soft Credit Type',
+          'where' => 'contribution_softcredit_type.label',
+          'data_type' => CRM_Utils_Type::T_STRING
+        ),
+        'contribution_soft_credit_contribution_id' => array(
+          'name' => 'contribution_soft_credit_contribution_id',
+          'title' => 'Soft Credit For Contribution ID',
+          'where' => 'civicrm_contribution_soft.contribution_id',
+          'data_type' => CRM_Utils_Type::T_INT
+        ),
+      );
+
       $fields = array_merge($impFields, $typeField, $contributionStatus, $optionField, $expFieldProduct,
-        $expFieldsContrib, $contributionNote, $contributionRecurId, $extraFields, $financialAccount,
+        $expFieldsContrib, $contributionNote, $contributionRecurId, $extraFields, $softCreditFields, $financialAccount,
         CRM_Core_BAO_CustomField::getFieldsForImport('Contribution')
       );
 
@@ -597,6 +679,13 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution {
     return self::$_exportableFields;
   }
 
+  /**
+   * @param null $status
+   * @param null $startDate
+   * @param null $endDate
+   *
+   * @return array|null
+   */
   static function getTotalAmountAndCount($status = NULL, $startDate = NULL, $endDate = NULL) {
     $where = array();
     switch ($status) {
@@ -648,8 +737,9 @@ INNER JOIN  civicrm_contact contact ON ( contact.id = civicrm_contribution.conta
   /**
    * Delete the indirect records associated with this contribution first
    *
-   * @return $results no of deleted Contribution on success, false otherwise
-   * @access public
+   * @param $id
+   *
+   * @return mixed|null $results no of deleted Contribution on success, false otherwise@access public
    * @static
    */
   static function deleteContribution($id) {
@@ -719,9 +809,12 @@ INNER JOIN  civicrm_contact contact ON ( contact.id = civicrm_contribution.conta
   /**
    * Check if there is a contribution with the same trxn_id or invoice_id
    *
-   * @param array  $params (reference ) an assoc array of name/value pairs
-   * @param array  $duplicates (reference ) store ids of duplicate contribs
+   * @param $input
+   * @param array $duplicates (reference ) store ids of duplicate contribs
    *
+   * @param null $id
+   *
+   * @internal param array $params (reference ) an assoc array of name/value pairs
    * @return boolean true if duplicate, false otherwise
    * @access public
    * static
@@ -827,50 +920,26 @@ INNER JOIN  civicrm_contact contact ON ( contact.id = civicrm_contribution.conta
    */
   static function getSpecialContributionFields() {
     $extraFields = array(
-      'honor_contact_name' => array(
-        'name' => 'honor_contact_name',
-        'title' => 'Honor Contact Name',
-        'headerPattern' => '/^honor_contact_name$/i',
-        'where' => 'civicrm_contact_c.display_name',
-      ),
-      'honor_contact_email' => array(
-        'name' => 'honor_contact_email',
-        'title' => 'Honor Contact Email',
-        'headerPattern' => '/^honor_contact_email$/i',
-        'where' => 'honor_email.email',
-      ),
-      'honor_contact_id' => array(
-        'name' => 'honor_contact_id',
-        'title' => 'Honor Contact ID',
-        'headerPattern' => '/^honor_contact_id$/i',
-        'where' => 'civicrm_contribution.honor_contact_id',
-      ),
-      'honor_type_label' => array(
-        'name' => 'honor_type_label',
-        'title' => 'Honor Type Label',
-        'headerPattern' => '/^honor_type_label$/i',
-        'where' => 'honor_type.label',
-      ),
-      'soft_credit_name' => array(
-        'name' => 'soft_credit_name',
+      'contribution_soft_credit_name' => array(
+        'name' => 'contribution_soft_credit_name',
         'title' => 'Soft Credit Name',
         'headerPattern' => '/^soft_credit_name$/i',
         'where' => 'civicrm_contact_d.display_name',
       ),
-      'soft_credit_email' => array(
-        'name' => 'soft_credit_email',
+      'contribution_soft_credit_email' => array(
+        'name' => 'contribution_soft_credit_email',
         'title' => 'Soft Credit Email',
         'headerPattern' => '/^soft_credit_email$/i',
         'where' => 'soft_email.email',
       ),
-      'soft_credit_phone' => array(
-        'name' => 'soft_credit_phone',
+      'contribution_soft_credit_phone' => array(
+        'name' => 'contribution_soft_credit_phone',
         'title' => 'Soft Credit Phone',
         'headerPattern' => '/^soft_credit_phone$/i',
         'where' => 'soft_phone.phone',
       ),
-      'soft_credit_contact_id' => array(
-        'name' => 'soft_credit_contact_id',
+      'contribution_soft_credit_contact_id' => array(
+        'name' => 'contribution_soft_credit_contact_id',
         'title' => 'Soft Credit Contact ID',
         'headerPattern' => '/^soft_credit_contact_id$/i',
         'where' => 'civicrm_contribution_soft.contact_id',
@@ -880,6 +949,11 @@ INNER JOIN  civicrm_contact contact ON ( contact.id = civicrm_contribution.conta
     return $extraFields;
   }
 
+  /**
+   * @param $pageID
+   *
+   * @return array
+   */
   static function getCurrentandGoalAmount($pageID) {
     $query = "
 SELECT p.goal_amount as goal, sum( c.total_amount ) as total
@@ -903,44 +977,6 @@ GROUP BY p.id
     }
   }
 
-  /**
-   * Function to create is honor of
-   *
-   * @param array $params  associated array of fields (by reference)
-   * @param int   $honorId honor Id
-   * @param array $honorParams any params that should be send to the create function
-   *
-   * @return contact id
-   */
-  static function createHonorContact(&$params, $honorId = NULL, $honorParams = array()) {
-    $honorParams = array_merge(
-      array(
-        'first_name' => $params['honor_first_name'],
-        'last_name' => $params['honor_last_name'],
-        'prefix_id' => $params['honor_prefix_id'],
-        'email-Primary' => $params['honor_email'],
-      ),
-      $honorParams
-    );
-    if (!$honorId) {
-      $honorParams['email'] = $params['honor_email'];
-
-      $dedupeParams = CRM_Dedupe_Finder::formatParams($honorParams, 'Individual');
-      $dedupeParams['check_permission'] = FALSE;
-      $ids = CRM_Dedupe_Finder::dupesByParams($dedupeParams, 'Individual');
-
-      // if we find more than one contact, use the first one
-      $honorId = CRM_Utils_Array::value(0, $ids);
-    }
-
-    $contactID = CRM_Contact_BAO_Contact::createProfileContact(
-      $honorParams,
-      CRM_Core_DAO::$_nullArray,
-      $honorId
-    );
-    return $contactID;
-  }
-
   /**
    * Function to get list of contribution In Honor of contact Ids
    *
@@ -953,22 +989,27 @@ GROUP BY p.id
    */
   static function getHonorContacts($honorId) {
     $params = array();
-    $honorDAO = new CRM_Contribute_DAO_Contribution();
-    $honorDAO->honor_contact_id = $honorId;
+    $honorDAO = new CRM_Contribute_DAO_ContributionSoft();
+    $honorDAO->contact_id = $honorId;
     $honorDAO->find();
 
-    $status = CRM_Contribute_PseudoConstant::contributionStatus($honorDAO->contribution_status_id);
     $type = CRM_Contribute_PseudoConstant::financialType();
 
     while ($honorDAO->fetch()) {
-      $params[$honorDAO->id]['honorId'] = $honorDAO->contact_id;
-      $params[$honorDAO->id]['display_name'] = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $honorDAO->contact_id, 'display_name');
-      $params[$honorDAO->id]['type'] = $type[$honorDAO->financial_type_id];
-      $params[$honorDAO->id]['type_id'] = $honorDAO->financial_type_id;
-      $params[$honorDAO->id]['amount'] = CRM_Utils_Money::format($honorDAO->total_amount, $honorDAO->currency);
-      $params[$honorDAO->id]['source'] = $honorDAO->source;
-      $params[$honorDAO->id]['receive_date'] = $honorDAO->receive_date;
-      $params[$honorDAO->id]['contribution_status'] = CRM_Utils_Array::value($honorDAO->contribution_status_id, $status);
+      $contributionDAO = new CRM_Contribute_DAO_Contribution();
+      $contributionDAO->id = $honorDAO->contribution_id;
+
+      if ($contributionDAO->find(TRUE)) {
+        $params[$contributionDAO->id]['honor_type'] = CRM_Core_OptionGroup::getLabel('soft_credit_type', $honorDAO->soft_credit_type_id, 'value');
+        $params[$contributionDAO->id]['honorId'] = $contributionDAO->contact_id;
+        $params[$contributionDAO->id]['display_name'] = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $contributionDAO->contact_id, 'display_name');
+        $params[$contributionDAO->id]['type'] = $type[$contributionDAO->financial_type_id];
+        $params[$contributionDAO->id]['type_id'] = $contributionDAO->financial_type_id;
+        $params[$contributionDAO->id]['amount'] = CRM_Utils_Money::format($contributionDAO->total_amount, $contributionDAO->currency);
+        $params[$contributionDAO->id]['source'] = $contributionDAO->source;
+        $params[$contributionDAO->id]['receive_date'] = $contributionDAO->receive_date;
+        $params[$contributionDAO->id]['contribution_status'] = CRM_Contribute_PseudoConstant::contributionStatus($contributionDAO->contribution_status_id);
+      }
     }
 
     return $params;
@@ -995,6 +1036,11 @@ WHERE  civicrm_contribution.contact_id = civicrm_contact.id
     return CRM_Core_DAO::singleValueQuery($query, CRM_Core_DAO::$_nullArray);
   }
 
+  /**
+   * @param $contactID
+   *
+   * @return array
+   */
   static function annual($contactID) {
     if (is_array($contactID)) {
       $contactIDs = implode(',', $contactID);
@@ -1165,11 +1211,14 @@ LEFT JOIN civicrm_option_value contribution_status ON (civicrm_contribution.cont
 
   /**
    *  Function to create address associated with contribution record.
-   *  @param array $params an associated array
-   *  @param int   $billingID $billingLocationTypeID
    *
-   *  @return address id
-   *  @static
+   * @param array $params an associated array
+   * @param $billingLocationTypeID
+   *
+   * @internal param int $billingID $billingLocationTypeID
+   *
+   * @return address id
+   * @static
    */
   static function createAddress(&$params, $billingLocationTypeID) {
     $billingFields = array(
@@ -1202,8 +1251,11 @@ LEFT JOIN civicrm_option_value contribution_status ON (civicrm_contribution.cont
   /**
    * Delete billing address record related contribution
    *
-   * @param int $contact_id contact id
-   * @param int $contribution_id contributionId
+   * @param null $contributionId
+   * @param null $contactId
+   *
+   * @internal param int $contact_id contact id
+   * @internal param int $contribution_id contributionId
    * @access public
    * @static
    */
@@ -1346,7 +1398,7 @@ LEFT JOIN  civicrm_contribution contribution ON ( componentPayment.contribution_
       }
     }
 
-    if (CRM_Utils_Array::value('contact_id', $componentDetails)) {
+    if (!empty($componentDetails['contact_id'])) {
       $componentDetails['contact_id'] = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution',
         $contributionId,
         'contact_id'
@@ -1354,11 +1406,7 @@ LEFT JOIN  civicrm_contribution contribution ON ( componentPayment.contribution_
     }
 
     // do check for required ids.
-    if (!CRM_Utils_Array::value('membership', $componentDetails) &&
-      !CRM_Utils_Array::value('participant', $componentDetails) &&
-      !CRM_Utils_Array::value('pledge_payment', $componentDetails) ||
-      !CRM_Utils_Array::value('contact_id', $componentDetails)
-    ) {
+    if (empty($componentDetails['membership']) && empty($componentDetails['participant']) && empty($componentDetails['pledge_payment']) || empty($componentDetails['contact_id'])) {
       return $updateResult;
     }
 
@@ -1546,7 +1594,9 @@ LEFT JOIN  civicrm_contribution contribution ON ( componentPayment.contribution_
               $dates['end_date'],
               $dates['join_date'],
               'today',
-              TRUE
+              TRUE,
+              $membership->membership_type_id,
+              (array) $membership
             );
 
             $formattedParams = array(
@@ -1622,7 +1672,7 @@ LEFT JOIN  civicrm_contribution contribution ON ( componentPayment.contribution_
         'contribution_status_id', 'non_deductible_amount', 'receipt_date', 'check_number',
       );
       foreach ($fields as $field) {
-        if (!CRM_Utils_Array::value($field, $params)) {
+        if (empty($params[$field])) {
           continue;
         }
         $contributionParams[$field] = $params[$field];
@@ -1691,7 +1741,13 @@ LEFT JOIN  civicrm_contribution contribution ON ( componentPayment.contribution_
     return $componentDetails;
   }
 
-  static function contributionCount($contactId, $includeSoftCredit = TRUE, $includeHonoree = TRUE) {
+  /**
+   * @param $contactId
+   * @param bool $includeSoftCredit
+   *
+   * @return null|string
+   */
+  static function contributionCount($contactId, $includeSoftCredit = TRUE) {
     if (!$contactId) {
       return 0;
     }
@@ -1701,11 +1757,6 @@ LEFT JOIN  civicrm_contribution contribution ON ( componentPayment.contribution_
       FROM civicrm_contribution contribution
       WHERE contribution.is_test = 0 AND contribution.contact_id = {$contactId} ";
 
-    $contactHonoreeContributionsSQL = "
-      SELECT contribution.id
-      FROM civicrm_contribution contribution
-      WHERE contribution.is_test = 0 AND contribution.honor_contact_id = {$contactId} ";
-
     $contactSoftCreditContributionsSQL = "
       SELECT contribution.id
       FROM civicrm_contribution contribution INNER JOIN civicrm_contribution_soft softContribution
@@ -1719,11 +1770,6 @@ LEFT JOIN  civicrm_contribution contribution ON ( componentPayment.contribution_
       $query .= $contactSoftCreditContributionsSQL;
     }
 
-    if ($includeHonoree) {
-      $query .= " UNION ";
-      $query .= $contactHonoreeContributionsSQL;
-    }
-
     $query .= ") x";
 
     return CRM_Core_DAO::singleValueQuery($query);
@@ -1848,6 +1894,15 @@ INNER JOIN civicrm_activity ON civicrm_activity_contact.activity_id = civicrm_ac
    * @param boolean $loadAll - load all related objects - even where id not passed in? (allows API to call this)
    * Note that the unit test for the BaseIPN class tests this function
    */
+  /**
+   * @param $input
+   * @param $ids
+   * @param bool $required
+   * @param bool $loadAll
+   *
+   * @return bool
+   * @throws Exception
+   */
   function loadRelatedObjects(&$input, &$ids, $required = FALSE, $loadAll = false) {
     if($loadAll){
       $ids = array_merge($this->getComponentDetails($this->id),$ids);
@@ -1951,7 +2006,7 @@ WHERE  contribution_id = %1 ";
           );
         }
         //fail to load payment processor id.
-        elseif (!CRM_Utils_Array::value('pledge_payment', $ids)) {
+        elseif (empty($ids['pledge_payment'])) {
           $loadObjectSuccess = TRUE;
           if ($required) {
             throw new Exception("Could not find contribution page for contribution record: " . $this->id);
@@ -2020,6 +2075,15 @@ WHERE  contribution_id = %1 ";
    *   function doing emails / pdfs with it
    * @return array $messageArray - messages
    */
+  /**
+   * @param $input
+   * @param $ids
+   * @param $values
+   * @param bool $recur
+   * @param bool $returnMessageText
+   *
+   * @throws Exception
+   */
   function composeMessageArray(&$input, &$ids, &$values, $recur = FALSE, $returnMessageText = TRUE) {
     if (empty($this->_relatedObjects)) {
       $this->loadRelatedObjects($input, $ids);
@@ -2072,7 +2136,7 @@ WHERE  contribution_id = %1 ";
     }
     else {
       $values['contribution_id'] = $this->id;
-      if (CRM_Utils_Array::value('related_contact', $ids)) {
+      if (!empty($ids['related_contact'])) {
         $values['related_contact'] = $ids['related_contact'];
         if (isset($ids['onbehalf_dupe_alert'])) {
           $values['onbehalf_dupe_alert'] = $ids['onbehalf_dupe_alert'];
@@ -2093,7 +2157,7 @@ WHERE  contribution_id = %1 ";
       if (!empty($this->_relatedObjects['membership'])) {
         foreach ($this->_relatedObjects['membership'] as $membership) {
           if ($membership->id) {
-            $values['membership_id'] = $membership->id;
+            $values['isMembership'] = TRUE;
 
             // need to set the membership values here
             $template->assign('membership_assign', 1);
@@ -2149,6 +2213,13 @@ WHERE  contribution_id = %1 ";
    *
    * NB don't add direct calls to the function as we intend to change the signature
    */
+  /**
+   * @param $input
+   * @param $values
+   * @param array $ids
+   *
+   * @return mixed
+   */
   function _gatherMessageValues($input, &$values, $ids = array()) {
     // set display address of contributor
     if ($this->address_id) {
@@ -2201,7 +2272,7 @@ WHERE  contribution_id = %1 ";
         $this->contact_id
       );
       // if this is onbehalf of contribution then set related contact
-      if (CRM_Utils_Array::value('individual_id', $relatedContact)) {
+      if (!empty($relatedContact['individual_id'])) {
         $values['related_contact'] = $ids['related_contact'] = $relatedContact['individual_id'];
       }
     }
@@ -2256,6 +2327,14 @@ WHERE  contribution_id = %1 ";
    * function & breaking it down into manageable chunks. Eventually it will be refactored into something else
    * Note we send directly from this function in some cases because it is only partly refactored
    * Don't call this function directly as the signature will change
+   *
+   * @param $values
+   * @param $input
+   * @param $template CRM_Core_SMARTY
+   * @param bool $recur
+   * @param bool $returnMessageText
+   *
+   * @return mixed
    */
   function _assignMessageVariablesToTemplate(&$values, $input, &$template, $recur = FALSE, $returnMessageText = True) {
     $template->assign('first_name', $this->_relatedObjects['contact']->first_name);
@@ -2264,28 +2343,35 @@ WHERE  contribution_id = %1 ";
     if (!empty($values['lineItem']) && !empty($this->_relatedObjects['membership'])) {
       $template->assign('useForMember', true);
     }
-    //assign honor infomation to receiptmessage
-    $honorID = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution',
-      $this->id,
-      'honor_contact_id'
-    );
-    if (!empty($honorID)) {
+    //assign honor information to receipt message
+    $softRecord = CRM_Contribute_BAO_ContributionSoft::getSoftContribution($this->id);
 
-      $honorDefault = $honorIds = array();
-      $honorIds['contribution'] = $this->id;
-      $idParams = array('id' => $honorID, 'contact_id' => $honorID);
-      CRM_Contact_BAO_Contact::retrieve($idParams, $honorDefault, $honorIds);
-      $honorType = CRM_Core_PseudoConstant::get('CRM_Contribute_DAO_Contribution', 'honor_type_id');
+    if (isset($softRecord['soft_credit'])) {
+      //if id of contribution page is present
+      if (!empty($values['id'])) {
+        $values['honor'] = array(
+          'honor_profile_values' => array(),
+          'honor_profile_id' => CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFJoin', $values['id'], 'uf_group_id', 'entity_id'),
+          'honor_id' => $softRecord['soft_credit'][1]['contact_id'],
+        );
+        $softCreditTypes = CRM_Core_OptionGroup::values('soft_credit_type');
 
-      $template->assign('honor_block_is_active', 1);
-      if (CRM_Utils_Array::value('prefix_id', $honorDefault)) {
-        $prefix = CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'prefix_id');
-        $template->assign('honor_prefix', $prefix[$honorDefault['prefix_id']]);
+        $template->assign('soft_credit_type',  $softRecord['soft_credit'][1]['soft_credit_type_label']);
+        $template->assign('honor_block_is_active', CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFJoin', $values['id'], 'is_active', 'entity_id'));
+      }
+      else {
+        //offline contribution
+        $softCreditTypes = $softCredits = array();
+        foreach ($softRecord['soft_credit'] as $key => $softCredit) {
+          $softCreditTypes[$key] = $softCredit['soft_credit_type_label'];
+          $softCredits[$key] = array(
+            'Name' => $softCredit['contact_name'],
+            'Amount' => CRM_Utils_Money::format($softCredit['amount'], $softCredit['currency'])
+          );
+        }
+        $template->assign('softCreditTypes', $softCreditTypes);
+        $template->assign('softCredits', $softCredits);
       }
-      $template->assign('honor_first_name', CRM_Utils_Array::value('first_name', $honorDefault));
-      $template->assign('honor_last_name', CRM_Utils_Array::value('last_name', $honorDefault));
-      $template->assign('honor_email', CRM_Utils_Array::value('email', $honorDefault['email'][1]));
-      $template->assign('honor_type', $honorType[$this->honor_type_id]);
     }
 
     $dao = new CRM_Contribute_DAO_ContributionProduct();
@@ -2432,6 +2518,8 @@ WHERE  contribution_id = %1 ";
    *
    * @param int $contributionId contribution id
    *
+   * @param bool $isNotCancelled
+   *
    * @return boolean
    * @access public
    * @static
@@ -2488,11 +2576,15 @@ WHERE  contribution_id = %1 ";
    * @param array $params contribution object, line item array and params for trxn
    *
    *
+   * @param array $financialTrxnValues
+   *
+   * @return null|object
    * @access public
    * @static
    */
-  static function recordFinancialAccounts(&$params) {
-    $skipRecords = $update = FALSE;
+  static function recordFinancialAccounts(&$params, $financialTrxnValues = NULL) {
+    $skipRecords = $update = $return = $isRelatedId = FALSE;
+
     $additionalParticipantId = array();
     $contributionStatuses = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
 
@@ -2501,26 +2593,72 @@ WHERE  contribution_id = %1 ";
       $entityTable = 'civicrm_participant';
       $additionalParticipantId = CRM_Event_BAO_Participant::getAdditionalParticipantIds($entityId);
     }
+    elseif (!empty($params['membership_id'])) {
+      //so far $params['membership_id'] should only be set coming in from membershipBAO::create so the situation where multiple memberships
+      // are created off one contribution should be handled elsewhere
+      $entityId = $params['membership_id'];
+      $entityTable = 'civicrm_membership';
+    }
     else {
       $entityId = $params['contribution']->id;
       $entityTable = 'civicrm_contribution';
     }
 
+    if (CRM_Utils_Array::value('contribution_mode', $params) == 'membership') {
+      $isRelatedId = TRUE;
+    }
+
     $entityID[] = $entityId;
     if (!empty($additionalParticipantId)) {
       $entityID += $additionalParticipantId;
     }
     // prevContribution appears to mean - original contribution object- ie copy of contribution from before the update started that is being updated
-    if (!CRM_Utils_Array::value('prevContribution', $params)) {
+    if (empty($params['prevContribution'])) {
       $entityID = NULL;
     }
     else {
       $update = TRUE;
     }
 
+    $statusId = $params['contribution']->contribution_status_id;
+    // CRM-13964 partial payment
+    if (CRM_Utils_Array::value('contribution_status_id', $params) == array_search('Partially paid', $contributionStatuses)
+      && !empty($params['partial_payment_total']) && !empty($params['partial_amount_pay'])) {
+      $partialAmtPay = $params['partial_amount_pay'];
+      $partialAmtTotal = $params['partial_payment_total'];
+
+      $relationTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Accounts Receivable Account is' "));
+      $fromFinancialAccountId = CRM_Contribute_PseudoConstant::financialAccountType($params['financial_type_id'], $relationTypeId);
+      $statusId = CRM_Core_OptionGroup::getValue('contribution_status', 'Completed', 'name');
+      $params['total_amount'] = $partialAmtPay;
+
+      $balanceTrxnInfo = CRM_Core_BAO_FinancialTrxn::getBalanceTrxnAmt($params['contribution']->id, $params['financial_type_id']);
+      if (empty($balanceTrxnInfo['trxn_id'])) {
+        // create new balance transaction record
+        $relationTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Accounts Receivable Account is' "));
+        $toFinancialAccount = CRM_Contribute_PseudoConstant::financialAccountType($params['financial_type_id'], $relationTypeId);
+
+        $balanceTrxnParams['total_amount'] = $partialAmtTotal;
+        $balanceTrxnParams['to_financial_account_id'] = $toFinancialAccount;
+        $balanceTrxnParams['contribution_id'] = $params['contribution']->id;
+        $balanceTrxnParams['trxn_date'] = date('YmdHis');
+        $balanceTrxnParams['fee_amount'] = CRM_Utils_Array::value('fee_amount', $params);
+        $balanceTrxnParams['net_amount'] = CRM_Utils_Array::value('net_amount', $params);
+        $balanceTrxnParams['currency'] = $params['contribution']->currency;
+        $balanceTrxnParams['trxn_id'] = $params['contribution']->trxn_id;
+        $balanceTrxnParams['status_id'] = $statusId;
+        $balanceTrxnParams['payment_instrument_id'] = $params['contribution']->payment_instrument_id;
+        $balanceTrxnParams['check_number'] = CRM_Utils_Array::value('check_number', $params);
+        if (!empty($params['payment_processor'])) {
+          $balanceTrxnParams['payment_processor_id'] = $params['payment_processor'];
+        }
+        CRM_Core_BAO_FinancialTrxn::create($balanceTrxnParams);
+      }
+    }
+
     // build line item array if its not set in $params
-    if (!CRM_Utils_Array::value('line_item', $params) || $additionalParticipantId) {
-      CRM_Price_BAO_LineItem::getLineItemArray($params, $entityID, str_replace('civicrm_', '', $entityTable));
+    if (empty($params['line_item']) || $additionalParticipantId) {
+      CRM_Price_BAO_LineItem::getLineItemArray($params, $entityID, str_replace('civicrm_', '', $entityTable), $isRelatedId);
     }
 
     if (CRM_Utils_Array::value('contribution_status_id', $params) != array_search('Failed', $contributionStatuses) &&
@@ -2531,10 +2669,10 @@ WHERE  contribution_id = %1 ";
         $relationTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Accounts Receivable Account is' "));
         $params['to_financial_account_id'] = CRM_Contribute_PseudoConstant::financialAccountType($params['financial_type_id'], $relationTypeId);
       }
-      elseif (CRM_Utils_Array::value('payment_processor', $params)) {
+      elseif (!empty($params['payment_processor'])) {
         $params['to_financial_account_id'] = CRM_Financial_BAO_FinancialTypeAccount::getFinancialAccount($params['payment_processor'], 'civicrm_payment_processor', 'financial_account_id');
       }
-      elseif (CRM_Utils_Array::value('payment_instrument_id', $params)) {
+      elseif (!empty($params['payment_instrument_id'])) {
         $params['to_financial_account_id'] = CRM_Financial_BAO_FinancialTypeAccount::getInstrumentFinancialAccount($params['payment_instrument_id']);
       }
       else {
@@ -2544,9 +2682,10 @@ WHERE  contribution_id = %1 ";
       }
 
       $totalAmount = CRM_Utils_Array::value('total_amount', $params);
-      if (!isset($totalAmount) && CRM_Utils_Array::value('prevContribution', $params)) {
+      if (!isset($totalAmount) && !empty($params['prevContribution'])) {
         $totalAmount = $params['total_amount'] = $params['prevContribution']->total_amount;
       }
+
       //build financial transaction params
       $trxnParams = array(
         'contribution_id' => $params['contribution']->id,
@@ -2557,27 +2696,43 @@ WHERE  contribution_id = %1 ";
         'net_amount' => CRM_Utils_Array::value('net_amount', $params),
         'currency' => $params['contribution']->currency,
         'trxn_id' => $params['contribution']->trxn_id,
-        'status_id' => $params['contribution']->contribution_status_id,
+        'status_id' => $statusId,
         'payment_instrument_id' => $params['contribution']->payment_instrument_id,
         'check_number' => CRM_Utils_Array::value('check_number', $params),
       );
 
-      if (CRM_Utils_Array::value('payment_processor', $params)) {
+      if (!empty($params['payment_processor'])) {
         $trxnParams['payment_processor_id'] = $params['payment_processor'];
       }
+
+      if (isset($fromFinancialAccountId)) {
+        $trxnParams['from_financial_account_id'] = $fromFinancialAccountId;
+      }
+
+      // consider external values passed for recording transaction entry
+      if (!empty($financialTrxnValues)) {
+        $trxnParams = array_merge($trxnParams, $financialTrxnValues);
+      }
+
       $params['trxnParams'] = $trxnParams;
 
-      if (CRM_Utils_Array::value('prevContribution', $params)) {
+      if (!empty($params['prevContribution'])) {
         $params['trxnParams']['total_amount'] = $trxnParams['total_amount'] = $params['total_amount'] = $params['prevContribution']->total_amount;
         $params['trxnParams']['fee_amount'] = $params['prevContribution']->fee_amount;
         $params['trxnParams']['net_amount'] = $params['prevContribution']->net_amount;
         $params['trxnParams']['trxn_id'] = $params['prevContribution']->trxn_id;
         $params['trxnParams']['status_id'] = $params['prevContribution']->contribution_status_id;
-        $params['trxnParams']['payment_instrument_id'] = $params['prevContribution']->payment_instrument_id;
-        $params['trxnParams']['check_number'] = $params['prevContribution']->check_number;
+
+
+        if (!(($params['prevContribution']->contribution_status_id == array_search('Pending', $contributionStatuses)
+          || $params['prevContribution']->contribution_status_id == array_search('In Progress', $contributionStatuses))
+          && $params['contribution']->contribution_status_id == array_search('Completed', $contributionStatuses))) {
+          $params['trxnParams']['payment_instrument_id'] = $params['prevContribution']->payment_instrument_id;
+          $params['trxnParams']['check_number'] = $params['prevContribution']->check_number;
+        }
 
         //if financial type is changed
-        if (CRM_Utils_Array::value('financial_type_id', $params) &&
+        if (!empty($params['financial_type_id']) &&
           $params['contribution']->financial_type_id != $params['prevContribution']->financial_type_id) {
           $incomeTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Income Account is' "));
           $oldFinancialAccount = CRM_Contribute_PseudoConstant::financialAccountType($params['prevContribution']->financial_type_id, $incomeTypeId);
@@ -2590,7 +2745,7 @@ WHERE  contribution_id = %1 ";
             }
             else {
               $lastFinancialTrxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($params['prevContribution']->id, 'DESC');
-              if (CRM_Utils_Array::value('financialTrxnId', $lastFinancialTrxnId)) {
+              if (!empty($lastFinancialTrxnId['financialTrxnId'])) {
                 $params['trxnParams']['to_financial_account_id'] = CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_FinancialTrxn', $lastFinancialTrxnId['financialTrxnId'], 'to_financial_account_id');
               }
             }
@@ -2605,7 +2760,7 @@ WHERE  contribution_id = %1 ";
 
         //Update contribution status
         $params['trxnParams']['status_id'] = $params['contribution']->contribution_status_id;
-        if (CRM_Utils_Array::value('contribution_status_id', $params) &&
+        if (!empty($params['contribution_status_id']) &&
           $params['prevContribution']->contribution_status_id != $params['contribution']->contribution_status_id) {
           //Update Financial Records
           self::updateFinancialAccounts($params, 'changedStatus');
@@ -2665,18 +2820,19 @@ WHERE  contribution_id = %1 ";
       }
 
       if (!$update) {
-        //records finanical trxn and entity financial trxn
-        $financialTxn = CRM_Core_BAO_FinancialTrxn::create($trxnParams);
+        // records finanical trxn and entity financial trxn
+        // also make it available as return value
+        $return = $financialTxn = CRM_Core_BAO_FinancialTrxn::create($trxnParams);
         $params['entity_id'] = $financialTxn->id;
       }
     }
     // record line items and finacial items
-    if (!CRM_Utils_Array::value('skipLineItem', $params)) {
+    if (empty($params['skipLineItem'])) {
       CRM_Price_BAO_LineItem::processPriceSet($entityId, CRM_Utils_Array::value('line_item', $params), $params['contribution'], $entityTable, $update);
     }
 
     // create batch entry if batch_id is passed
-    if (CRM_Utils_Array::value('batch_id', $params)) {
+    if (!empty($params['batch_id'])) {
       $entityParams = array(
         'batch_id' => $params['batch_id'],
         'entity_table' => 'civicrm_financial_trxn',
@@ -2686,18 +2842,19 @@ WHERE  contribution_id = %1 ";
     }
 
     // when a fee is charged
-    if (CRM_Utils_Array::value('fee_amount', $params) && (!CRM_Utils_Array::value('prevContribution', $params)
-      || $params['contribution']->fee_amount != $params['prevContribution']->fee_amount) && $skipRecords) {
+    if (!empty($params['fee_amount']) && (empty($params['prevContribution']) || $params['contribution']->fee_amount != $params['prevContribution']->fee_amount) && $skipRecords) {
       CRM_Core_BAO_FinancialTrxn::recordFees($params);
     }
 
-    if (CRM_Utils_Array::value('prevContribution', $params) && $entityTable == 'civicrm_participant'
+    if (!empty($params['prevContribution']) && $entityTable == 'civicrm_participant'
       && $params['prevContribution']->contribution_status_id != $params['contribution']->contribution_status_id) {
       $eventID = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Participant', $entityId, 'event_id');
       $feeLevel[] = str_replace('\ 1', '', $params['prevContribution']->amount_level);
       CRM_Event_BAO_Participant::createDiscountTrxn($eventID, $params, $feeLevel);
     }
     unset($params['line_item']);
+
+    return $return;
   }
 
   /**
@@ -2707,6 +2864,8 @@ WHERE  contribution_id = %1 ";
    *
    * @param string $context update scenarios
    *
+   * @param null $skipTrxn
+   *
    * @access public
    * @static
    */
@@ -2748,7 +2907,7 @@ WHERE  contribution_id = %1 ";
     elseif ($context == 'changePaymentInstrument') {
       if ($params['trxnParams']['total_amount'] < 0) {
         $lastFinancialTrxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($params['prevContribution']->id, 'DESC');
-        if (CRM_Utils_Array::value('financialTrxnId', $lastFinancialTrxnId)) {
+        if (!empty($lastFinancialTrxnId['financialTrxnId'])) {
           $params['trxnParams']['to_financial_account_id'] = CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_FinancialTrxn', $lastFinancialTrxnId['financialTrxnId'], 'to_financial_account_id');
           $params['trxnParams']['payment_instrument_id'] = $params['prevContribution']->payment_instrument_id;
         }
@@ -2807,7 +2966,7 @@ WHERE  contribution_id = %1 ";
           }
 
           $financialAccount = $prevfinancialItem->financial_account_id;
-          if (CRM_Utils_Array::value('financial_account_id', $params)) {
+          if (!empty($params['financial_account_id'])) {
             $financialAccount = $params['financial_account_id'];
           }
 
@@ -2815,7 +2974,7 @@ WHERE  contribution_id = %1 ";
           if ($params['contribution']->currency) {
             $currency = $params['contribution']->currency;
           }
-          if (CRM_Utils_Array::value('is_quick_config', $params)) {
+          if (!empty($params['is_quick_config'])) {
             $amount = $itemAmount;
             if (!$amount) {
               $amount = $params['total_amount'];
@@ -2823,7 +2982,8 @@ WHERE  contribution_id = %1 ";
           }
           else {
             $diff = 1;
-            if ($context == 'changeFinancialType' || $params['contribution']->contribution_status_id == array_search('Cancelled', $contributionStatus)) {
+            if ($context == 'changeFinancialType' || $params['contribution']->contribution_status_id == array_search('Cancelled', $contributionStatus)
+              || $params['contribution']->contribution_status_id == array_search('Refunded', $contributionStatus)) {
              $diff = -1;
             }
             $amount = $diff * $fieldValues['line_total'];
@@ -2855,11 +3015,12 @@ WHERE  contribution_id = %1 ";
    *
    * @param array $errors list of errors
    *
+   * @return bool
    * @access public
    * @static
    */
   static function checkStatusValidation($values, &$fields, &$errors) {
-    if (CRM_Utils_System::isNull($values) && CRM_Utils_Array::value('id', $fields)) {
+    if (CRM_Utils_System::isNull($values) && !empty($fields['id'])) {
       $values['contribution_status_id'] = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $fields['id'], 'contribution_status_id');
       if ($values['contribution_status_id'] == $fields['contribution_status_id']) {
         return FALSE;
@@ -2903,8 +3064,10 @@ WHERE  contribution_id = %1 ";
    * @see CRM_Core_DAO::buildOptions
    *
    * @param String $fieldName
-   * @param String $context: @see CRM_Core_DAO::buildOptionsContext
-   * @param Array $props: whatever is known about this dao object
+   * @param String $context : @see CRM_Core_DAO::buildOptionsContext
+   * @param Array $props : whatever is known about this dao object
+   *
+   * @return Array|bool
    */
   public static function buildOptions($fieldName, $context = NULL, $props = array()) {
     $className = __CLASS__;
@@ -2933,6 +3096,9 @@ WHERE  contribution_id = %1 ";
    *
    * @param integer $financialTypeId Financial Type id
    *
+   * @param string $relationName
+   *
+   * @return array|bool
    * @access public
    * @static
    */
@@ -2945,6 +3111,335 @@ WHERE  contribution_id = %1 ";
     }
     return FALSE;
   }
-}
 
 
+  /*
+   * Function to record additional payment for partial and refund contributions
+   *
+   * @param integer $contributionId : is the invoice contribution id (got created after processing participant payment)
+   * @param array $trxnData : to take user provided input of transaction details.
+   * @param string $paymentType 'owed' for purpose of recording partial payments, 'refund' for purpose of recording refund payments
+   */
+  /**
+   * @param $contributionId
+   * @param $trxnsData
+   * @param string $paymentType
+   * @param null $participantId
+   *
+   * @return null|object
+   */
+  static function recordAdditionalPayment($contributionId, $trxnsData, $paymentType = 'owed', $participantId = NULL) {
+    $statusId = CRM_Core_OptionGroup::getValue('contribution_status', 'Completed', 'name');
+    $getInfoOf['id'] = $contributionId;
+    $defaults = array();
+    $contributionDAO = CRM_Contribute_BAO_Contribution::retrieve($getInfoOf, $defaults, CRM_Core_DAO::$_nullArray);
+
+    if ($paymentType == 'owed') {
+      // build params for recording financial trxn entry
+      $params['contribution'] = $contributionDAO;
+      $params = array_merge($defaults, $params);
+      $params['skipLineItem'] = TRUE;
+      $params['partial_payment_total'] = $contributionDAO->total_amount;
+      $params['partial_amount_pay'] = $trxnsData['total_amount'];
+      $trxnsData['trxn_date'] = !empty($trxnsData['trxn_date']) ? $trxnsData['trxn_date'] : date('YmdHis');
+
+      // record the entry
+      $financialTrxn = CRM_Contribute_BAO_Contribution::recordFinancialAccounts($params, $trxnsData);
+      $relationTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Accounts Receivable Account is' "));
+      $toFinancialAccount = CRM_Contribute_PseudoConstant::financialAccountType($contributionDAO->financial_type_id, $relationTypeId);
+
+      $trxnId = CRM_Core_BAO_FinancialTrxn::getBalanceTrxnAmt($contributionId, $contributionDAO->financial_type_id);
+      if (!empty($trxnId)) {
+        $trxnId = $trxnId['trxn_id'];
+      }
+      elseif (!empty($contributionDAO->payment_instrument_id)) {
+        $trxnId = CRM_Financial_BAO_FinancialTypeAccount::getInstrumentFinancialAccount($contributionDAO->payment_instrument_id);
+      }
+      else {
+        $relationTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('financial_account_type', NULL, " AND v.name LIKE 'Asset' "));
+        $queryParams = array(1 => array($relationTypeId, 'Integer'));
+        $trxnId = CRM_Core_DAO::singleValueQuery("SELECT id FROM civicrm_financial_account WHERE is_default = 1 AND financial_account_type_id = %1", $queryParams);
+      }
+
+      // update statuses
+      // criteria for updates contribution total_amount == financial_trxns of partial_payments
+      $sql = "SELECT SUM(ft.total_amount) as sum_of_payments
+FROM civicrm_financial_trxn ft
+LEFT JOIN civicrm_entity_financial_trxn eft
+  ON (ft.id = eft.financial_trxn_id)
+WHERE eft.entity_table = 'civicrm_contribution'
+  AND eft.entity_id = {$contributionId}
+  AND ft.to_financial_account_id != {$toFinancialAccount}
+  AND ft.status_id = {$statusId}
+";
+      $sumOfPayments = CRM_Core_DAO::singleValueQuery($sql);
+
+      // update statuses
+      if ($contributionDAO->total_amount == $sumOfPayments) {
+        // update contribution status and
+        // clean cancel info (if any) if prev. contribution was updated in case of 'Refunded' => 'Completed'
+        $contributionDAO->contribution_status_id = $statusId;
+        $contributionDAO->cancel_date = 'null';
+        $contributionDAO->cancel_reason = NULL;
+        $netAmount = !empty($trxnsData['net_amount']) ? $trxnsData['net_amount'] : $trxnsData['total_amount'];
+        $contributionDAO->net_amount = $contributionDAO->net_amount + $netAmount;
+        $contributionDAO->save();
+
+        //Change status of financial record too
+        $financialTrxn->status_id = $statusId;
+        $financialTrxn->save();
+
+        // note : not using the self::add method,
+        // the reason because it performs 'status change' related code execution for financial records
+        // which in 'Partial Paid' => 'Completed' is not useful, instead specific financial record updates
+        // are coded below i.e. just updating financial_item status to 'Paid'
+
+        if ($participantId) {
+          // update participant status
+          $participantStatuses = CRM_Event_PseudoConstant::participantStatus();
+          $ids = CRM_Event_BAO_Participant::getParticipantIds($contributionId);
+          foreach ($ids as $val) {
+            $participantUpdate['id'] = $val;
+            $participantUpdate['status_id'] = array_search('Registered', $participantStatuses);
+            CRM_Event_BAO_Participant::add($participantUpdate);
+          }
+        }
+
+        // update financial item statuses
+        $financialItemStatus = CRM_Core_PseudoConstant::get('CRM_Financial_DAO_FinancialItem', 'status_id');
+        $paidStatus = array_search('Paid', $financialItemStatus);
+
+        $baseTrxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($contributionId);
+        $sqlFinancialItemUpdate = "
+UPDATE civicrm_financial_item fi
+  LEFT JOIN civicrm_entity_financial_trxn eft
+    ON (eft.entity_id = fi.id AND eft.entity_table = 'civicrm_financial_item')
+SET status_id = {$paidStatus}
+WHERE eft.financial_trxn_id IN ({$trxnId}, {$baseTrxnId['financialTrxnId']})
+";
+        CRM_Core_DAO::executeQuery($sqlFinancialItemUpdate);
+      }
+    }
+    elseif ($paymentType == 'refund') {
+      // build params for recording financial trxn entry
+      $params['contribution'] = $contributionDAO;
+      $params = array_merge($defaults, $params);
+      $params['skipLineItem'] = TRUE;
+      $trxnsData['trxn_date'] = !empty($trxnsData['trxn_date']) ? $trxnsData['trxn_date'] : date('YmdHis');
+      $trxnsData['total_amount'] = - $trxnsData['total_amount'];
+
+      $relationTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Accounts Receivable Account is' "));
+      $trxnsData['from_financial_account_id'] = CRM_Contribute_PseudoConstant::financialAccountType($contributionDAO->financial_type_id, $relationTypeId);
+      $trxnsData['status_id'] = CRM_Core_OptionGroup::getValue('contribution_status', 'Refunded', 'name');
+      // record the entry
+      $financialTrxn = CRM_Contribute_BAO_Contribution::recordFinancialAccounts($params, $trxnsData);
+
+      // note : not using the self::add method,
+      // the reason because it performs 'status change' related code execution for financial records
+      // which in 'Pending Refund' => 'Completed' is not useful, instead specific financial record updates
+      // are coded below i.e. just updating financial_item status to 'Paid'
+      $contributionDetails = CRM_Core_DAO::setFieldValue('CRM_Contribute_BAO_Contribution', $contributionId, 'contribution_status_id', $statusId);
+
+      // add financial item entry
+      $financialItemStatus = CRM_Core_PseudoConstant::get('CRM_Financial_DAO_FinancialItem', 'status_id');
+      $getLine['entity_id'] = $contributionDAO->id;
+      $getLine['entity_table'] = 'civicrm_contribution';
+      $lineItemId = CRM_Price_BAO_LineItem::retrieve($getLine, CRM_Core_DAO::$_nullArray);
+      if (!empty($lineItemId->id)) {
+        $addFinancialEntry = array(
+          'transaction_date' => $financialTrxn->trxn_date,
+          'contact_id' => $contributionDAO->contact_id,
+          'amount' => $financialTrxn->total_amount,
+          'status_id' => array_search('Paid', $financialItemStatus),
+          'entity_id' => $lineItemId->id,
+          'entity_table' => 'civicrm_line_item'
+        );
+        $trxnIds['id'] =  $financialTrxn->id;
+        CRM_Financial_BAO_FinancialItem::create($addFinancialEntry, NULL, $trxnIds);
+      }
+      if ($participantId) {
+        // update participant status
+        $participantStatuses = CRM_Event_PseudoConstant::participantStatus();
+        $ids = CRM_Event_BAO_Participant::getParticipantIds($contributionId);
+        foreach ($ids as $val) {
+          $participantUpdate['id'] = $val;
+          $participantUpdate['status_id'] = array_search('Registered', $participantStatuses);
+          CRM_Event_BAO_Participant::add($participantUpdate);
+        }
+      }
+    }
+
+    // activity creation
+    if (!empty($financialTrxn)) {
+      if ($participantId) {
+        $inputParams['id'] = $participantId;
+        $values = array();
+        $ids = array();
+        $component = 'event';
+        $entityObj = CRM_Event_BAO_Participant::getValues($inputParams, $values, $ids);
+        $entityObj = $entityObj[$participantId];
+      }
+      $activityType = ($paymentType == 'refund') ? 'Refund' : 'Payment';
+
+      self::addActivityForPayment($entityObj, $financialTrxn, $activityType, $component, $contributionId);
+    }
+    return $financialTrxn;
+  }
+
+  /**
+   * @param $entityObj
+   * @param $trxnObj
+   * @param $activityType
+   * @param $component
+   * @param $contributionId
+   *
+   * @throws CRM_Core_Exception
+   */
+  static function addActivityForPayment($entityObj, $trxnObj, $activityType, $component, $contributionId) {
+    if ($component == 'event') {
+      $date = CRM_Utils_Date::isoToMysql($trxnObj->trxn_date);
+      $paymentAmount = CRM_Utils_Money::format($trxnObj->total_amount, $trxnObj->currency);
+      $eventTitle = CRM_Core_DAO::getFieldValue('CRM_Event_BAO_Event', $entityObj->event_id, 'title');
+      $subject = "{$paymentAmount} - Offline {$activityType} for {$eventTitle}";
+      $targetCid = $entityObj->contact_id;
+      // source record id would be the contribution id
+      $srcRecId = $contributionId;
+    }
+
+    // activity params
+    $activityParams = array(
+      'source_contact_id' => $targetCid,
+      'source_record_id' => $srcRecId,
+      'activity_type_id' => CRM_Core_OptionGroup::getValue('activity_type',
+        $activityType,
+        'name'
+      ),
+      'subject' => $subject,
+      'activity_date_time' => $date,
+      'status_id' => CRM_Core_OptionGroup::getValue('activity_status',
+        'Completed',
+        'name'
+      ),
+      'skipRecentView' => TRUE,
+    );
+
+    // create activity with target contacts
+    $session = CRM_Core_Session::singleton();
+    $id = $session->get('userID');
+    if ($id) {
+      $activityParams['source_contact_id'] = $id;
+      $activityParams['target_contact_id'][] = $targetCid;
+    }
+    CRM_Activity_BAO_Activity::create($activityParams);
+  }
+
+  /**
+   * function to get list of payments displayed by Contribute_Page_PaymentInfo
+   *
+   * @param $id
+   * @param $component
+   * @param bool $getTrxnInfo
+   * @param bool $usingLineTotal
+   *
+   * @return mixed
+   */
+  static function getPaymentInfo($id, $component, $getTrxnInfo = FALSE, $usingLineTotal = FALSE) {
+    if ($component == 'event') {
+      $entity = 'participant';
+      $entityTable = 'civicrm_participant';
+      $contributionId = CRM_Core_DAO::getFieldValue('CRM_Event_BAO_ParticipantPayment', $id, 'contribution_id', 'participant_id');
+
+      if (!$contributionId) {
+        if ($primaryParticipantId = CRM_Core_DAO::getFieldValue('CRM_Event_BAO_Participant', $id, 'registered_by_id')) {
+          $contributionId = CRM_Core_DAO::getFieldValue('CRM_Event_BAO_ParticipantPayment', $primaryParticipantId, 'contribution_id', 'participant_id');
+          $id = $primaryParticipantId;
+        }
+      }
+    }
+    $total = CRM_Core_BAO_FinancialTrxn::getBalanceTrxnAmt($contributionId);
+    $baseTrxnId = !empty($total['trxn_id']) ? $total['trxn_id'] : NULL;
+    $isBalance = NULL;
+    if ($baseTrxnId) {
+      $isBalance = TRUE;
+    }
+    else {
+      $baseTrxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($contributionId);
+      $baseTrxnId = $baseTrxnId['financialTrxnId'];
+      $isBalance = FALSE;
+    }
+    if (empty($total) || $usingLineTotal) {
+      // for additional participants
+      if ($entityTable == 'civicrm_participant') {
+       $ids = CRM_Event_BAO_Participant::getParticipantIds($contributionId);
+       $total = 0;
+       foreach ($ids as $val) {
+         $total += CRM_Price_BAO_LineItem::getLineTotal($val, $entityTable);
+       }
+      }
+      else {
+        $total = CRM_Price_BAO_LineItem::getLineTotal($id, $entityTable);
+      }
+    }
+    else {
+      $baseTrxnId = $total['trxn_id'];
+      $total = $total['total_amount'];
+    }
+
+    $paymentBalance = CRM_Core_BAO_FinancialTrxn::getPartialPaymentWithType($id, $entity, FALSE, $total);
+    $contributionIsPayLater = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $contributionId, 'is_pay_later');
+
+    $feeRelationTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Expense Account is' "));
+    $financialTypeId = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $contributionId, 'financial_type_id');
+    $feeFinancialAccount = CRM_Contribute_PseudoConstant::financialAccountType($financialTypeId, $feeRelationTypeId);
+
+    if ($paymentBalance == 0 && $contributionIsPayLater) {
+      $paymentBalance = $total;
+    }
+
+    $info['total'] = $total;
+    $info['paid'] = $total - $paymentBalance;
+    $info['balance'] = $paymentBalance;
+    $info['id'] = $id;
+    $info['component'] = $component;
+    $info['payLater'] = $contributionIsPayLater;
+    $rows = array();
+    if ($getTrxnInfo && $baseTrxnId) {
+      // Need to exclude fee trxn rows so filter out rows where TO FINANCIAL ACCOUNT is expense account
+      $sql = "
+SELECT ft.total_amount, con.financial_type_id, ft.payment_instrument_id, ft.trxn_date, ft.trxn_id, ft.status_id, ft.check_number
+FROM civicrm_contribution con
+  LEFT JOIN civicrm_entity_financial_trxn eft ON (eft.entity_id = con.id AND eft.entity_table = 'civicrm_contribution')
+  LEFT JOIN civicrm_financial_trxn ft ON ft.id = eft.financial_trxn_id AND ft.to_financial_account_id != {$feeFinancialAccount}
+WHERE con.id = {$contributionId}
+";
+
+      // conditioned WHERE clause
+      if ($isBalance) {
+        // if balance trxn exists don't include details of it in transaction info
+        $sql .= " AND ft.id != {$baseTrxnId} ";
+      }
+      $resultDAO = CRM_Core_DAO::executeQuery($sql);
+
+      $statuses = CRM_Contribute_PseudoConstant::contributionStatus();
+      $financialTypes = CRM_Contribute_PseudoConstant::financialType();
+      while($resultDAO->fetch()) {
+        $paidByLabel = CRM_Core_PseudoConstant::getLabel('CRM_Core_BAO_FinancialTrxn', 'payment_instrument_id', $resultDAO->payment_instrument_id);
+        $paidByName = CRM_Core_PseudoConstant::getName('CRM_Core_BAO_FinancialTrxn', 'payment_instrument_id', $resultDAO->payment_instrument_id);
+        $val = array(
+          'total_amount' => $resultDAO->total_amount,
+          'financial_type' => $financialTypes[$resultDAO->financial_type_id],
+          'payment_instrument' => $paidByLabel,
+          'receive_date' => $resultDAO->trxn_date,
+          'trxn_id' => $resultDAO->trxn_id,
+          'status' => $statuses[$resultDAO->status_id],
+        );
+        if ($paidByName == 'Check') {
+          $val['check_number'] = $resultDAO->check_number;
+        }
+        $rows[] = $val;
+      }
+      $info['transaction'] = $rows;
+    }
+    return $info;
+  }
+}