Merge pull request #6254 from yashodha/CRM-16853.fixes
[civicrm-core.git] / CRM / Contribute / Form / Contribution / Confirm.php
index 5e38c9fde013d3c18d3796d8b31bcee8d0b6f178..76ee59cd7869c1dad4c2692341a40afa1d7f2f4a 100644 (file)
@@ -63,10 +63,10 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
    * @param int $campaignId
    * @param bool $isMonetary
    * @param bool $pending
-   * @param $paymentProcessorOutcome
-   * @param $receiptDate
+   * @param array $paymentProcessorOutcome
+   * @param string $receiptDate
    * @param int $recurringContributionID
-   * @param $isTest
+   * @param bool $isTest
    * @param int $addressID
    * @param int $softCreditToID
    * @param array $lineItems
@@ -218,8 +218,8 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
         CRM_Core_DAO::$_nullObject, FALSE, NULL, 'GET'
       );
       if ($rfp) {
-        $payment = CRM_Core_Payment::singleton($this->_mode, $this->_paymentProcessor, $this);
-        $expressParams = $payment->getExpressCheckoutDetails($this->get('token'));
+        $payment = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor);
+        $expressParams = $payment->getPreApprovalDetails($this->get('pre_approval_parameters'));
 
         $this->_params['payer'] = CRM_Utils_Array::value('payer', $expressParams);
         $this->_params['payer_id'] = $expressParams['payer_id'];
@@ -545,8 +545,7 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
       if (isset($params['selectMembership']) &&
         $params['selectMembership'] != 'no_thanks'
       ) {
-        CRM_Member_BAO_Membership::buildMembershipBlock($this,
-          $this->_id,
+        $this->buildMembershipBlock(
           $this->_membershipContactID,
           FALSE,
           $params['selectMembership'],
@@ -696,8 +695,9 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
   }
 
   /**
-   * Overwrite action, since we are only showing elements in frozen mode
-   * no help display needed
+   * Overwrite action.
+   *
+   * Since we are only showing elements in frozen mode no help display needed.
    *
    * @return int
    */
@@ -715,8 +715,6 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
    *
    * Note that in edit/view mode
    * the default values are retrieved from the database
-   *
-   * @return void
    */
   public function setDefaultValues() {
   }
@@ -726,964 +724,596 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
    */
   public function postProcess() {
     $contactID = $this->getContactID();
-    $isPayLater = $this->_params['is_pay_later'];
-    if (isset($this->_params['payment_processor_id']) && $this->_params['payment_processor_id'] == 0) {
-      $this->_params['is_pay_later'] = $isPayLater = TRUE;
+    $result = $this->processFormSubmission($contactID);
+    if (is_array($result) && !empty($result['is_payment_failure'])) {
+      // We will probably have the function that gets this error throw an exception on the next round of refactoring.
+      CRM_Core_Session::singleton()->setStatus(ts("Payment Processor Error message :") .
+          $result['error']->getMessage());
+      CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contribute/transact',
+        "_qf_Main_display=true&qfKey={$this->_params['qfKey']}"
+      ));
     }
-    // add a description field at the very beginning
-    $this->_params['description'] = ts('Online Contribution') . ': ' . (($this->_pcpInfo['title']) ? $this->_pcpInfo['title'] : $this->_values['title']);
-
-    $this->_params['accountingCode'] = CRM_Utils_Array::value('accountingCode', $this->_values);
-
-    // fix currency ID
-    $this->_params['currencyID'] = CRM_Core_Config::singleton()->defaultCurrency;
+    // Presumably this is for hooks to access? Not quite clear & perhaps not required.
+    $this->set('params', $this->_params);
+  }
 
-    //carry payment processor id.
-    if (CRM_Utils_Array::value('id', $this->_paymentProcessor)) {
-      $this->_params['payment_processor_id'] = $this->_paymentProcessor['id'];
+  /**
+   * Wrangle financial type ID.
+   *
+   * This wrangling of the financialType ID was happening in a shared function rather than in the form it relates to & hence has been moved to that form
+   * Pledges are not relevant to the membership code so that portion will not go onto the membership form.
+   *
+   * Comments from previous refactor indicate doubt as to what was going on.
+   *
+   * @param int $contributionTypeId
+   *
+   * @return null|string
+   */
+  public function wrangleFinancialTypeID($contributionTypeId) {
+    if (isset($paymentParams['financial_type'])) {
+      $contributionTypeId = $paymentParams['financial_type'];
     }
-    if (!empty($params['image_URL'])) {
-      CRM_Contact_BAO_Contact::processImageParams($params);
+    elseif (!empty($this->_values['pledge_id'])) {
+      $contributionTypeId = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_Pledge',
+        $this->_values['pledge_id'],
+        'financial_type_id'
+      );
     }
-    $premiumParams = $membershipParams = $params = $this->_params;
-    $fields = array('email-Primary' => 1);
-
-    // get the add to groups
-    $addToGroups = array();
+    return $contributionTypeId;
+  }
 
-    // now set the values for the billing location.
-    foreach ($this->_fields as $name => $value) {
-      $fields[$name] = 1;
+  /**
+   * Process the form.
+   *
+   * @param array $premiumParams
+   * @param CRM_Contribute_BAO_Contribution $contribution
+   */
+  protected function postProcessPremium($premiumParams, $contribution) {
+    $hour = $minute = $second = 0;
+    // assigning Premium information to receipt tpl
+    $selectProduct = CRM_Utils_Array::value('selectProduct', $premiumParams);
+    if ($selectProduct &&
+      $selectProduct != 'no_thanks'
+    ) {
+      $startDate = $endDate = "";
+      $this->assign('selectPremium', TRUE);
+      $productDAO = new CRM_Contribute_DAO_Product();
+      $productDAO->id = $selectProduct;
+      $productDAO->find(TRUE);
+      $this->assign('product_name', $productDAO->name);
+      $this->assign('price', $productDAO->price);
+      $this->assign('sku', $productDAO->sku);
+      $this->assign('option', CRM_Utils_Array::value('options_' . $premiumParams['selectProduct'], $premiumParams));
 
-      // get the add to groups for uf fields
-      if (!empty($value['add_to_group_id'])) {
-        $addToGroups[$value['add_to_group_id']] = $value['add_to_group_id'];
-      }
-    }
+      $periodType = $productDAO->period_type;
 
-    if (!array_key_exists('first_name', $fields)) {
-      $nameFields = array('first_name', 'middle_name', 'last_name');
-      foreach ($nameFields as $name) {
-        $fields[$name] = 1;
-        if (array_key_exists("billing_$name", $params)) {
-          $params[$name] = $params["billing_{$name}"];
-          $params['preserveDBName'] = TRUE;
+      if ($periodType) {
+        $fixed_period_start_day = $productDAO->fixed_period_start_day;
+        $duration_unit = $productDAO->duration_unit;
+        $duration_interval = $productDAO->duration_interval;
+        if ($periodType == 'rolling') {
+          $startDate = date('Y-m-d');
+        }
+        elseif ($periodType == 'fixed') {
+          if ($fixed_period_start_day) {
+            $date = explode('-', date('Y-m-d'));
+            $month = substr($fixed_period_start_day, 0, strlen($fixed_period_start_day) - 2);
+            $day = substr($fixed_period_start_day, -2) . "<br/>";
+            $year = $date[0];
+            $startDate = $year . '-' . $month . '-' . $day;
+          }
+          else {
+            $startDate = date('Y-m-d');
+          }
         }
-      }
-    }
 
-    // billing email address
-    $fields["email-{$this->_bltID}"] = 1;
+        $date = explode('-', $startDate);
+        $year = $date[0];
+        $month = $date[1];
+        $day = $date[2];
 
-    //unset the billing parameters if it is pay later mode
-    //to avoid creation of billing location
-    if ($isPayLater && !$this->_isBillingAddressRequiredForPayLater) {
-      $billingFields = array(
-        'billing_first_name',
-        'billing_middle_name',
-        'billing_last_name',
-        "billing_street_address-{$this->_bltID}",
-        "billing_city-{$this->_bltID}",
-        "billing_state_province-{$this->_bltID}",
-        "billing_state_province_id-{$this->_bltID}",
-        "billing_postal_code-{$this->_bltID}",
-        "billing_country-{$this->_bltID}",
-        "billing_country_id-{$this->_bltID}",
-      );
+        switch ($duration_unit) {
+          case 'year':
+            $year = $year + $duration_interval;
+            break;
 
-      foreach ($billingFields as $value) {
-        unset($params[$value]);
-        unset($fields[$value]);
-      }
-    }
+          case 'month':
+            $month = $month + $duration_interval;
+            break;
 
-    // if onbehalf-of-organization contribution, take out
-    // organization params in a separate variable, to make sure
-    // normal behavior is continued. And use that variable to
-    // process on-behalf-of functionality.
-    if (!empty($this->_params['hidden_onbehalf_profile'])) {
-      $behalfOrganization = array();
-      $orgFields = array('organization_name', 'organization_id', 'org_option');
-      foreach ($orgFields as $fld) {
-        if (array_key_exists($fld, $params)) {
-          $behalfOrganization[$fld] = $params[$fld];
-          unset($params[$fld]);
-        }
-      }
+          case 'day':
+            $day = $day + $duration_interval;
+            break;
 
-      if (is_array($params['onbehalf']) && !empty($params['onbehalf'])) {
-        foreach ($params['onbehalf'] as $fld => $values) {
-          if (strstr($fld, 'custom_')) {
-            $behalfOrganization[$fld] = $values;
-          }
-          elseif (!(strstr($fld, '-'))) {
-            if (in_array($fld, array(
-              'contribution_campaign_id',
-              'member_campaign_id',
-            ))) {
-              $fld = 'campaign_id';
-            }
-            else {
-              $behalfOrganization[$fld] = $values;
-            }
-            $this->_params[$fld] = $values;
-          }
+          case 'week':
+            $day = $day + ($duration_interval * 7);
         }
+        $endDate = date('Y-m-d H:i:s', mktime($hour, $minute, $second, $month, $day, $year));
+        $this->assign('start_date', $startDate);
+        $this->assign('end_date', $endDate);
       }
 
-      if (array_key_exists('onbehalf_location', $params) && is_array($params['onbehalf_location'])) {
-        foreach ($params['onbehalf_location'] as $block => $vals) {
-          //fix for custom data (of type checkbox, multi-select)
-          if (substr($block, 0, 7) == 'custom_') {
-            continue;
-          }
-          // fix the index of block elements
-          if (is_array($vals)) {
-            foreach ($vals as $key => $val) {
-              //dont adjust the index of address block as
-              //it's index is WRT to location type
-              $newKey = ($block == 'address') ? $key : ++$key;
-              $behalfOrganization[$block][$newKey] = $val;
-            }
-          }
-        }
-        unset($params['onbehalf_location']);
+      $dao = new CRM_Contribute_DAO_Premium();
+      $dao->entity_table = 'civicrm_contribution_page';
+      $dao->entity_id = $this->_id;
+      $dao->find(TRUE);
+      $this->assign('contact_phone', $dao->premiums_contact_phone);
+      $this->assign('contact_email', $dao->premiums_contact_email);
+
+      //create Premium record
+      $params = array(
+        'product_id' => $premiumParams['selectProduct'],
+        'contribution_id' => $contribution->id,
+        'product_option' => CRM_Utils_Array::value('options_' . $premiumParams['selectProduct'], $premiumParams),
+        'quantity' => 1,
+        'start_date' => CRM_Utils_Date::customFormat($startDate, '%Y%m%d'),
+        'end_date' => CRM_Utils_Date::customFormat($endDate, '%Y%m%d'),
+      );
+      if (!empty($premiumParams['selectProduct'])) {
+        $daoPremiumsProduct = new CRM_Contribute_DAO_PremiumsProduct();
+        $daoPremiumsProduct->product_id = $premiumParams['selectProduct'];
+        $daoPremiumsProduct->premiums_id = $dao->id;
+        $daoPremiumsProduct->find(TRUE);
+        $params['financial_type_id'] = $daoPremiumsProduct->financial_type_id;
       }
-      if (!empty($params['onbehalf[image_URL]'])) {
-        $behalfOrganization['image_URL'] = $params['onbehalf[image_URL]'];
+      //Fixed For CRM-3901
+      $daoContrProd = new CRM_Contribute_DAO_ContributionProduct();
+      $daoContrProd->contribution_id = $contribution->id;
+      if ($daoContrProd->find(TRUE)) {
+        $params['id'] = $daoContrProd->id;
       }
-    }
-
-    // check for profile double opt-in and get groups to be subscribed
-    $subscribeGroupIds = CRM_Core_BAO_UFGroup::getDoubleOptInGroupIds($params, $contactID);
 
-    // since we are directly adding contact to group lets unset it from mailing
-    if (!empty($addToGroups)) {
-      foreach ($addToGroups as $groupId) {
-        if (isset($subscribeGroupIds[$groupId])) {
-          unset($subscribeGroupIds[$groupId]);
-        }
+      CRM_Contribute_BAO_Contribution::addPremium($params);
+      if ($productDAO->cost && !empty($params['financial_type_id'])) {
+        $trxnParams = array(
+          'cost' => $productDAO->cost,
+          'currency' => $productDAO->currency,
+          'financial_type_id' => $params['financial_type_id'],
+          'contributionId' => $contribution->id,
+        );
+        CRM_Core_BAO_FinancialTrxn::createPremiumTrxn($trxnParams);
       }
     }
-
-    foreach ($addToGroups as $k) {
-      if (array_key_exists($k, $subscribeGroupIds)) {
-        unset($addToGroups[$k]);
+    elseif ($selectProduct == 'no_thanks') {
+      //Fixed For CRM-3901
+      $daoContrProd = new CRM_Contribute_DAO_ContributionProduct();
+      $daoContrProd->contribution_id = $contribution->id;
+      if ($daoContrProd->find(TRUE)) {
+        $daoContrProd->delete();
       }
     }
+  }
 
-    if (empty($contactID)) {
-      $dupeParams = $params;
-      if (!empty($dupeParams['onbehalf'])) {
-        unset($dupeParams['onbehalf']);
-      }
-      if (!empty($dupeParams['honor'])) {
-        unset($dupeParams['honor']);
-      }
-
-      $dedupeParams = CRM_Dedupe_Finder::formatParams($dupeParams, 'Individual');
-      $dedupeParams['check_permission'] = FALSE;
-      $ids = CRM_Dedupe_Finder::dupesByParams($dedupeParams, 'Individual');
-
-      // if we find more than one contact, use the first one
-      $contactID = CRM_Utils_Array::value(0, $ids);
-
-      // Fetch default greeting id's if creating a contact
-      if (!$contactID) {
-        foreach (CRM_Contact_BAO_Contact::$_greetingTypes as $greeting) {
-          if (!isset($params[$greeting])) {
-            $params[$greeting] = CRM_Contact_BAO_Contact_Utils::defaultGreeting('Individual', $greeting);
-          }
-        }
-      }
-      $contactType = NULL;
+  /**
+   * Process the contribution.
+   *
+   * @param CRM_Core_Form $form
+   * @param array $params
+   * @param array $result
+   * @param int $contactID
+   * @param CRM_Financial_DAO_FinancialType $financialType
+   * @param bool $pending
+   * @param bool $online
+   *
+   * @param bool $isTest
+   * @param array $lineItems
+   *
+   * @param int $billingLocationID
+   *   ID of billing location type.
+   *
+   * @return \CRM_Contribute_DAO_Contribution
+   * @throws \Exception
+   */
+  public static function processFormContribution(
+    &$form,
+    $params,
+    $result,
+    $contactID,
+    $financialType,
+    $pending,
+    $online,
+    $isTest,
+    $lineItems,
+    $billingLocationID
+  ) {
+    $transaction = new CRM_Core_Transaction();
+    $contribSoftContactId = $addressID = NULL;
+    $isMonetary = !empty($form->_values['is_monetary']);
+    $isEmailReceipt = !empty($form->_values['is_email_receipt']);
+    // How do these vary from params? These are currently passed to
+    // - custom data function....
+    $formParams = $form->_params;
+    $isSeparateMembershipPayment = empty($formParams['separate_membership_payment']) ? FALSE : TRUE;
+    $pledgeID = empty($formParams['pledge_id']) ? NULL : $formParams['pledge_id'];
+    if (!$isSeparateMembershipPayment && !empty($form->_values['pledge_block_id']) &&
+      (!empty($formParams['is_pledge']) || $pledgeID)) {
+      $isPledge = TRUE;
     }
     else {
-      $contactType = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $contactID, 'contact_type');
+      $isPledge = FALSE;
     }
-    $contactID = CRM_Contact_BAO_Contact::createProfileContact(
-      $params,
-      $fields,
-      $contactID,
-      $addToGroups,
-      NULL,
-      $contactType,
-      TRUE
-    );
 
-    // Make the contact ID associated with the contribution available at the Class level.
-    // Also make available to the session.
-    //@todo consider handling this in $this->getContactID();
-    $this->set('contactID', $contactID);
-    $this->_contactID = $contactID;
+    // add these values for the recurringContrib function ,CRM-10188
+    $params['financial_type_id'] = $financialType->id;
 
-    //get email primary first if exist
-    $subscriptionEmail = array('email' => CRM_Utils_Array::value('email-Primary', $params));
-    if (!$subscriptionEmail['email']) {
-      $subscriptionEmail['email'] = CRM_Utils_Array::value("email-{$this->_bltID}", $params);
-    }
-    // subscribing contact to groups
-    if (!empty($subscribeGroupIds) && $subscriptionEmail['email']) {
-      CRM_Mailing_Event_BAO_Subscribe::commonSubscribe($subscribeGroupIds, $subscriptionEmail, $contactID);
-    }
+    $addressID = CRM_Contribute_BAO_Contribution::createAddress($params, $billingLocationID);
 
-    // If onbehalf-of-organization contribution / signup, add organization
-    // and it's location.
-    if (isset($params['hidden_onbehalf_profile']) && isset($behalfOrganization['organization_name'])) {
-      $ufFields = array();
-      foreach ($this->_fields['onbehalf'] as $name => $value) {
-        $ufFields[$name] = 1;
-      }
-      self::processOnBehalfOrganization($behalfOrganization, $contactID, $this->_values,
-        $this->_params, $ufFields
-      );
-    }
-    elseif (!empty($this->_membershipContactID) && $contactID != $this->_membershipContactID) {
-      // this is an onbehalf renew case for inherited membership. For e.g a permissioned member of household,
-      // store current user id as related contact for later use for mailing / activity..
-      $this->_values['related_contact'] = $contactID;
-      $this->_params['related_contact'] = $contactID;
-      // swap contact like we do for on-behalf-org case, so parent/primary membership is affected
-      $contactID = $this->_membershipContactID;
+    //@todo - this is being set from the form to resolve CRM-10188 - an
+    // eNotice caused by it not being set @ the front end
+    // however, we then get it being over-written with null for backend contributions
+    // a better fix would be to set the values in the respective forms rather than require
+    // a function being shared by two forms to deal with their respective values
+    // moving it to the BAO & not taking the $form as a param would make sense here.
+    if (!isset($params['is_email_receipt']) && $isEmailReceipt) {
+      $params['is_email_receipt'] = $isEmailReceipt;
     }
+    $recurringContributionID = self::processRecurringContribution($form, $params, $contactID, $financialType, $online);
+    $nonDeductibleAmount = self::getNonDeductibleAmount($params, $financialType, $online);
 
-    // lets store the contactID in the session
-    // for things like tell a friend
-    $session = CRM_Core_Session::singleton();
-    if (!$session->get('userID')) {
-      $session->set('transaction.userID', $contactID);
-    }
-    else {
-      $session->set('transaction.userID', NULL);
+    $now = date('YmdHis');
+    $receiptDate = CRM_Utils_Array::value('receipt_date', $params);
+    if ($isEmailReceipt) {
+      $receiptDate = $now;
     }
 
-    $this->_useForMember = $this->get('useForMember');
-
-    // store the fact that this is a membership and membership type is selected
-    $processMembership = FALSE;
-    if ((!empty($membershipParams['selectMembership']) &&
-        $membershipParams['selectMembership'] != 'no_thanks'
-      ) ||
-      $this->_useForMember
-    ) {
-      $processMembership = TRUE;
-
-      if (!$this->_useForMember) {
-        $this->assign('membership_assign', TRUE);
-        $this->set('membershipTypeID', $this->_params['selectMembership']);
-      }
-
-      if ($this->_action & CRM_Core_Action::PREVIEW) {
-        $membershipParams['is_test'] = 1;
-      }
-      if ($this->_params['is_pay_later']) {
-        $membershipParams['is_pay_later'] = 1;
+    //get the contrib page id.
+    $contributionPageId = NULL;
+    if ($online) {
+      $contributionPageId = $form->_id;
+      $campaignId = CRM_Utils_Array::value('campaign_id', $params);
+      if (!array_key_exists('campaign_id', $params)) {
+        $campaignId = CRM_Utils_Array::value('campaign_id', $form->_values);
       }
     }
+    else {
+      //also for offline we do support - CRM-7290
+      $contributionPageId = CRM_Utils_Array::value('contribution_page_id', $params);
+      $campaignId = CRM_Utils_Array::value('campaign_id', $params);
+    }
 
-    if ($processMembership) {
-      CRM_Core_Payment_Form::mapParams($this->_bltID, $this->_params, $membershipParams, TRUE);
-
-      // added new parameter for cms user contact id, needed to distinguish behaviour for on behalf of sign-ups
-      if (isset($this->_params['related_contact'])) {
-        $membershipParams['cms_contactID'] = $this->_params['related_contact'];
+    // Prepare soft contribution due to pcp or Submit Credit / Debit Card Contribution by admin.
+    if (!empty($params['pcp_made_through_id']) || !empty($params['soft_credit_to'])) {
+      // if its due to pcp
+      if (!empty($params['pcp_made_through_id'])) {
+        $contribSoftContactId = CRM_Core_DAO::getFieldValue(
+          'CRM_PCP_DAO_PCP',
+          $params['pcp_made_through_id'],
+          'contact_id'
+        );
       }
       else {
-        $membershipParams['cms_contactID'] = $contactID;
+        $contribSoftContactId = CRM_Utils_Array::value('soft_credit_to', $params);
       }
 
-      //inherit campaign from contribution page.
-      if (!array_key_exists('campaign_id', $membershipParams)) {
-        $membershipParams['campaign_id'] = CRM_Utils_Array::value('campaign_id', $this->_values);
-      }
+      // Pass these details onto with the contribution to make them
+      // available at hook_post_process, CRM-8908
+      $params['soft_credit_to'] = $contribSoftContactId;
+    }
 
-      if (!empty($membershipParams['onbehalf']) &&
-        is_array($membershipParams['onbehalf']) && !empty($membershipParams['onbehalf']['member_campaign_id'])
-      ) {
-        $this->_params['campaign_id'] = $membershipParams['onbehalf']['member_campaign_id'];
-      }
+    if (isset($params['amount'])) {
+      $contribParams = self::getContributionParams(
+        $params, $contactID, $financialType->id, $online, $contributionPageId, $nonDeductibleAmount, $campaignId, $isMonetary, $pending, $result, $receiptDate,
+        $recurringContributionID, $isTest, $addressID, $contribSoftContactId, $lineItems
+      );
+      $contribution = CRM_Contribute_BAO_Contribution::add($contribParams);
 
-      $customFieldsFormatted = $fieldTypes = array();
-      if (!empty($membershipParams['onbehalf']) &&
-        is_array($membershipParams['onbehalf'])
-      ) {
-        foreach ($membershipParams['onbehalf'] as $key => $value) {
-          if (strstr($key, 'custom_')) {
-            $customFieldId = explode('_', $key);
-            CRM_Core_BAO_CustomField::formatCustomField(
-              $customFieldId[1],
-              $customFieldsFormatted,
-              $value,
-              'Membership',
-              NULL,
-              $contactID
-            );
-          }
-        }
-        $fieldTypes = array('Contact', 'Organization', 'Membership');
-      }
-
-      $priceFieldIds = $this->get('memberPriceFieldIDS');
-
-      if (!empty($priceFieldIds)) {
-        $contributionTypeID = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $priceFieldIds['id'], 'financial_type_id');
-        unset($priceFieldIds['id']);
-        $membershipTypeIds = array();
-        $membershipTypeTerms = array();
-        foreach ($priceFieldIds as $priceFieldId) {
-          if ($id = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', $priceFieldId, 'membership_type_id')) {
-            $membershipTypeIds[] = $id;
-            //@todo the value for $term is immediately overwritten. It is unclear from the code whether it was intentional to
-            // do this or a double = was intended (this ambiguity is the reason many IDEs complain about 'assignment in condition'
-            $term = 1;
-            if ($term = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', $priceFieldId, 'membership_num_terms')) {
-              $membershipTypeTerms[$id] = ($term > 1) ? $term : 1;
-            }
-            else {
-              $membershipTypeTerms[$id] = 1;
-            }
-          }
-        }
-        $membershipParams['selectMembership'] = $membershipTypeIds;
-        $membershipParams['financial_type_id'] = $contributionTypeID;
-        $membershipParams['types_terms'] = $membershipTypeTerms;
-      }
-      if (!empty($membershipParams['selectMembership'])) {
-        // CRM-12233
-        $membershipLineItems = array();
-        if ($this->_separateMembershipPayment && $this->_values['amount_block_is_active']) {
-          foreach ($this->_values['fee'] as $key => $feeValues) {
-            if ($feeValues['name'] == 'membership_amount') {
-              $fieldId = $this->_params['price_' . $key];
-              $membershipLineItems[$this->_priceSetId][$fieldId] = $this->_lineItem[$this->_priceSetId][$fieldId];
-              unset($this->_lineItem[$this->_priceSetId][$fieldId]);
-              break;
+      $invoiceSettings = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME, 'contribution_invoice_settings');
+      $invoicing = CRM_Utils_Array::value('invoicing', $invoiceSettings);
+      if ($invoicing) {
+        $dataArray = array();
+        foreach ($form->_lineItem as $lineItemKey => $lineItemValue) {
+          foreach ($lineItemValue as $key => $value) {
+            if (isset($value['tax_amount']) && isset($value['tax_rate'])) {
+              if (isset($dataArray[$value['tax_rate']])) {
+                $dataArray[$value['tax_rate']] = $dataArray[$value['tax_rate']] + CRM_Utils_Array::value('tax_amount', $value);
+              }
+              else {
+                $dataArray[$value['tax_rate']] = CRM_Utils_Array::value('tax_amount', $value);
+              }
             }
           }
         }
-        $this->processMembership($membershipParams, $contactID, $customFieldsFormatted, $fieldTypes, $premiumParams, $membershipLineItems, $isPayLater);
-        if (!$this->_amount > 0.0 || !$membershipParams['amount']) {
-          // we need to explicitly create a CMS user in case of free memberships
-          // since it is done under processConfirm for paid memberships
-          CRM_Contribute_BAO_Contribution_Utils::createCMSUser($membershipParams,
-            $membershipParams['cms_contactID'],
-            'email-' . $this->_bltID
-          );
-        }
+        $smarty = CRM_Core_Smarty::singleton();
+        $smarty->assign('dataArray', $dataArray);
+        $smarty->assign('totalTaxAmount', $params['tax_amount']);
       }
-    }
-    else {
-      // at this point we've created a contact and stored its address etc
-      // all the payment processors expect the name and address to be in the
-      // so we copy stuff over to first_name etc.
-      $paymentParams = $this->_params;
-      $contributionTypeId = $this->_values['financial_type_id'];
-
-      $fieldTypes = array();
-      if (!empty($paymentParams['onbehalf']) &&
-        is_array($paymentParams['onbehalf'])
-      ) {
-        foreach ($paymentParams['onbehalf'] as $key => $value) {
-          if (strstr($key, 'custom_')) {
-            $this->_params[$key] = $value;
-          }
-        }
-        $fieldTypes = array('Contact', 'Organization', 'Contribution');
+      if (is_a($contribution, 'CRM_Core_Error')) {
+        $message = CRM_Core_Error::getMessages($contribution);
+        CRM_Core_Error::fatal($message);
       }
-      $financialTypeID = $this->wrangleFinancialTypeID($contributionTypeId);
 
-      CRM_Contribute_BAO_Contribution_Utils::processConfirm($this, $paymentParams,
-        $premiumParams, $contactID,
-        $financialTypeID,
-        'contribution',
-        $fieldTypes,
-        ($this->_mode == 'test') ? 1 : 0,
-        $isPayLater
-      );
+      // lets store it in the form variable so postProcess hook can get to this and use it
+      $form->_contributionID = $contribution->id;
     }
-  }
 
-  /**
-   * Wrangle financial type ID.
-   *
-   * This wrangling of the financialType ID was happening in a shared function rather than in the form it relates to & hence has been moved to that form
-   * Pledges are not relevant to the membership code so that portion will not go onto the membership form.
-   *
-   * Comments from previous refactor indicate doubt as to what was going on.
-   *
-   * @param int $contributionTypeId
-   *
-   * @return null|string
-   */
-  public function wrangleFinancialTypeID($contributionTypeId) {
-    if (isset($paymentParams['financial_type'])) {
-      $contributionTypeId = $paymentParams['financial_type'];
-    }
-    elseif (!empty($this->_values['pledge_id'])) {
-      $contributionTypeId = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_Pledge',
-        $this->_values['pledge_id'],
-        'financial_type_id'
-      );
-    }
-    return $contributionTypeId;
-  }
+    //CRM-13981, processing honor contact into soft-credit contribution
+    CRM_Contact_Form_ProfileContact::postProcess($form);
 
-  /**
-   * Process the form.
-   *
-   * @param array $premiumParams
-   * @param CRM_Contribute_BAO_Contribution $contribution
-   */
-  public function postProcessPremium($premiumParams, $contribution) {
-    $hour = $minute = $second = 0;
-    // assigning Premium information to receipt tpl
-    $selectProduct = CRM_Utils_Array::value('selectProduct', $premiumParams);
-    if ($selectProduct &&
-      $selectProduct != 'no_thanks'
-    ) {
-      $startDate = $endDate = "";
-      $this->assign('selectPremium', TRUE);
-      $productDAO = new CRM_Contribute_DAO_Product();
-      $productDAO->id = $selectProduct;
-      $productDAO->find(TRUE);
-      $this->assign('product_name', $productDAO->name);
-      $this->assign('price', $productDAO->price);
-      $this->assign('sku', $productDAO->sku);
-      $this->assign('option', CRM_Utils_Array::value('options_' . $premiumParams['selectProduct'], $premiumParams));
+    // process soft credit / pcp pages
+    CRM_Contribute_Form_Contribution_Confirm::processPcpSoft($params, $contribution);
 
-      $periodType = $productDAO->period_type;
+    //handle pledge stuff.
+    if ($isPledge) {
+      if ($pledgeID) {
+        //when user doing pledge payments.
+        //update the schedule when payment(s) are made
+        foreach ($form->_params['pledge_amount'] as $paymentId => $dontCare) {
+          $scheduledAmount = CRM_Core_DAO::getFieldValue(
+            'CRM_Pledge_DAO_PledgePayment',
+            $paymentId,
+            'scheduled_amount',
+            'id'
+          );
 
-      if ($periodType) {
-        $fixed_period_start_day = $productDAO->fixed_period_start_day;
-        $duration_unit = $productDAO->duration_unit;
-        $duration_interval = $productDAO->duration_interval;
-        if ($periodType == 'rolling') {
-          $startDate = date('Y-m-d');
+          $pledgePaymentParams = array(
+            'id' => $paymentId,
+            'contribution_id' => $contribution->id,
+            'status_id' => $contribution->contribution_status_id,
+            'actual_amount' => $scheduledAmount,
+          );
+
+          CRM_Pledge_BAO_PledgePayment::add($pledgePaymentParams);
         }
-        elseif ($periodType == 'fixed') {
-          if ($fixed_period_start_day) {
-            $date = explode('-', date('Y-m-d'));
-            $month = substr($fixed_period_start_day, 0, strlen($fixed_period_start_day) - 2);
-            $day = substr($fixed_period_start_day, -2) . "<br/>";
-            $year = $date[0];
-            $startDate = $year . '-' . $month . '-' . $day;
-          }
-          else {
-            $startDate = date('Y-m-d');
-          }
+
+        //update pledge status according to the new payment statuses
+        CRM_Pledge_BAO_PledgePayment::updatePledgePaymentStatus($pledgeID);
+      }
+      else {
+        //when user creating pledge record.
+        $pledgeParams = array();
+        $pledgeParams['contact_id'] = $contribution->contact_id;
+        $pledgeParams['installment_amount'] = $pledgeParams['actual_amount'] = $contribution->total_amount;
+        $pledgeParams['contribution_id'] = $contribution->id;
+        $pledgeParams['contribution_page_id'] = $contribution->contribution_page_id;
+        $pledgeParams['financial_type_id'] = $contribution->financial_type_id;
+        $pledgeParams['frequency_interval'] = $params['pledge_frequency_interval'];
+        $pledgeParams['installments'] = $params['pledge_installments'];
+        $pledgeParams['frequency_unit'] = $params['pledge_frequency_unit'];
+        if ($pledgeParams['frequency_unit'] == 'month') {
+          $pledgeParams['frequency_day'] = intval(date("d"));
+        }
+        else {
+          $pledgeParams['frequency_day'] = 1;
         }
+        $pledgeParams['create_date'] = $pledgeParams['start_date'] = $pledgeParams['scheduled_date'] = date("Ymd");
+        $pledgeParams['status_id'] = $contribution->contribution_status_id;
+        $pledgeParams['max_reminders'] = $form->_values['max_reminders'];
+        $pledgeParams['initial_reminder_day'] = $form->_values['initial_reminder_day'];
+        $pledgeParams['additional_reminder_day'] = $form->_values['additional_reminder_day'];
+        $pledgeParams['is_test'] = $contribution->is_test;
+        $pledgeParams['acknowledge_date'] = date('Ymd');
+        $pledgeParams['original_installment_amount'] = $pledgeParams['installment_amount'];
 
-        $date = explode('-', $startDate);
-        $year = $date[0];
-        $month = $date[1];
-        $day = $date[2];
+        //inherit campaign from contirb page.
+        $pledgeParams['campaign_id'] = $campaignId;
 
-        switch ($duration_unit) {
-          case 'year':
-            $year = $year + $duration_interval;
-            break;
+        $pledge = CRM_Pledge_BAO_Pledge::create($pledgeParams);
 
-          case 'month':
-            $month = $month + $duration_interval;
-            break;
+        $form->_params['pledge_id'] = $pledge->id;
 
-          case 'day':
-            $day = $day + $duration_interval;
-            break;
+        //send acknowledgment email. only when pledge is created
+        if ($pledge->id) {
+          //build params to send acknowledgment.
+          $pledgeParams['id'] = $pledge->id;
+          $pledgeParams['receipt_from_name'] = $form->_values['receipt_from_name'];
+          $pledgeParams['receipt_from_email'] = $form->_values['receipt_from_email'];
 
-          case 'week':
-            $day = $day + ($duration_interval * 7);
+          //scheduled amount will be same as installment_amount.
+          $pledgeParams['scheduled_amount'] = $pledgeParams['installment_amount'];
+
+          //get total pledge amount.
+          $pledgeParams['total_pledge_amount'] = $pledge->amount;
+
+          CRM_Pledge_BAO_Pledge::sendAcknowledgment($form, $pledgeParams);
         }
-        $endDate = date('Y-m-d H:i:s', mktime($hour, $minute, $second, $month, $day, $year));
-        $this->assign('start_date', $startDate);
-        $this->assign('end_date', $endDate);
       }
+    }
 
-      $dao = new CRM_Contribute_DAO_Premium();
-      $dao->entity_table = 'civicrm_contribution_page';
-      $dao->entity_id = $this->_id;
-      $dao->find(TRUE);
-      $this->assign('contact_phone', $dao->premiums_contact_phone);
-      $this->assign('contact_email', $dao->premiums_contact_email);
-
-      //create Premium record
-      $params = array(
-        'product_id' => $premiumParams['selectProduct'],
-        'contribution_id' => $contribution->id,
-        'product_option' => CRM_Utils_Array::value('options_' . $premiumParams['selectProduct'], $premiumParams),
-        'quantity' => 1,
-        'start_date' => CRM_Utils_Date::customFormat($startDate, '%Y%m%d'),
-        'end_date' => CRM_Utils_Date::customFormat($endDate, '%Y%m%d'),
+    if ($online && $contribution) {
+      CRM_Core_BAO_CustomValueTable::postProcess($form->_params,
+        'civicrm_contribution',
+        $contribution->id,
+        'Contribution'
       );
-      if (!empty($premiumParams['selectProduct'])) {
-        $daoPremiumsProduct = new CRM_Contribute_DAO_PremiumsProduct();
-        $daoPremiumsProduct->product_id = $premiumParams['selectProduct'];
-        $daoPremiumsProduct->premiums_id = $dao->id;
-        $daoPremiumsProduct->find(TRUE);
-        $params['financial_type_id'] = $daoPremiumsProduct->financial_type_id;
-      }
-      //Fixed For CRM-3901
-      $daoContrProd = new CRM_Contribute_DAO_ContributionProduct();
-      $daoContrProd->contribution_id = $contribution->id;
-      if ($daoContrProd->find(TRUE)) {
-        $params['id'] = $daoContrProd->id;
+    }
+    elseif ($contribution) {
+      //handle custom data.
+      $params['contribution_id'] = $contribution->id;
+      if (!empty($params['custom']) &&
+        is_array($params['custom']) &&
+        !is_a($contribution, 'CRM_Core_Error')
+      ) {
+        CRM_Core_BAO_CustomValueTable::store($params['custom'], 'civicrm_contribution', $contribution->id);
       }
+    }
+    // Save note
+    if ($contribution && !empty($params['contribution_note'])) {
+      $noteParams = array(
+        'entity_table' => 'civicrm_contribution',
+        'note' => $params['contribution_note'],
+        'entity_id' => $contribution->id,
+        'contact_id' => $contribution->contact_id,
+        'modified_date' => date('Ymd'),
+      );
 
-      CRM_Contribute_BAO_Contribution::addPremium($params);
-      if ($productDAO->cost && !empty($params['financial_type_id'])) {
-        $trxnParams = array(
-          'cost' => $productDAO->cost,
-          'currency' => $productDAO->currency,
-          'financial_type_id' => $params['financial_type_id'],
-          'contributionId' => $contribution->id,
-        );
-        CRM_Core_BAO_FinancialTrxn::createPremiumTrxn($trxnParams);
-      }
+      CRM_Core_BAO_Note::add($noteParams, array());
     }
-    elseif ($selectProduct == 'no_thanks') {
-      //Fixed For CRM-3901
-      $daoContrProd = new CRM_Contribute_DAO_ContributionProduct();
-      $daoContrProd->contribution_id = $contribution->id;
-      if ($daoContrProd->find(TRUE)) {
-        $daoContrProd->delete();
-      }
+
+    if (isset($params['related_contact'])) {
+      $contactID = $params['related_contact'];
+    }
+    elseif (isset($params['cms_contactID'])) {
+      $contactID = $params['cms_contactID'];
+    }
+
+    //create contribution activity w/ individual and target
+    //activity w/ organisation contact id when onbelf, CRM-4027
+    $targetContactID = NULL;
+    if (!empty($params['hidden_onbehalf_profile'])) {
+      $targetContactID = $contribution->contact_id;
+      $contribution->contact_id = $contactID;
+    }
+
+    // create an activity record
+    if ($contribution) {
+      CRM_Activity_BAO_Activity::addActivity($contribution, NULL, $targetContactID);
     }
+
+    $transaction->commit();
+    // CRM-13074 - create the CMSUser after the transaction is completed as it
+    // is not appropriate to delete a valid contribution if a user create problem occurs
+    CRM_Contribute_BAO_Contribution_Utils::createCMSUser($params,
+      $contactID,
+      'email-' . $billingLocationID
+    );
+    return $contribution;
   }
 
   /**
-   * Process the contribution.
+   * Create the recurring contribution record.
    *
    * @param CRM_Core_Form $form
    * @param array $params
-   * @param array $result
    * @param int $contactID
-   * @param CRM_Financial_DAO_FinancialType $financialType
-   * @param bool $pending
+   * @param string $contributionType
    * @param bool $online
    *
-   * @param bool $isTest
-   * @param array $lineItems
-   *
-   * @param int $billingLocationID
-   *   ID of billing location type.
-   *
-   * @return \CRM_Contribute_DAO_Contribution
-   * @throws \Exception
+   * @return mixed
    */
-  public static function processFormContribution(
-    &$form,
-    $params,
-    $result,
-    $contactID,
-    $financialType,
-    $pending,
-    $online,
-    $isTest,
-    $lineItems,
-    $billingLocationID
-  ) {
-    $transaction = new CRM_Core_Transaction();
-    $contribSoftContactId = $addressID = NULL;
-    $contributeMode = $form->_contributeMode;
-    $isMonetary = !empty($form->_values['is_monetary']);
-    $isEmailReceipt = !empty($form->_values['is_email_receipt']);
-    // How do these vary from params? These are currently passed to
-    // - custom data function....
-    $formParams = $form->_params;
-    $isSeparateMembershipPayment = empty($formParams['separate_membership_payment']) ? FALSE : TRUE;
-    $pledgeID = empty($formParams['pledge_id']) ? NULL : $formParams['pledge_id'];
-    if (!$isSeparateMembershipPayment && !empty($form->_values['pledge_block_id']) &&
-      (!empty($formParams['is_pledge']) || $pledgeID)) {
-      $isPledge = TRUE;
-    }
-    else {
-      $isPledge = FALSE;
+  public static function processRecurringContribution(&$form, &$params, $contactID, $contributionType, $online = TRUE) {
+    // return if this page is not set for recurring
+    // or the user has not chosen the recurring option
+
+    //this is online case validation.
+    if ((empty($form->_values['is_recur']) && $online) || empty($params['is_recur'])) {
+      return NULL;
     }
 
-    // add these values for the recurringContrib function ,CRM-10188
-    $params['financial_type_id'] = $financialType->id;
+    $recurParams = array('contact_id' => $contactID);
+    $recurParams['amount'] = CRM_Utils_Array::value('amount', $params);
+    $recurParams['auto_renew'] = CRM_Utils_Array::value('auto_renew', $params);
+    $recurParams['frequency_unit'] = CRM_Utils_Array::value('frequency_unit', $params);
+    $recurParams['frequency_interval'] = CRM_Utils_Array::value('frequency_interval', $params);
+    $recurParams['installments'] = CRM_Utils_Array::value('installments', $params);
+    $recurParams['financial_type_id'] = CRM_Utils_Array::value('financial_type_id', $params);
+    $recurParams['currency'] = CRM_Utils_Array::value('currency', $params);
 
-    //create an contribution address
-    if ($contributeMode != 'notify' && empty($params['is_pay_later']) && $isMonetary) {
-      $addressID = CRM_Contribute_BAO_Contribution::createAddress($params, $billingLocationID);
+    // CRM-14354: For an auto-renewing membership with an additional contribution,
+    // if separate payments is not enabled, make sure only the membership fee recurs
+    if (!empty($form->_membershipBlock)
+      && $form->_membershipBlock['is_separate_payment'] === '0'
+      && isset($params['selectMembership'])
+      && $form->_values['is_allow_other_amount'] == '1'
+      // CRM-16331
+      && !empty($form->_membershipTypeValues)
+      && !empty($form->_membershipTypeValues[$params['selectMembership']]['minimum_fee'])
+    ) {
+      $recurParams['amount'] = $form->_membershipTypeValues[$params['selectMembership']]['minimum_fee'];
     }
 
-    //@todo - this is being set from the form to resolve CRM-10188 - an
-    // eNotice caused by it not being set @ the front end
-    // however, we then get it being over-written with null for backend contributions
-    // a better fix would be to set the values in the respective forms rather than require
-    // a function being shared by two forms to deal with their respective values
-    // moving it to the BAO & not taking the $form as a param would make sense here.
-    if (!isset($params['is_email_receipt']) && $isEmailReceipt) {
-      $params['is_email_receipt'] = $isEmailReceipt;
+    $recurParams['is_test'] = 0;
+    if (($form->_action & CRM_Core_Action::PREVIEW) ||
+      (isset($form->_mode) && ($form->_mode == 'test'))
+    ) {
+      $recurParams['is_test'] = 1;
     }
-    $recurringContributionID = self::processRecurringContribution($form, $params, $contactID, $financialType, $online);
-    $nonDeductibleAmount = self::getNonDeductibleAmount($params, $financialType, $online);
 
-    $now = date('YmdHis');
-    $receiptDate = CRM_Utils_Array::value('receipt_date', $params);
-    if ($isEmailReceipt) {
-      $receiptDate = $now;
+    $recurParams['start_date'] = $recurParams['create_date'] = $recurParams['modified_date'] = date('YmdHis');
+    if (!empty($params['receive_date'])) {
+      $recurParams['start_date'] = $params['receive_date'];
+    }
+    $recurParams['invoice_id'] = CRM_Utils_Array::value('invoiceID', $params);
+    $recurParams['contribution_status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending');
+    $recurParams['payment_processor_id'] = CRM_Utils_Array::value('payment_processor_id', $params);
+    $recurParams['is_email_receipt'] = CRM_Utils_Array::value('is_email_receipt', $params);
+    // we need to add a unique trxn_id to avoid a unique key error
+    // in paypal IPN we reset this when paypal sends us the real trxn id, CRM-2991
+    $recurParams['trxn_id'] = CRM_Utils_Array::value('trxn_id', $params, $params['invoiceID']);
+    $recurParams['financial_type_id'] = $contributionType->id;
+
+    if (!$online || $form->_values['is_monetary']) {
+      $recurParams['payment_instrument_id'] = 1;
     }
 
-    //get the contrib page id.
-    $contributionPageId = NULL;
+    $campaignId = CRM_Utils_Array::value('campaign_id', $params);
     if ($online) {
-      $contributionPageId = $form->_id;
-      $campaignId = CRM_Utils_Array::value('campaign_id', $params);
       if (!array_key_exists('campaign_id', $params)) {
         $campaignId = CRM_Utils_Array::value('campaign_id', $form->_values);
       }
     }
-    else {
-      //also for offline we do support - CRM-7290
-      $contributionPageId = CRM_Utils_Array::value('contribution_page_id', $params);
-      $campaignId = CRM_Utils_Array::value('campaign_id', $params);
-    }
+    $recurParams['campaign_id'] = $campaignId;
 
-    // Prepare soft contribution due to pcp or Submit Credit / Debit Card Contribution by admin.
-    if (!empty($params['pcp_made_through_id']) || !empty($params['soft_credit_to'])) {
-      // if its due to pcp
-      if (!empty($params['pcp_made_through_id'])) {
-        $contribSoftContactId = CRM_Core_DAO::getFieldValue(
-          'CRM_PCP_DAO_PCP',
-          $params['pcp_made_through_id'],
-          'contact_id'
-        );
-      }
-      else {
-        $contribSoftContactId = CRM_Utils_Array::value('soft_credit_to', $params);
+    $recurring = CRM_Contribute_BAO_ContributionRecur::add($recurParams);
+    if (is_a($recurring, 'CRM_Core_Error')) {
+      CRM_Core_Error::displaySessionError($recurring);
+      $urlString = 'civicrm/contribute/transact';
+      $urlParams = '_qf_Main_display=true';
+      if (get_class($form) == 'CRM_Contribute_Form_Contribution') {
+        $urlString = 'civicrm/contact/view/contribution';
+        $urlParams = "action=add&cid={$form->_contactID}";
+        if ($form->_mode) {
+          $urlParams .= "&mode={$form->_mode}";
+        }
       }
-
-      // Pass these details onto with the contribution to make them
-      // available at hook_post_process, CRM-8908
-      $params['soft_credit_to'] = $contribSoftContactId;
+      CRM_Utils_System::redirect(CRM_Utils_System::url($urlString, $urlParams));
     }
 
-    if (isset($params['amount'])) {
-      $contribParams = self::getContributionParams(
-        $params, $contactID, $financialType->id, $online, $contributionPageId, $nonDeductibleAmount, $campaignId, $isMonetary, $pending, $result, $receiptDate,
-        $recurringContributionID, $isTest, $addressID, $contribSoftContactId, $lineItems
-      );
-      $contribution = CRM_Contribute_BAO_Contribution::add($contribParams);
-
-      $invoiceSettings = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME, 'contribution_invoice_settings');
-      $invoicing = CRM_Utils_Array::value('invoicing', $invoiceSettings);
-      if ($invoicing) {
-        $dataArray = array();
-        foreach ($form->_lineItem as $lineItemKey => $lineItemValue) {
-          foreach ($lineItemValue as $key => $value) {
-            if (isset($value['tax_amount']) && isset($value['tax_rate'])) {
-              if (isset($dataArray[$value['tax_rate']])) {
-                $dataArray[$value['tax_rate']] = $dataArray[$value['tax_rate']] + CRM_Utils_Array::value('tax_amount', $value);
-              }
-              else {
-                $dataArray[$value['tax_rate']] = CRM_Utils_Array::value('tax_amount', $value);
-              }
-            }
-          }
-        }
-        $smarty = CRM_Core_Smarty::singleton();
-        $smarty->assign('dataArray', $dataArray);
-        $smarty->assign('totalTaxAmount', $params['tax_amount']);
-      }
-      if (is_a($contribution, 'CRM_Core_Error')) {
-        $message = CRM_Core_Error::getMessages($contribution);
-        CRM_Core_Error::fatal($message);
-      }
+    return $recurring->id;
+  }
 
-      // lets store it in the form variable so postProcess hook can get to this and use it
-      $form->_contributionID = $contribution->id;
+  /**
+   * Add on behalf of organization and it's location.
+   *
+   * This situation occurs when on behalf of is enabled for the contribution page and the person
+   * signing up does so on behalf of an organization.
+   *
+   * @param array $behalfOrganization
+   *   array of organization info.
+   * @param int $contactID
+   *   individual contact id. One.
+   *   who is doing the process of signup / contribution.
+   *
+   * @param array $values
+   *   form values array.
+   * @param array $params
+   * @param array $fields
+   *   Array of fields from the onbehalf profile relevant to the organization.
+   */
+  public static function processOnBehalfOrganization(&$behalfOrganization, &$contactID, &$values, &$params, $fields = NULL) {
+    $isCurrentEmployer = FALSE;
+    $dupeIDs = array();
+    $orgID = NULL;
+    if (!empty($behalfOrganization['organization_id']) && empty($behalfOrganization['org_option'])) {
+      $orgID = $behalfOrganization['organization_id'];
+      unset($behalfOrganization['organization_id']);
+      $isCurrentEmployer = TRUE;
     }
 
-    //CRM-13981, processing honor contact into soft-credit contribution
-    CRM_Contact_Form_ProfileContact::postProcess($form);
-
-    // process soft credit / pcp pages
-    CRM_Contribute_Form_Contribution_Confirm::processPcpSoft($params, $contribution);
+    // formalities for creating / editing organization.
+    $behalfOrganization['contact_type'] = 'Organization';
 
-    //handle pledge stuff.
-    if ($isPledge) {
-      if ($pledgeID) {
-        //when user doing pledge payments.
-        //update the schedule when payment(s) are made
-        foreach ($form->_params['pledge_amount'] as $paymentId => $dontCare) {
-          $scheduledAmount = CRM_Core_DAO::getFieldValue(
-            'CRM_Pledge_DAO_PledgePayment',
-            $paymentId,
-            'scheduled_amount',
-            'id'
-          );
+    // get the relationship type id
+    $relType = new CRM_Contact_DAO_RelationshipType();
+    $relType->name_a_b = 'Employee of';
+    $relType->find(TRUE);
+    $relTypeId = $relType->id;
 
-          $pledgePaymentParams = array(
-            'id' => $paymentId,
-            'contribution_id' => $contribution->id,
-            'status_id' => $contribution->contribution_status_id,
-            'actual_amount' => $scheduledAmount,
-          );
+    // keep relationship params ready
+    $relParams['relationship_type_id'] = $relTypeId . '_a_b';
+    $relParams['is_permission_a_b'] = 1;
+    $relParams['is_active'] = 1;
 
-          CRM_Pledge_BAO_PledgePayment::add($pledgePaymentParams);
-        }
+    if (!$orgID) {
+      // check if matching organization contact exists
+      $dedupeParams = CRM_Dedupe_Finder::formatParams($behalfOrganization, 'Organization');
+      $dedupeParams['check_permission'] = FALSE;
+      $dupeIDs = CRM_Dedupe_Finder::dupesByParams($dedupeParams, 'Organization', 'Unsupervised');
 
-        //update pledge status according to the new payment statuses
-        CRM_Pledge_BAO_PledgePayment::updatePledgePaymentStatus($pledgeID);
-      }
-      else {
-        //when user creating pledge record.
-        $pledgeParams = array();
-        $pledgeParams['contact_id'] = $contribution->contact_id;
-        $pledgeParams['installment_amount'] = $pledgeParams['actual_amount'] = $contribution->total_amount;
-        $pledgeParams['contribution_id'] = $contribution->id;
-        $pledgeParams['contribution_page_id'] = $contribution->contribution_page_id;
-        $pledgeParams['financial_type_id'] = $contribution->financial_type_id;
-        $pledgeParams['frequency_interval'] = $params['pledge_frequency_interval'];
-        $pledgeParams['installments'] = $params['pledge_installments'];
-        $pledgeParams['frequency_unit'] = $params['pledge_frequency_unit'];
-        if ($pledgeParams['frequency_unit'] == 'month') {
-          $pledgeParams['frequency_day'] = intval(date("d"));
-        }
-        else {
-          $pledgeParams['frequency_day'] = 1;
-        }
-        $pledgeParams['create_date'] = $pledgeParams['start_date'] = $pledgeParams['scheduled_date'] = date("Ymd");
-        $pledgeParams['status_id'] = $contribution->contribution_status_id;
-        $pledgeParams['max_reminders'] = $form->_values['max_reminders'];
-        $pledgeParams['initial_reminder_day'] = $form->_values['initial_reminder_day'];
-        $pledgeParams['additional_reminder_day'] = $form->_values['additional_reminder_day'];
-        $pledgeParams['is_test'] = $contribution->is_test;
-        $pledgeParams['acknowledge_date'] = date('Ymd');
-        $pledgeParams['original_installment_amount'] = $pledgeParams['installment_amount'];
-
-        //inherit campaign from contirb page.
-        $pledgeParams['campaign_id'] = $campaignId;
-
-        $pledge = CRM_Pledge_BAO_Pledge::create($pledgeParams);
-
-        $form->_params['pledge_id'] = $pledge->id;
-
-        //send acknowledgment email. only when pledge is created
-        if ($pledge->id) {
-          //build params to send acknowledgment.
-          $pledgeParams['id'] = $pledge->id;
-          $pledgeParams['receipt_from_name'] = $form->_values['receipt_from_name'];
-          $pledgeParams['receipt_from_email'] = $form->_values['receipt_from_email'];
-
-          //scheduled amount will be same as installment_amount.
-          $pledgeParams['scheduled_amount'] = $pledgeParams['installment_amount'];
-
-          //get total pledge amount.
-          $pledgeParams['total_pledge_amount'] = $pledge->amount;
-
-          CRM_Pledge_BAO_Pledge::sendAcknowledgment($form, $pledgeParams);
-        }
-      }
-    }
-
-    if ($online && $contribution) {
-      CRM_Core_BAO_CustomValueTable::postProcess($form->_params,
-        'civicrm_contribution',
-        $contribution->id,
-        'Contribution'
-      );
-    }
-    elseif ($contribution) {
-      //handle custom data.
-      $params['contribution_id'] = $contribution->id;
-      if (!empty($params['custom']) &&
-        is_array($params['custom']) &&
-        !is_a($contribution, 'CRM_Core_Error')
-      ) {
-        CRM_Core_BAO_CustomValueTable::store($params['custom'], 'civicrm_contribution', $contribution->id);
-      }
-    }
-    // Save note
-    if ($contribution && !empty($params['contribution_note'])) {
-      $noteParams = array(
-        'entity_table' => 'civicrm_contribution',
-        'note' => $params['contribution_note'],
-        'entity_id' => $contribution->id,
-        'contact_id' => $contribution->contact_id,
-        'modified_date' => date('Ymd'),
-      );
-
-      CRM_Core_BAO_Note::add($noteParams, array());
-    }
-
-    if (isset($params['related_contact'])) {
-      $contactID = $params['related_contact'];
-    }
-    elseif (isset($params['cms_contactID'])) {
-      $contactID = $params['cms_contactID'];
-    }
-
-    //create contribution activity w/ individual and target
-    //activity w/ organisation contact id when onbelf, CRM-4027
-    $targetContactID = NULL;
-    if (!empty($params['hidden_onbehalf_profile'])) {
-      $targetContactID = $contribution->contact_id;
-      $contribution->contact_id = $contactID;
-    }
-
-    // create an activity record
-    if ($contribution) {
-      CRM_Activity_BAO_Activity::addActivity($contribution, NULL, $targetContactID);
-    }
-
-    $transaction->commit();
-    // CRM-13074 - create the CMSUser after the transaction is completed as it
-    // is not appropriate to delete a valid contribution if a user create problem occurs
-    CRM_Contribute_BAO_Contribution_Utils::createCMSUser($params,
-      $contactID,
-      'email-' . $billingLocationID
-    );
-    return $contribution;
-  }
-
-  /**
-   * Create the recurring contribution record.
-   *
-   * @param CRM_Core_Form $form
-   * @param array $params
-   * @param int $contactID
-   * @param string $contributionType
-   * @param bool $online
-   *
-   * @return mixed
-   */
-  public static function processRecurringContribution(&$form, &$params, $contactID, $contributionType, $online = TRUE) {
-    // return if this page is not set for recurring
-    // or the user has not chosen the recurring option
-
-    //this is online case validation.
-    if ((empty($form->_values['is_recur']) && $online) || empty($params['is_recur'])) {
-      return NULL;
-    }
-
-    $recurParams = array('contact_id' => $contactID);
-    $recurParams['amount'] = CRM_Utils_Array::value('amount', $params);
-    $recurParams['auto_renew'] = CRM_Utils_Array::value('auto_renew', $params);
-    $recurParams['frequency_unit'] = CRM_Utils_Array::value('frequency_unit', $params);
-    $recurParams['frequency_interval'] = CRM_Utils_Array::value('frequency_interval', $params);
-    $recurParams['installments'] = CRM_Utils_Array::value('installments', $params);
-    $recurParams['financial_type_id'] = CRM_Utils_Array::value('financial_type_id', $params);
-    $recurParams['currency'] = CRM_Utils_Array::value('currency', $params);
-
-    // CRM-14354: For an auto-renewing membership with an additional contribution,
-    // if separate payments is not enabled, make sure only the membership fee recurs
-    if (!empty($form->_membershipBlock)
-      && $form->_membershipBlock['is_separate_payment'] === '0'
-      && isset($params['selectMembership'])
-      && $form->_values['is_allow_other_amount'] == '1'
-      // CRM-16331
-      && !empty($form->_membershipTypeValues)
-      && !empty($form->_membershipTypeValues[$params['selectMembership']]['minimum_fee'])
-    ) {
-      $recurParams['amount'] = $form->_membershipTypeValues[$params['selectMembership']]['minimum_fee'];
-    }
-
-    $recurParams['is_test'] = 0;
-    if (($form->_action & CRM_Core_Action::PREVIEW) ||
-      (isset($form->_mode) && ($form->_mode == 'test'))
-    ) {
-      $recurParams['is_test'] = 1;
-    }
-
-    $recurParams['start_date'] = $recurParams['create_date'] = $recurParams['modified_date'] = date('YmdHis');
-    if (!empty($params['receive_date'])) {
-      $recurParams['start_date'] = $params['receive_date'];
-    }
-    $recurParams['invoice_id'] = CRM_Utils_Array::value('invoiceID', $params);
-    $recurParams['contribution_status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending');
-    $recurParams['payment_processor_id'] = CRM_Utils_Array::value('payment_processor_id', $params);
-    $recurParams['is_email_receipt'] = CRM_Utils_Array::value('is_email_receipt', $params);
-    // we need to add a unique trxn_id to avoid a unique key error
-    // in paypal IPN we reset this when paypal sends us the real trxn id, CRM-2991
-    $recurParams['trxn_id'] = CRM_Utils_Array::value('trxn_id', $params, $params['invoiceID']);
-    $recurParams['financial_type_id'] = $contributionType->id;
-
-    if (!$online || $form->_values['is_monetary']) {
-      $recurParams['payment_instrument_id'] = 1;
-    }
-
-    $campaignId = CRM_Utils_Array::value('campaign_id', $params);
-    if ($online) {
-      if (!array_key_exists('campaign_id', $params)) {
-        $campaignId = CRM_Utils_Array::value('campaign_id', $form->_values);
-      }
-    }
-    $recurParams['campaign_id'] = $campaignId;
-
-    $recurring = CRM_Contribute_BAO_ContributionRecur::add($recurParams);
-    if (is_a($recurring, 'CRM_Core_Error')) {
-      CRM_Core_Error::displaySessionError($recurring);
-      $urlString = 'civicrm/contribute/transact';
-      $urlParams = '_qf_Main_display=true';
-      if (get_class($form) == 'CRM_Contribute_Form_Contribution') {
-        $urlString = 'civicrm/contact/view/contribution';
-        $urlParams = "action=add&cid={$form->_contactID}";
-        if ($form->_mode) {
-          $urlParams .= "&mode={$form->_mode}";
-        }
-      }
-      CRM_Utils_System::redirect(CRM_Utils_System::url($urlString, $urlParams));
-    }
-
-    return $recurring->id;
-  }
-
-  /**
-   * Add on behalf of organization and it's location.
-   *
-   * This situation occurs when on behalf of is enabled for the contribution page and the person
-   * signing up does so on behalf of an organization.
-   *
-   * @param array $behalfOrganization
-   *   array of organization info.
-   * @param int $contactID
-   *   individual contact id. One.
-   *   who is doing the process of signup / contribution.
-   *
-   * @param array $values
-   *   form values array.
-   * @param array $params
-   * @param array $fields
-   *   Array of fields from the onbehalf profile relevant to the organization.
-   */
-  public static function processOnBehalfOrganization(&$behalfOrganization, &$contactID, &$values, &$params, $fields = NULL) {
-    $isCurrentEmployer = FALSE;
-    $dupeIDs = array();
-    $orgID = NULL;
-    if (!empty($behalfOrganization['organization_id']) && empty($behalfOrganization['org_option'])) {
-      $orgID = $behalfOrganization['organization_id'];
-      unset($behalfOrganization['organization_id']);
-      $isCurrentEmployer = TRUE;
-    }
-
-    // formalities for creating / editing organization.
-    $behalfOrganization['contact_type'] = 'Organization';
-
-    // get the relationship type id
-    $relType = new CRM_Contact_DAO_RelationshipType();
-    $relType->name_a_b = 'Employee of';
-    $relType->find(TRUE);
-    $relTypeId = $relType->id;
-
-    // keep relationship params ready
-    $relParams['relationship_type_id'] = $relTypeId . '_a_b';
-    $relParams['is_permission_a_b'] = 1;
-    $relParams['is_active'] = 1;
-
-    if (!$orgID) {
-      // check if matching organization contact exists
-      $dedupeParams = CRM_Dedupe_Finder::formatParams($behalfOrganization, 'Organization');
-      $dedupeParams['check_permission'] = FALSE;
-      $dupeIDs = CRM_Dedupe_Finder::dupesByParams($dedupeParams, 'Organization', 'Unsupervised');
-
-      // CRM-6243 says to pick the first org even if more than one match
-      if (count($dupeIDs) >= 1) {
-        $behalfOrganization['contact_id'] = $orgID = $dupeIDs[0];
-        // don't allow name edit
-        unset($behalfOrganization['organization_name']);
+      // CRM-6243 says to pick the first org even if more than one match
+      if (count($dupeIDs) >= 1) {
+        $behalfOrganization['contact_id'] = $orgID = $dupeIDs[0];
+        // don't allow name edit
+        unset($behalfOrganization['organization_name']);
       }
     }
     else {
@@ -1829,6 +1459,7 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
         'donors_email' => $email,
         'pcpInfoURL' => $pcpInfoURL,
         'is_honor_roll_enabled' => $contributionSoft->pcp_display_in_roll,
+        'currency' => $contributionSoft->currency,
       );
       $domainValues = CRM_Core_BAO_Domain::getNameAndEmail();
       $sendTemplateParams = array(
@@ -1864,229 +1495,991 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
       $params['pcp_is_anonymous'] = 1;
     }
     else {
-      $params['pcp_is_anonymous'] = 0;
-    }
-    foreach (array(
-               'pcp_display_in_roll',
-               'pcp_is_anonymous',
-               'pcp_roll_nickname',
-               'pcp_personal_note',
-             ) as $val) {
-      if (!empty($params[$val])) {
-        $page->assign($val, $params[$val]);
-      }
+      $params['pcp_is_anonymous'] = 0;
+    }
+    foreach (array(
+               'pcp_display_in_roll',
+               'pcp_is_anonymous',
+               'pcp_roll_nickname',
+               'pcp_personal_note',
+             ) as $val) {
+      if (!empty($params[$val])) {
+        $page->assign($val, $params[$val]);
+      }
+    }
+
+    return $params;
+  }
+
+  /**
+   * Process membership.
+   *
+   * @param array $membershipParams
+   * @param int $contactID
+   * @param array $customFieldsFormatted
+   * @param array $fieldTypes
+   * @param array $premiumParams
+   * @param array $membershipLineItems
+   *   Line items specifically relating to memberships.
+   * @param bool $isPayLater
+   */
+  public function processMembership($membershipParams, $contactID, $customFieldsFormatted, $fieldTypes, $premiumParams, $membershipLineItems, $isPayLater) {
+    try {
+      $membershipTypeIDs = (array) $membershipParams['selectMembership'];
+      $membershipTypes = CRM_Member_BAO_Membership::buildMembershipTypeValues($this, $membershipTypeIDs);
+      $membershipType = empty($membershipTypes) ? array() : reset($membershipTypes);
+      $isPending = $this->getIsPending();
+
+      $this->assign('membership_name', CRM_Utils_Array::value('name', $membershipType));
+
+      $isPaidMembership = FALSE;
+      if ($this->_amount >= 0.0 && isset($membershipParams['amount'])) {
+        //amount must be greater than zero for
+        //adding contribution record  to contribution table.
+        //this condition arises when separate membership payment is
+        //enabled and contribution amount is not selected. fix for CRM-3010
+        $isPaidMembership = TRUE;
+      }
+      $isProcessSeparateMembershipTransaction = $this->isSeparateMembershipTransaction($this->_id, $this->_values['amount_block_is_active']);
+
+      if ($this->_values['amount_block_is_active']) {
+        $financialTypeID = $this->_values['financial_type_id'];
+      }
+      else {
+        $financialTypeID = CRM_Utils_Array::value('financial_type_id', $membershipType, CRM_Utils_Array::value('financial_type_id', $membershipParams));
+      }
+
+      if (CRM_Utils_Array::value('membership_source', $this->_params)) {
+        $membershipParams['contribution_source'] = $this->_params['membership_source'];
+      }
+
+      $this->postProcessMembership($membershipParams, $contactID,
+        $this, $premiumParams, $customFieldsFormatted, $fieldTypes, $membershipType, $membershipTypeIDs, $isPaidMembership, $this->_membershipId, $isProcessSeparateMembershipTransaction, $financialTypeID,
+        $membershipLineItems, $isPayLater, $isPending);
+
+      $this->assign('membership_assign', TRUE);
+      $this->set('membershipTypeID', $membershipParams['selectMembership']);
+    }
+    catch (CRM_Core_Exception $e) {
+      CRM_Core_Session::singleton()->setStatus($e->getMessage());
+      CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contribute/transact', "_qf_Main_display=true&qfKey={$this->_params['qfKey']}"));
+    }
+  }
+
+  /**
+   * Process the Memberships.
+   *
+   * @param array $membershipParams
+   *   Array of membership fields.
+   * @param int $contactID
+   *   Contact id.
+   * @param CRM_Contribute_Form_Contribution_Confirm $form
+   *   Confirmation form object.
+   *
+   * @param array $premiumParams
+   * @param null $customFieldsFormatted
+   * @param null $includeFieldTypes
+   *
+   * @param array $membershipDetails
+   *
+   * @param array $membershipTypeIDs
+   *
+   * @param bool $isPaidMembership
+   * @param array $membershipID
+   *
+   * @param bool $isProcessSeparateMembershipTransaction
+   *
+   * @param int $financialTypeID
+   * @param array $membershipLineItems
+   *   Line items specific to membership payment that is separate to contribution.
+   * @param bool $isPayLater
+   * @param bool $isPending
+   *
+   * @throws \CRM_Core_Exception
+   */
+  protected function postProcessMembership(
+    $membershipParams, $contactID, &$form, $premiumParams,
+    $customFieldsFormatted = NULL, $includeFieldTypes = NULL, $membershipDetails, $membershipTypeIDs, $isPaidMembership, $membershipID,
+    $isProcessSeparateMembershipTransaction, $financialTypeID, $membershipLineItems, $isPayLater, $isPending) {
+    $membershipContribution = NULL;
+    $isTest = CRM_Utils_Array::value('is_test', $membershipParams, FALSE);
+    $errors = $createdMemberships = $paymentResult = array();
+
+    if ($isPaidMembership) {
+      if ($isProcessSeparateMembershipTransaction) {
+        // If we have 2 transactions only one can use the invoice id.
+        $membershipParams['invoiceID'] .= '-2';
+      }
+
+      $paymentResult = CRM_Contribute_BAO_Contribution_Utils::processConfirm($form, $membershipParams,
+        $contactID,
+        $financialTypeID,
+        'membership',
+        array(),
+        $isTest,
+        $isPayLater
+      );
+
+      if (!empty($paymentResult['contribution'])) {
+        $this->postProcessPremium($premiumParams, $paymentResult['contribution']);
+        //note that this will be over-written if we are using a separate membership transaction. Otherwise there is only one
+        $membershipContribution = $paymentResult['contribution'];
+        // Save the contribution ID so that I can be used in email receipts
+        // For example, if you need to generate a tax receipt for the donation only.
+        $form->_values['contribution_other_id'] = $membershipContribution->id;
+      }
+    }
+
+    if ($isProcessSeparateMembershipTransaction) {
+      try {
+        $form->_lineItem = $membershipLineItems;
+        if (empty($form->_params['auto_renew']) && !empty($membershipParams['is_recur'])) {
+          unset($membershipParams['is_recur']);
+        }
+        $membershipContribution = $this->processSecondaryFinancialTransaction($contactID, $form, $membershipParams,
+          $isTest, $membershipLineItems, CRM_Utils_Array::value('minimum_fee', $membershipDetails, 0), CRM_Utils_Array::value('financial_type_id', $membershipDetails));
+      }
+      catch (CRM_Core_Exception $e) {
+        $errors[2] = $e->getMessage();
+        $membershipContribution = NULL;
+      }
+    }
+
+    $membership = NULL;
+    if (!empty($membershipContribution) && !is_a($membershipContribution, 'CRM_Core_Error')) {
+      $membershipContributionID = $membershipContribution->id;
+    }
+
+    //@todo - why is this nested so deep? it seems like it could be just set on the calling function on the form layer
+    if (isset($membershipParams['onbehalf']) && !empty($membershipParams['onbehalf']['member_campaign_id'])) {
+      $form->_params['campaign_id'] = $membershipParams['onbehalf']['member_campaign_id'];
+    }
+    //@todo it should no longer be possible for it to get to this point & membership to not be an array
+    if (is_array($membershipTypeIDs) && !empty($membershipContributionID)) {
+      $typesTerms = CRM_Utils_Array::value('types_terms', $membershipParams, array());
+      foreach ($membershipTypeIDs as $memType) {
+        $numTerms = CRM_Utils_Array::value($memType, $typesTerms, 1);
+        if (!empty($membershipContribution)) {
+          $pendingStatus = CRM_Core_OptionGroup::getValue('contribution_status', 'Pending', 'name');
+          $pending = ($membershipContribution->contribution_status_id == $pendingStatus) ? TRUE : FALSE;
+        }
+        else {
+          $pending = $isPending;
+        }
+        $contributionRecurID = isset($form->_params['contributionRecurID']) ? $form->_params['contributionRecurID'] : NULL;
+
+        $membershipSource = NULL;
+        if (!empty($form->_params['membership_source'])) {
+          $membershipSource = $form->_params['membership_source'];
+        }
+        elseif (isset($form->_values['title']) && !empty($form->_values['title'])) {
+          $membershipSource = ts('Online Contribution:') . ' ' . $form->_values['title'];
+        }
+        $isPayLater = NULL;
+        if (isset($form->_params)) {
+          $isPayLater = CRM_Utils_Array::value('is_pay_later', $form->_params);
+        }
+        $campaignId = NULL;
+        if (isset($form->_values) && is_array($form->_values) && !empty($form->_values)) {
+          $campaignId = CRM_Utils_Array::value('campaign_id', $form->_params);
+          if (!array_key_exists('campaign_id', $form->_params)) {
+            $campaignId = CRM_Utils_Array::value('campaign_id', $form->_values);
+          }
+        }
+
+        list($membership, $renewalMode, $dates) = CRM_Member_BAO_Membership::renewMembership(
+          $contactID, $memType, $isTest,
+          date('YmdHis'), CRM_Utils_Array::value('cms_contactID', $membershipParams),
+          $customFieldsFormatted,
+          $numTerms, $membershipID, $pending,
+          $contributionRecurID, $membershipSource, $isPayLater, $campaignId
+        );
+        $form->set('renewal_mode', $renewalMode);
+        if (!empty($dates)) {
+          $form->assign('mem_start_date',
+            CRM_Utils_Date::customFormat($dates['start_date'], '%Y%m%d')
+          );
+          $form->assign('mem_end_date',
+            CRM_Utils_Date::customFormat($dates['end_date'], '%Y%m%d')
+          );
+        }
+
+        if (!empty($membershipContribution)) {
+          // update recurring id for membership record
+          CRM_Member_BAO_Membership::updateRecurMembership($membership, $membershipContribution);
+          CRM_Member_BAO_Membership::linkMembershipPayment($membership, $membershipContribution);
+        }
+      }
+      if ($form->_priceSetId && !empty($form->_useForMember) && !empty($form->_lineItem)) {
+        foreach ($form->_lineItem[$form->_priceSetId] as & $priceFieldOp) {
+          if (!empty($priceFieldOp['membership_type_id']) &&
+            isset($createdMemberships[$priceFieldOp['membership_type_id']])
+          ) {
+            $membershipOb = $createdMemberships[$priceFieldOp['membership_type_id']];
+            $priceFieldOp['start_date'] = $membershipOb->start_date ? CRM_Utils_Date::customFormat($membershipOb->start_date, '%B %E%f, %Y') : '-';
+            $priceFieldOp['end_date'] = $membershipOb->end_date ? CRM_Utils_Date::customFormat($membershipOb->end_date, '%B %E%f, %Y') : '-';
+          }
+          else {
+            $priceFieldOp['start_date'] = $priceFieldOp['end_date'] = 'N/A';
+          }
+        }
+        $form->_values['lineItem'] = $form->_lineItem;
+        $form->assign('lineItem', $form->_lineItem);
+      }
+    }
+
+    if (!empty($errors)) {
+      $message = $this->compileErrorMessage($errors);
+      throw new CRM_Core_Exception($message);
+    }
+    $form->_params['createdMembershipIDs'] = array();
+
+    // CRM-7851 - Moved after processing Payment Errors
+    //@todo - the reasoning for this being here seems a little outdated
+    foreach ($createdMemberships as $createdMembership) {
+      CRM_Core_BAO_CustomValueTable::postProcess(
+        $form->_params,
+        'civicrm_membership',
+        $createdMembership->id,
+        'Membership'
+      );
+      $form->_params['createdMembershipIDs'][] = $createdMembership->id;
+    }
+    if (count($createdMemberships) == 1) {
+      //presumably this is only relevant for exactly 1 membership
+      $form->_params['membershipID'] = $createdMembership->id;
+    }
+
+    //CRM-15232: Check if membership is created and on the basis of it use
+    //membership receipt template to send payment receipt
+    if (count($createdMemberships)) {
+      $form->_values['isMembership'] = TRUE;
+    }
+    if (isset($membershipContributionID)) {
+      $form->_values['contribution_id'] = $membershipContributionID;
+    }
+    if ($form->_contributeMode) {
+      if ($form->_values['is_monetary'] && $form->_amount > 0.0 && !$form->_params['is_pay_later']) {
+        // call postProcess hook before leaving
+        $form->postProcessHook();
+      }
+      $payment = Civi\Payment\System::singleton()->getByProcessor($form->_paymentProcessor);
+      $result = $payment->doPayment($form->_params, 'contribute');
+
+      if (CRM_Utils_Array::value('payment_status_id', $result) == 1) {
+        // Refer to CRM-16737. Payment processors 'should' return payment_status_id
+        // to denote the outcome of the transaction.
+        try {
+          civicrm_api3('contribution', 'completetransaction', array(
+            'id' => $paymentResult['contribution']->id,
+            'trxn_id' => $paymentResult['contribution']->trxn_id,
+            'is_transactional' => FALSE,
+          ));
+        }
+        catch (CiviCRM_API3_Exception $e) {
+          // if for any reason it is already completed this will fail - e.g extensions hacking around core not completing transactions prior to CRM-15296
+          // so let's be gentle here
+          CRM_Core_Error::debug_log_message('contribution ' . $membershipContribution->id . ' not completed with trxn_id ' . $membershipContribution->trxn_id . ' and message ' . $e->getMessage());
+        }
+      }
+      // Do not send an email if Recurring transaction is done via Direct Mode
+      // Email will we sent when the IPN is received.
+      return;
+    }
+
+    //finally send an email receipt
+    CRM_Contribute_BAO_ContributionPage::sendMail($contactID,
+      $form->_values,
+      $isTest, FALSE,
+      $includeFieldTypes
+    );
+  }
+
+  /**
+   * Turn array of errors into message string.
+   *
+   * @param array $errors
+   *
+   * @return string
+   */
+  protected function compileErrorMessage($errors) {
+    foreach ($errors as $error) {
+      if (is_string($error)) {
+        $message[] = $error;
+      }
+    }
+    return ts('Payment Processor Error message') . ': ' . implode('<br/>', $message);
+  }
+
+  /**
+   * Where a second separate financial transaction is supported we will process it here.
+   *
+   * @param int $contactID
+   * @param CRM_Contribute_Form_Contribution_Confirm $form
+   * @param array $tempParams
+   * @param bool $isTest
+   * @param array $lineItems
+   * @param $minimumFee
+   * @param int $financialTypeID
+   *
+   * @throws CRM_Core_Exception
+   * @throws Exception
+   * @return CRM_Contribute_BAO_Contribution
+   */
+  protected function processSecondaryFinancialTransaction($contactID, &$form, $tempParams, $isTest, $lineItems, $minimumFee,
+                                                   $financialTypeID) {
+    $financialType = new CRM_Financial_DAO_FinancialType();
+    $financialType->id = $financialTypeID;
+    $financialType->find(TRUE);
+    $tempParams['amount'] = $minimumFee;
+    $tempParams['invoiceID'] = md5(uniqid(rand(), TRUE));
+
+    $result = NULL;
+    if ($form->_values['is_monetary'] && !$form->_params['is_pay_later'] && $minimumFee > 0.0) {
+      // At the moment our tests are calling this form in a way that leaves 'object' empty. For
+      // now we compensate here.
+      if (empty($form->_paymentProcessor['object'])) {
+        $payment = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor);
+      }
+      else {
+        $payment = $form->_paymentProcessor['object'];
+      }
+      $result = $payment->doPayment($tempParams, 'contribute');
+    }
+
+    //assign receive date when separate membership payment
+    //and contribution amount not selected.
+    if ($form->_amount == 0) {
+      $now = date('YmdHis');
+      $form->_params['receive_date'] = $now;
+      $receiveDate = CRM_Utils_Date::mysqlToIso($now);
+      $form->set('params', $form->_params);
+      $form->assign('receive_date', $receiveDate);
+    }
+
+    $form->set('membership_trx_id', $result['trxn_id']);
+    $form->set('membership_amount', $minimumFee);
+
+    $form->assign('membership_trx_id', $result['trxn_id']);
+    $form->assign('membership_amount', $minimumFee);
+
+    // we don't need to create the user twice, so lets disable cms_create_account
+    // irrespective of the value, CRM-2888
+    $tempParams['cms_create_account'] = 0;
+
+    //CRM-16165, scenarios are
+    // 1) If contribution is_pay_later and if contribution amount is > 0.0 we set pending = TRUE, vice-versa FALSE
+    // 2) If not pay later but auto-renewal membership is chosen then pending = TRUE as it later triggers
+    //   pending recurring contribution, vice-versa FALSE
+    $pending = $form->_params['is_pay_later'] ? (($minimumFee > 0.0) ? TRUE : FALSE) : (!empty($form->_params['auto_renew']) ? TRUE : FALSE);
+
+    //set this variable as we are not creating pledge for
+    //separate membership payment contribution.
+    //so for differentiating membership contribution from
+    //main contribution.
+    $form->_params['separate_membership_payment'] = 1;
+    $membershipContribution = CRM_Contribute_Form_Contribution_Confirm::processFormContribution($form,
+      $tempParams,
+      $result,
+      $contactID,
+      $financialType,
+      $pending,
+      TRUE,
+      $isTest,
+      $lineItems,
+      $form->_bltID
+    );
+    return $membershipContribution;
+  }
+
+  /**
+   * Is the payment a pending payment.
+   *
+   * We are moving towards always creating as pending and updating at the end (based on payment), so this should be
+   * an interim refactoring. It was shared with another unrelated form & some parameters may not apply to this form.
+   *
+   *
+   * @return bool
+   */
+  protected function getIsPending() {
+    if (((isset($this->_contributeMode)) || !empty
+        ($this->_params['is_pay_later'])
+      ) &&
+      (($this->_values['is_monetary'] && $this->_amount > 0.0))
+    ) {
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+  /**
+   * Are we going to do 2 financial transactions.
+   *
+   * Ie the membership block supports a separate transactions AND the contribution form has been configured for a
+   * contribution
+   * transaction AND a membership transaction AND the payment processor supports double financial transactions (ie. NOT doTransferPayment style)
+   *
+   * @param int $formID
+   * @param bool $amountBlockActiveOnForm
+   *
+   * @return bool
+   */
+  public function isSeparateMembershipTransaction($formID, $amountBlockActiveOnForm) {
+    $memBlockDetails = CRM_Member_BAO_Membership::getMembershipBlock($formID);
+    if (!empty($memBlockDetails['is_separate_payment']) && $amountBlockActiveOnForm) {
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+  /**
+   * This function sets the fields.
+   *
+   * - $this->_params['amount_level']
+   * - $this->_params['selectMembership']
+   * And under certain circumstances sets
+   * $this->_params['amount'] = null;
+   *
+   * @param int $priceSetID
+   */
+  public function setFormAmountFields($priceSetID) {
+    $isQuickConfig = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_params['priceSetId'], 'is_quick_config');
+    $priceField = new CRM_Price_DAO_PriceField();
+    $priceField->price_set_id = $priceSetID;
+    $priceField->orderBy('weight');
+    $priceField->find();
+    $paramWeDoNotUnderstand = NULL;
+
+    while ($priceField->fetch()) {
+      if ($priceField->name == "contribution_amount") {
+        $paramWeDoNotUnderstand = $priceField->id;
+      }
+      if ($isQuickConfig && !empty($this->_params["price_{$priceField->id}"])) {
+        if ($this->_values['fee'][$priceField->id]['html_type'] != 'Text') {
+          $this->_params['amount_level'] = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue',
+            $this->_params["price_{$priceField->id}"], 'label');
+        }
+        if ($priceField->name == "membership_amount") {
+          $this->_params['selectMembership'] = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue',
+            $this->_params["price_{$priceField->id}"], 'membership_type_id');
+        }
+      }
+      // If separate payment we set contribution amount to be null, so that it will not show contribution amount same
+      // as membership amount.
+      // @todo - this needs more documentation - it appears the setting to null is tied up with separate membership payments
+      // but the circumstances are very confusing. Many of these conditions are repeated in the next conditional
+      // so we should merge them together
+      // the quick config seems like a red-herring - if this is about a separate membership payment then there
+      // are 2 types of line items - membership ones & non-membership ones - regardless of whether quick config is set
+      elseif (
+        CRM_Utils_Array::value('is_separate_payment', $this->_membershipBlock)
+        && !empty($this->_values['fee'][$priceField->id])
+        && ($this->_values['fee'][$priceField->id]['name'] == "other_amount")
+        && CRM_Utils_Array::value("price_{$paramWeDoNotUnderstand}", $this->_params) < 1
+        && empty($this->_params["price_{$priceField->id}"])
+      ) {
+        $this->_params['amount'] = NULL;
+      }
+
+      // Fix for CRM-14375 - If we are using separate payments and "no
+      // thank you" is selected for the additional contribution, set
+      // contribution amount to be null, so that it will not show
+      // contribution amount same as membership amount.
+      //@todo - merge with section above
+      if ($this->_membershipBlock['is_separate_payment']
+        && !empty($this->_values['fee'][$priceField->id])
+        && CRM_Utils_Array::value('name', $this->_values['fee'][$priceField->id]) == 'contribution_amount'
+        && CRM_Utils_Array::value("price_{$priceField->id}", $this->_params) == '-1'
+      ) {
+        $this->_params['amount'] = NULL;
+      }
+    }
+  }
+
+  /**
+   * Submit function.
+   *
+   * @param array $params
+   *
+   * @throws CiviCRM_API3_Exception
+   */
+  public static function submit($params) {
+    $form = new CRM_Contribute_Form_Contribution_Confirm();
+    $form->_id = $params['id'];
+
+    CRM_Contribute_BAO_ContributionPage::setValues($form->_id, $form->_values);
+    $form->_separateMembershipPayment = CRM_Contribute_BAO_ContributionPage::getIsMembershipPayment($form->_id);
+    //this way the mocked up controller ignores the session stuff
+    $_SERVER['REQUEST_METHOD'] = 'GET';
+    $form->controller = new CRM_Contribute_Controller_Contribution();
+    $params['invoiceID'] = md5(uniqid(rand(), TRUE));
+    $paramsProcessedForForm = $form->_params = self::getFormParams($params['id'], $params);
+    $form->_amount = $params['amount'];
+    $priceSetID = $form->_params['priceSetId'] = $paramsProcessedForForm['price_set_id'];
+    $priceFields = CRM_Price_BAO_PriceSet::getSetDetail($priceSetID);
+    $priceSetFields = reset($priceFields);
+    $form->_values['fee'] = $priceSetFields['fields'];
+    $form->_priceSetId = $priceSetID;
+    $form->setFormAmountFields($priceSetID);
+    if (!empty($params['payment_processor_id'])) {
+      $form->_paymentProcessor = civicrm_api3('payment_processor', 'getsingle', array(
+        'id' => $params['payment_processor_id'],
+      ));
+      if ($form->_paymentProcessor['billing_mode'] == 1) {
+        $form->_contributeMode = 'direct';
+      }
+      else {
+        $form->_contributeMode = 'notify';
+      }
+    }
+    else {
+      $form->_params['payment_processor_id'] = 0;
+    }
+    $priceFields = $priceFields[$priceSetID]['fields'];
+    CRM_Price_BAO_PriceSet::processAmount($priceFields, $paramsProcessedForForm, $lineItems, 'civicrm_contribution');
+    $form->_lineItem = array($priceSetID => $lineItems);
+    $form->processFormSubmission(CRM_Utils_Array::value('contact_id', $params));
+  }
+
+  /**
+   * Helper function for static submit function.
+   *
+   * Set relevant params - help us to build up an array that we can pass in.
+   *
+   * @param int $id
+   * @param array $params
+   *
+   * @return array
+   * @throws CiviCRM_API3_Exception
+   */
+  public static function getFormParams($id, array $params) {
+    if (!isset($params['is_pay_later'])) {
+      if (!empty($params['payment_processor_id'])) {
+        $params['is_pay_later'] = 0;
+      }
+      else {
+        $params['is_pay_later'] = civicrm_api3('contribution_page', 'getvalue', array(
+          'id' => $id,
+          'return' => 'is_pay_later',
+        ));
+      }
+    }
+    if (empty($params['price_set_id'])) {
+      $params['price_set_id'] = CRM_Price_BAO_PriceSet::getFor('civicrm_contribution_page', $params['id']);
+    }
+    return $params;
+  }
+
+  /**
+   * Post form submission handling.
+   *
+   * This is also called from the test suite.
+   *
+   * @param int $contactID
+   *
+   * @return array
+   */
+  protected function processFormSubmission($contactID) {
+    $isPayLater = $this->_params['is_pay_later'];
+    if (isset($this->_params['payment_processor_id']) && $this->_params['payment_processor_id'] == 0) {
+      $this->_params['is_pay_later'] = $isPayLater = TRUE;
+    }
+    // add a description field at the very beginning
+    $this->_params['description'] = ts('Online Contribution') . ': ' . (($this->_pcpInfo['title']) ? $this->_pcpInfo['title'] : $this->_values['title']);
+
+    $this->_params['accountingCode'] = CRM_Utils_Array::value('accountingCode', $this->_values);
+
+    // fix currency ID
+    $this->_params['currencyID'] = CRM_Core_Config::singleton()->defaultCurrency;
+
+    //carry payment processor id.
+    if (CRM_Utils_Array::value('id', $this->_paymentProcessor)) {
+      $this->_params['payment_processor_id'] = $this->_paymentProcessor['id'];
+    }
+
+    $premiumParams = $membershipParams = $params = $this->_params;
+    if (!empty($params['image_URL'])) {
+      CRM_Contact_BAO_Contact::processImageParams($params);
+    }
+
+    $fields = array('email-Primary' => 1);
+
+    // get the add to groups
+    $addToGroups = array();
+
+    // now set the values for the billing location.
+    foreach ($this->_fields as $name => $value) {
+      $fields[$name] = 1;
+
+      // get the add to groups for uf fields
+      if (!empty($value['add_to_group_id'])) {
+        $addToGroups[$value['add_to_group_id']] = $value['add_to_group_id'];
+      }
+    }
+
+    if (!array_key_exists('first_name', $fields)) {
+      $nameFields = array('first_name', 'middle_name', 'last_name');
+      foreach ($nameFields as $name) {
+        $fields[$name] = 1;
+        if (array_key_exists("billing_$name", $params)) {
+          $params[$name] = $params["billing_{$name}"];
+          $params['preserveDBName'] = TRUE;
+        }
+      }
+    }
+
+    // billing email address
+    $fields["email-{$this->_bltID}"] = 1;
+
+    //unset the billing parameters if it is pay later mode
+    //to avoid creation of billing location
+    if ($isPayLater && !$this->_isBillingAddressRequiredForPayLater) {
+      $billingFields = array(
+        'billing_first_name',
+        'billing_middle_name',
+        'billing_last_name',
+        "billing_street_address-{$this->_bltID}",
+        "billing_city-{$this->_bltID}",
+        "billing_state_province-{$this->_bltID}",
+        "billing_state_province_id-{$this->_bltID}",
+        "billing_postal_code-{$this->_bltID}",
+        "billing_country-{$this->_bltID}",
+        "billing_country_id-{$this->_bltID}",
+      );
+
+      foreach ($billingFields as $value) {
+        unset($params[$value]);
+        unset($fields[$value]);
+      }
+    }
+
+    // if onbehalf-of-organization contribution, take out
+    // organization params in a separate variable, to make sure
+    // normal behavior is continued. And use that variable to
+    // process on-behalf-of functionality.
+    if (!empty($this->_params['hidden_onbehalf_profile'])) {
+      $behalfOrganization = array();
+      $orgFields = array('organization_name', 'organization_id', 'org_option');
+      foreach ($orgFields as $fld) {
+        if (array_key_exists($fld, $params)) {
+          $behalfOrganization[$fld] = $params[$fld];
+          unset($params[$fld]);
+        }
+      }
+
+      if (is_array($params['onbehalf']) && !empty($params['onbehalf'])) {
+        foreach ($params['onbehalf'] as $fld => $values) {
+          if (strstr($fld, 'custom_')) {
+            $behalfOrganization[$fld] = $values;
+          }
+          elseif (!(strstr($fld, '-'))) {
+            if (in_array($fld, array(
+              'contribution_campaign_id',
+              'member_campaign_id',
+            ))) {
+              $fld = 'campaign_id';
+            }
+            else {
+              $behalfOrganization[$fld] = $values;
+            }
+            $this->_params[$fld] = $values;
+          }
+        }
+      }
+
+      if (array_key_exists('onbehalf_location', $params) && is_array($params['onbehalf_location'])) {
+        foreach ($params['onbehalf_location'] as $block => $vals) {
+          //fix for custom data (of type checkbox, multi-select)
+          if (substr($block, 0, 7) == 'custom_') {
+            continue;
+          }
+          // fix the index of block elements
+          if (is_array($vals)) {
+            foreach ($vals as $key => $val) {
+              //dont adjust the index of address block as
+              //it's index is WRT to location type
+              $newKey = ($block == 'address') ? $key : ++$key;
+              $behalfOrganization[$block][$newKey] = $val;
+            }
+          }
+        }
+        unset($params['onbehalf_location']);
+      }
+      if (!empty($params['onbehalf[image_URL]'])) {
+        $behalfOrganization['image_URL'] = $params['onbehalf[image_URL]'];
+      }
+    }
+
+    // check for profile double opt-in and get groups to be subscribed
+    $subscribeGroupIds = CRM_Core_BAO_UFGroup::getDoubleOptInGroupIds($params, $contactID);
+
+    // since we are directly adding contact to group lets unset it from mailing
+    if (!empty($addToGroups)) {
+      foreach ($addToGroups as $groupId) {
+        if (isset($subscribeGroupIds[$groupId])) {
+          unset($subscribeGroupIds[$groupId]);
+        }
+      }
+    }
+
+    foreach ($addToGroups as $k) {
+      if (array_key_exists($k, $subscribeGroupIds)) {
+        unset($addToGroups[$k]);
+      }
+    }
+
+    if (empty($contactID)) {
+      $dupeParams = $params;
+      if (!empty($dupeParams['onbehalf'])) {
+        unset($dupeParams['onbehalf']);
+      }
+      if (!empty($dupeParams['honor'])) {
+        unset($dupeParams['honor']);
+      }
+
+      $dedupeParams = CRM_Dedupe_Finder::formatParams($dupeParams, 'Individual');
+      $dedupeParams['check_permission'] = FALSE;
+      $ids = CRM_Dedupe_Finder::dupesByParams($dedupeParams, 'Individual');
+
+      // if we find more than one contact, use the first one
+      $contactID = CRM_Utils_Array::value(0, $ids);
+
+      // Fetch default greeting id's if creating a contact
+      if (!$contactID) {
+        foreach (CRM_Contact_BAO_Contact::$_greetingTypes as $greeting) {
+          if (!isset($params[$greeting])) {
+            $params[$greeting] = CRM_Contact_BAO_Contact_Utils::defaultGreeting('Individual', $greeting);
+          }
+        }
+      }
+      $contactType = NULL;
+    }
+    else {
+      $contactType = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $contactID, 'contact_type');
+    }
+    $contactID = CRM_Contact_BAO_Contact::createProfileContact(
+      $params,
+      $fields,
+      $contactID,
+      $addToGroups,
+      NULL,
+      $contactType,
+      TRUE
+    );
+
+    // Make the contact ID associated with the contribution available at the Class level.
+    // Also make available to the session.
+    //@todo consider handling this in $this->getContactID();
+    $this->set('contactID', $contactID);
+    $this->_contactID = $contactID;
+
+    //get email primary first if exist
+    $subscriptionEmail = array('email' => CRM_Utils_Array::value('email-Primary', $params));
+    if (!$subscriptionEmail['email']) {
+      $subscriptionEmail['email'] = CRM_Utils_Array::value("email-{$this->_bltID}", $params);
+    }
+    // subscribing contact to groups
+    if (!empty($subscribeGroupIds) && $subscriptionEmail['email']) {
+      CRM_Mailing_Event_BAO_Subscribe::commonSubscribe($subscribeGroupIds, $subscriptionEmail, $contactID);
+    }
+
+    // If onbehalf-of-organization contribution / signup, add organization
+    // and it's location.
+    if (isset($params['hidden_onbehalf_profile']) && isset($behalfOrganization['organization_name'])) {
+      $ufFields = array();
+      foreach ($this->_fields['onbehalf'] as $name => $value) {
+        $ufFields[$name] = 1;
+      }
+      self::processOnBehalfOrganization($behalfOrganization, $contactID, $this->_values,
+        $this->_params, $ufFields
+      );
+    }
+    elseif (!empty($this->_membershipContactID) && $contactID != $this->_membershipContactID) {
+      // this is an onbehalf renew case for inherited membership. For e.g a permissioned member of household,
+      // store current user id as related contact for later use for mailing / activity..
+      $this->_values['related_contact'] = $contactID;
+      $this->_params['related_contact'] = $contactID;
+      // swap contact like we do for on-behalf-org case, so parent/primary membership is affected
+      $contactID = $this->_membershipContactID;
+    }
+
+    // lets store the contactID in the session
+    // for things like tell a friend
+    $session = CRM_Core_Session::singleton();
+    if (!$session->get('userID')) {
+      $session->set('transaction.userID', $contactID);
+    }
+    else {
+      $session->set('transaction.userID', NULL);
     }
 
-    return $params;
-  }
-
-  /**
-   * Process membership.
-   *
-   * @param array $membershipParams
-   * @param int $contactID
-   * @param array $customFieldsFormatted
-   * @param array $fieldTypes
-   * @param array $premiumParams
-   * @param array $membershipLineItems
-   *   Line items specifically relating to memberships.
-   * @param $isPayLater
-   */
-  public function processMembership($membershipParams, $contactID, $customFieldsFormatted, $fieldTypes, $premiumParams, $membershipLineItems, $isPayLater) {
-    try {
-      $membershipTypeIDs = (array) $membershipParams['selectMembership'];
-      $membershipTypes = CRM_Member_BAO_Membership::buildMembershipTypeValues($this, $membershipTypeIDs);
-      $membershipType = empty($membershipTypes) ? array() : reset($membershipTypes);
-      $this->assign('membership_name', CRM_Utils_Array::value('name', $membershipType));
+    $this->_useForMember = $this->get('useForMember');
 
-      $isPaidMembership = FALSE;
-      if ($this->_amount >= 0.0 && isset($membershipParams['amount'])) {
-        //amount must be greater than zero for
-        //adding contribution record  to contribution table.
-        //this condition arises when separate membership payment is
-        //enabled and contribution amount is not selected. fix for CRM-3010
-        $isPaidMembership = TRUE;
+    // store the fact that this is a membership and membership type is selected
+    if ((!empty($membershipParams['selectMembership']) &&
+        $membershipParams['selectMembership'] != 'no_thanks'
+      ) ||
+      $this->_useForMember
+    ) {
+      if (!$this->_useForMember) {
+        $this->assign('membership_assign', TRUE);
+        $this->set('membershipTypeID', $this->_params['selectMembership']);
       }
-      $isProcessSeparateMembershipTransaction = $this->isSeparateMembershipTransaction($this->_id, $this->_values['amount_block_is_active']);
 
-      if ($this->_values['amount_block_is_active']) {
-        $contributionTypeId = $this->_values['financial_type_id'];
+      if ($this->_action & CRM_Core_Action::PREVIEW) {
+        $membershipParams['is_test'] = 1;
       }
-      else {
-        $contributionTypeId = CRM_Utils_Array::value('financial_type_id', $membershipType, CRM_Utils_Array::value('financial_type_id', $membershipParams));
+      if ($this->_params['is_pay_later']) {
+        $membershipParams['is_pay_later'] = 1;
       }
 
-      CRM_Member_BAO_Membership::postProcessMembership($membershipParams, $contactID,
-        $this, $premiumParams, $customFieldsFormatted, $fieldTypes, $membershipType, $membershipTypeIDs, $isPaidMembership, $this->_membershipId, $isProcessSeparateMembershipTransaction, $contributionTypeId,
-        $membershipLineItems, $isPayLater
-      );
-      $this->assign('membership_assign', TRUE);
-      $this->set('membershipTypeID', $membershipParams['selectMembership']);
-    }
-    catch (CRM_Core_Exception $e) {
-      CRM_Core_Session::singleton()->setStatus($e->getMessage());
-      CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contribute/transact', "_qf_Main_display=true&qfKey={$this->_params['qfKey']}"));
-    }
-  }
+      //inherit campaign from contribution page.
+      if (!array_key_exists('campaign_id', $membershipParams)) {
+        $membershipParams['campaign_id'] = CRM_Utils_Array::value('campaign_id', $this->_values);
+      }
 
-  /**
-   * Are we going to do 2 financial transactions.
-   *
-   * Ie the membership block supports a separate transactions AND the contribution form has been configured for a
-   * contribution
-   * transaction AND a membership transaction AND the payment processor supports double financial transactions (ie. NOT doTransferPayment style)
-   *
-   * @param int $formID
-   * @param bool $amountBlockActiveOnForm
-   *
-   * @return bool
-   */
-  public function isSeparateMembershipTransaction($formID, $amountBlockActiveOnForm) {
-    $memBlockDetails = CRM_Member_BAO_Membership::getMembershipBlock($formID);
-    if (!empty($memBlockDetails['is_separate_payment']) && $amountBlockActiveOnForm) {
-      return TRUE;
+      CRM_Core_Payment_Form::mapParams($this->_bltID, $this->_params, $membershipParams, TRUE);
+      $this->doMembershipProcessing($contactID, $membershipParams, $premiumParams, $isPayLater);
     }
-    return FALSE;
-  }
-
-  /**
-   * This function sets the fields.
-   *
-   * - $this->_params['amount_level']
-   * - $this->_params['selectMembership']
-   * And under certain circumstances sets
-   * $this->_params['amount'] = null;
-   *
-   * @param int $priceSetID
-   */
-  public function setFormAmountFields($priceSetID) {
-    $isQuickConfig = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_params['priceSetId'], 'is_quick_config');
-    $priceField = new CRM_Price_DAO_PriceField();
-    $priceField->price_set_id = $priceSetID;
-    $priceField->orderBy('weight');
-    $priceField->find();
-    $paramWeDoNotUnderstand = NULL;
+    else {
+      // at this point we've created a contact and stored its address etc
+      // all the payment processors expect the name and address to be in the
+      // so we copy stuff over to first_name etc.
+      $paymentParams = $this->_params;
+      $contributionTypeId = $this->_values['financial_type_id'];
 
-    while ($priceField->fetch()) {
-      if ($priceField->name == "contribution_amount") {
-        $paramWeDoNotUnderstand = $priceField->id;
-      }
-      if ($isQuickConfig && !empty($this->_params["price_{$priceField->id}"])) {
-        if ($this->_values['fee'][$priceField->id]['html_type'] != 'Text') {
-          $this->_params['amount_level'] = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue',
-            $this->_params["price_{$priceField->id}"], 'label');
-        }
-        if ($priceField->name == "membership_amount") {
-          $this->_params['selectMembership'] = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue',
-            $this->_params["price_{$priceField->id}"], 'membership_type_id');
-        }
-      }
-      // If separate payment we set contribution amount to be null, so that it will not show contribution amount same
-      // as membership amount.
-      // @todo - this needs more documentation - it appears the setting to null is tied up with separate membership payments
-      // but the circumstances are very confusing. Many of these conditions are repeated in the next conditional
-      // so we should merge them together
-      // the quick config seems like a red-herring - if this is about a separate membership payment then there
-      // are 2 types of line items - membership ones & non-membership ones - regardless of whether quick config is set
-      elseif (
-        CRM_Utils_Array::value('is_separate_payment', $this->_membershipBlock)
-        && !empty($this->_values['fee'][$priceField->id])
-        && ($this->_values['fee'][$priceField->id]['name'] == "other_amount")
-        && CRM_Utils_Array::value("price_{$paramWeDoNotUnderstand}", $this->_params) < 1
-        && empty($this->_params["price_{$priceField->id}"])
+      $fieldTypes = array();
+      if (!empty($paymentParams['onbehalf']) &&
+        is_array($paymentParams['onbehalf'])
       ) {
-        $this->_params['amount'] = NULL;
+        foreach ($paymentParams['onbehalf'] as $key => $value) {
+          if (strstr($key, 'custom_')) {
+            $this->_params[$key] = $value;
+          }
+        }
+        $fieldTypes = array('Contact', 'Organization', 'Contribution');
       }
+      $financialTypeID = $this->wrangleFinancialTypeID($contributionTypeId);
 
-      // Fix for CRM-14375 - If we are using separate payments and "no
-      // thank you" is selected for the additional contribution, set
-      // contribution amount to be null, so that it will not show
-      // contribution amount same as membership amount.
-      //@todo - merge with section above
-      if ($this->_membershipBlock['is_separate_payment']
-        && !empty($this->_values['fee'][$priceField->id])
-        && CRM_Utils_Array::value('name', $this->_values['fee'][$priceField->id]) == 'contribution_amount'
-        && CRM_Utils_Array::value("price_{$priceField->id}", $this->_params) == '-1'
-      ) {
-        $this->_params['amount'] = NULL;
+      $result = CRM_Contribute_BAO_Contribution_Utils::processConfirm($this, $paymentParams,
+        $contactID,
+        $financialTypeID,
+        'contribution',
+        $fieldTypes,
+        ($this->_mode == 'test') ? 1 : 0,
+        $isPayLater
+      );
+
+      if (!empty($result['is_payment_failure'])) {
+        return $result;
+      }
+      // @todo move premium processing to complete transaction if it truly is an 'after' action.
+      $this->postProcessPremium($premiumParams, $result['contribution']);
+      if (CRM_Utils_Array::value('payment_status_id', $result) == 1) {
+        civicrm_api3('contribution', 'completetransaction', array(
+          'id' => $result['contribution']->id,
+          'trxn_id' => CRM_Utils_Array::value('trxn_id', $result),
+          )
+        );
       }
+      return $result;
     }
   }
 
   /**
-   * Submit function.
+   * Membership processing section.
    *
-   * @param array $params
+   * This is in a separate function as part of a move towards refactoring.
    *
-   * @throws CiviCRM_API3_Exception
+   * @param int $contactID
+   * @param array $membershipParams
+   * @param array $premiumParams
+   * @param bool $isPayLater
    */
-  public static function submit($params) {
-    $form = new CRM_Contribute_Form_Contribution_Confirm();
-    $form->_id = $params['id'];
-    if (!empty($params['contact_id'])) {
-      $form->_contactID = $params['contact_id'];
-    }
-    CRM_Contribute_BAO_ContributionPage::setValues($form->_id, $form->_values);
-    $form->_separateMembershipPayment = CRM_Contribute_BAO_ContributionPage::getIsMembershipPayment($form->_id);
-    //this way the mocked up controller ignores the session stuff
-    $_SERVER['REQUEST_METHOD'] = 'GET';
-    $form->controller = new CRM_Contribute_Controller_Contribution();
-    $params['invoiceID'] = md5(uniqid(rand(), TRUE));
-    $paramsProcessedForForm = $form->_params = self::getFormParams($params['id'], $params);
-    $form->_amount = $params['amount'];
-    $priceSetID = $form->_params['priceSetId'] = $paramsProcessedForForm['price_set_id'];
-    $priceFields = CRM_Price_BAO_PriceSet::getSetDetail($priceSetID);
-    $priceSetFields = reset($priceFields);
-    $form->_values['fee'] = $priceSetFields['fields'];
-    $form->_priceSetId = $priceSetID;
-    $form->setFormAmountFields($priceSetID);
-    if (!empty($params['payment_processor_id'])) {
-      $form->_paymentProcessor = civicrm_api3('payment_processor', 'getsingle', array(
-        'id' => $params['payment_processor_id'],
-      ));
-      if ($form->_paymentProcessor['billing_mode'] == 1) {
-        $form->_contributeMode = 'direct';
-      }
-      else {
-        $form->_contributeMode = 'notify';
-      }
+  protected function doMembershipProcessing($contactID, $membershipParams, $premiumParams, $isPayLater) {
+
+    // added new parameter for cms user contact id, needed to distinguish behaviour for on behalf of sign-ups
+    if (isset($this->_params['related_contact'])) {
+      $membershipParams['cms_contactID'] = $this->_params['related_contact'];
     }
     else {
-      $form->_params['payment_processor_id'] = 0;
+      $membershipParams['cms_contactID'] = $contactID;
     }
-    $priceFields = $priceFields[$priceSetID]['fields'];
-    CRM_Price_BAO_PriceSet::processAmount($priceFields, $paramsProcessedForForm, $lineItems, 'civicrm_contribution');
-    $form->_lineItem = array($priceSetID => $lineItems);
-    $form->postProcess();
-  }
 
-  /**
-   * Helper function for static submit function.
-   *
-   * Set relevant params - help us to build up an array that we can pass in.
-   *
-   * @param int $id
-   * @param array $params
-   *
-   * @return array
-   * @throws CiviCRM_API3_Exception
-   */
-  public static function getFormParams($id, array $params) {
-    if (!isset($params['is_pay_later'])) {
-      if (!empty($params['payment_processor_id'])) {
-        $params['is_pay_later'] = 0;
-      }
-      else {
-        $params['is_pay_later'] = civicrm_api3('contribution_page', 'getvalue', array(
-          'id' => $id,
-          'return' => 'is_pay_later',
-        ));
+    if (!empty($membershipParams['onbehalf']) &&
+      is_array($membershipParams['onbehalf']) && !empty($membershipParams['onbehalf']['member_campaign_id'])
+    ) {
+      $this->_params['campaign_id'] = $membershipParams['onbehalf']['member_campaign_id'];
+    }
+
+    $customFieldsFormatted = $fieldTypes = array();
+    if (!empty($membershipParams['onbehalf']) &&
+      is_array($membershipParams['onbehalf'])
+    ) {
+      foreach ($membershipParams['onbehalf'] as $key => $value) {
+        if (strstr($key, 'custom_')) {
+          $customFieldId = explode('_', $key);
+          CRM_Core_BAO_CustomField::formatCustomField(
+            $customFieldId[1],
+            $customFieldsFormatted,
+            $value,
+            'Membership',
+            NULL,
+            $contactID
+          );
+        }
       }
+      $fieldTypes = array('Contact', 'Organization', 'Membership');
     }
-    if (empty($params['price_set_id'])) {
-      $params['price_set_id'] = CRM_Price_BAO_PriceSet::getFor('civicrm_contribution_page', $params['id']);
+
+    $priceFieldIds = $this->get('memberPriceFieldIDS');
+
+    if (!empty($priceFieldIds)) {
+      $contributionTypeID = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $priceFieldIds['id'], 'financial_type_id');
+      unset($priceFieldIds['id']);
+      $membershipTypeIds = array();
+      $membershipTypeTerms = array();
+      foreach ($priceFieldIds as $priceFieldId) {
+        if ($id = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', $priceFieldId, 'membership_type_id')) {
+          $membershipTypeIds[] = $id;
+          //@todo the value for $term is immediately overwritten. It is unclear from the code whether it was intentional to
+          // do this or a double = was intended (this ambiguity is the reason many IDEs complain about 'assignment in condition'
+          $term = 1;
+          if ($term = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', $priceFieldId, 'membership_num_terms')) {
+            $membershipTypeTerms[$id] = ($term > 1) ? $term : 1;
+          }
+          else {
+            $membershipTypeTerms[$id] = 1;
+          }
+        }
+      }
+      $membershipParams['selectMembership'] = $membershipTypeIds;
+      $membershipParams['financial_type_id'] = $contributionTypeID;
+      $membershipParams['types_terms'] = $membershipTypeTerms;
+    }
+    if (!empty($membershipParams['selectMembership'])) {
+      // CRM-12233
+      $membershipLineItems = array();
+      if ($this->_separateMembershipPayment && $this->_values['amount_block_is_active']) {
+        foreach ($this->_values['fee'] as $key => $feeValues) {
+          if ($feeValues['name'] == 'membership_amount') {
+            $fieldId = $this->_params['price_' . $key];
+            $membershipLineItems[$this->_priceSetId][$fieldId] = $this->_lineItem[$this->_priceSetId][$fieldId];
+            unset($this->_lineItem[$this->_priceSetId][$fieldId]);
+            break;
+          }
+        }
+      }
+      $this->processMembership($membershipParams, $contactID, $customFieldsFormatted, $fieldTypes, $premiumParams, $membershipLineItems, $isPayLater);
+      if (!$this->_amount > 0.0 || !$membershipParams['amount']) {
+        // we need to explicitly create a CMS user in case of free memberships
+        // since it is done under processConfirm for paid memberships
+        CRM_Contribute_BAO_Contribution_Utils::createCMSUser($membershipParams,
+          $membershipParams['cms_contactID'],
+          'email-' . $this->_bltID
+        );
+      }
     }
-    return $params;
   }
 
 }