CRM-17294 renamed fields not passed to processor
[civicrm-core.git] / CRM / Contribute / Form / Contribution / Confirm.php
index 69856982eaaeb3a05c498a879ac1bda3dfaa81d7..b367b2f4f556e4cd8cb1e3fb8c774e1e8be58b69 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  +--------------------------------------------------------------------+
- | CiviCRM version 4.6                                                |
+ | CiviCRM version 4.7                                                |
  +--------------------------------------------------------------------+
  | Copyright CiviCRM LLC (c) 2004-2015                                |
  +--------------------------------------------------------------------+
@@ -29,8 +29,6 @@
  *
  * @package CRM
  * @copyright CiviCRM LLC (c) 2004-2015
- * $Id$
- *
  */
 
 /**
@@ -57,31 +55,20 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
    * Set the parameters to be passed to contribution create function.
    *
    * @param array $params
-   * @param int $contactID
    * @param int $financialTypeID
-   * @param $online
-   * @param int $contributionPageId
-   * @param $nonDeductibleAmount
-   * @param int $campaignId
-   * @param $isMonetary
-   * @param $pending
-   * @param $paymentProcessorOutcome
-   * @param $receiptDate
+   * @param float $nonDeductibleAmount
+   * @param bool $pending
+   * @param array $paymentProcessorOutcome
+   * @param string $receiptDate
    * @param int $recurringContributionID
-   * @param $isTest
-   * @param int $addressID
-   * @param int $softCreditToID
-   * @param array $lineItems
    *
    * @return array
    */
   public static function getContributionParams(
-    $params, $contactID, $financialTypeID, $online, $contributionPageId, $nonDeductibleAmount, $campaignId, $isMonetary, $pending,
-    $paymentProcessorOutcome, $receiptDate, $recurringContributionID, $isTest, $addressID, $softCreditToID, $lineItems) {
+    $params, $financialTypeID, $nonDeductibleAmount, $pending,
+    $paymentProcessorOutcome, $receiptDate, $recurringContributionID) {
     $contributionParams = array(
-      'contact_id' => $contactID,
       'financial_type_id' => $financialTypeID,
-      'contribution_page_id' => $contributionPageId,
       'receive_date' => (CRM_Utils_Array::value('receive_date', $params)) ? CRM_Utils_Date::processDate($params['receive_date']) : date('YmdHis'),
       'non_deductible_amount' => $nonDeductibleAmount,
       'total_amount' => $params['amount'],
@@ -89,29 +76,16 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
       'amount_level' => CRM_Utils_Array::value('amount_level', $params),
       'invoice_id' => $params['invoiceID'],
       'currency' => $params['currencyID'],
-      'source' => (!$online || !empty($params['source'])) ? CRM_Utils_Array::value('source', $params) : CRM_Utils_Array::value('description', $params),
       'is_pay_later' => CRM_Utils_Array::value('is_pay_later', $params, 0),
       //configure cancel reason, cancel date and thankyou date
       //from 'contribution' type profile if included
       'cancel_reason' => CRM_Utils_Array::value('cancel_reason', $params, 0),
       'cancel_date' => isset($params['cancel_date']) ? CRM_Utils_Date::format($params['cancel_date']) : NULL,
       'thankyou_date' => isset($params['thankyou_date']) ? CRM_Utils_Date::format($params['thankyou_date']) : NULL,
-      'campaign_id' => $campaignId,
-      'is_test' => $isTest,
-      'address_id' => $addressID,
       //setting to make available to hook - although seems wrong to set on form for BAO hook availability
-      'soft_credit_to' => $softCreditToID,
-      'line_item' => $lineItems,
       'skipLineItem' => CRM_Utils_Array::value('skipLineItem', $params, 0),
     );
-    if (!$online && isset($params['thankyou_date'])) {
-      $contributionParam['thankyou_date'] = $params['thankyou_date'];
-    }
-    if (!$online || $isMonetary) {
-      if (empty($params['is_pay_later'])) {
-        $contributionParams['payment_instrument_id'] = 1;
-      }
-    }
+
     if ($paymentProcessorOutcome) {
       $contributionParams['payment_processor'] = CRM_Utils_Array::value('payment_processor', $paymentProcessorOutcome);
     }
@@ -148,103 +122,104 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
   }
 
   /**
-   * Set variables up before form is built.
+   * Get non-deductible amount.
+   *
+   * This is a bit too much about wierd form interpretation to be this deep.
+   *
+   * CRM-11885
+   *  if non_deductible_amount exists i.e. Additional Details fieldset was opened [and staff typed something] -> keep
+   * it.
+   *
+   * @param array $params
+   * @param CRM_Financial_BAO_FinancialType $financialType
+   * @param bool $online
    *
-   * @return void
+   * @return array
    */
-  public function preProcess() {
-    $config = CRM_Core_Config::singleton();
-    parent::preProcess();
-
-    // lineItem isn't set until Register postProcess
-    $this->_lineItem = $this->get('lineItem');
-    $this->_paymentProcessor = $this->get('paymentProcessor');
-
-    if ($this->_contributeMode == 'express') {
-      // rfp == redirect from paypal
-      $rfp = CRM_Utils_Request::retrieve('rfp', 'Boolean',
-        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'));
-
-        $this->_params['payer'] = CRM_Utils_Array::value('payer', $expressParams);
-        $this->_params['payer_id'] = $expressParams['payer_id'];
-        $this->_params['payer_status'] = $expressParams['payer_status'];
-
-        CRM_Core_Payment_Form::mapParams($this->_bltID, $expressParams, $this->_params, FALSE);
-
-        // fix state and country id if present
-        if (!empty($this->_params["billing_state_province_id-{$this->_bltID}"])) {
-          $this->_params["billing_state_province-{$this->_bltID}"] = CRM_Core_PseudoConstant::stateProvinceAbbreviation($this->_params["billing_state_province_id-{$this->_bltID}"]);
-        }
-        if (!empty($this->_params["billing_country_id-{$this->_bltID}"]) && $this->_params["billing_country_id-{$this->_bltID}"]) {
-          $this->_params["billing_country-{$this->_bltID}"] = CRM_Core_PseudoConstant::countryIsoCode($this->_params["billing_country_id-{$this->_bltID}"]);
+  protected static function getNonDeductibleAmount($params, $financialType, $online) {
+    if (isset($params['non_deductible_amount']) && (!empty($params['non_deductible_amount']))) {
+      return $params['non_deductible_amount'];
+    }
+    else {
+      if ($financialType->is_deductible) {
+        if ($online && isset($params['selectProduct'])) {
+          $selectProduct = CRM_Utils_Array::value('selectProduct', $params);
         }
-
-        // set a few other parameters for PayPal
-        $this->_params['token'] = $this->get('token');
-
-        $this->_params['amount'] = $this->get('amount');
-
-        if (!empty($this->_membershipBlock)) {
-          $this->_params['selectMembership'] = $this->get('selectMembership');
+        if (!$online && isset($params['product_name'][0])) {
+          $selectProduct = $params['product_name'][0];
         }
-        // we use this here to incorporate any changes made by folks in hooks
-        $this->_params['currencyID'] = $config->defaultCurrency;
-
-        $this->_params['payment_action'] = 'Sale';
-
-        // also merge all the other values from the profile fields
-        $values = $this->controller->exportValues('Main');
-        $skipFields = array(
-          'amount',
-          'amount_other',
-          "billing_street_address-{$this->_bltID}",
-          "billing_city-{$this->_bltID}",
-          "billing_state_province_id-{$this->_bltID}",
-          "billing_postal_code-{$this->_bltID}",
-          "billing_country_id-{$this->_bltID}",
-        );
-        foreach ($values as $name => $value) {
-          // skip amount field
-          if (!in_array($name, $skipFields)) {
-            $this->_params[$name] = $value;
+        // if there is a product - compare the value to the contribution amount
+        if (isset($selectProduct) &&
+          $selectProduct != 'no_thanks'
+        ) {
+          $productDAO = new CRM_Contribute_DAO_Product();
+          $productDAO->id = $selectProduct;
+          $productDAO->find(TRUE);
+          // product value exceeds contribution amount
+          if ($params['amount'] < $productDAO->price) {
+            $nonDeductibleAmount = $params['amount'];
+            return $nonDeductibleAmount;
+          }
+          // product value does NOT exceed contribution amount
+          else {
+            return $productDAO->price;
           }
         }
-        $this->set('getExpressCheckoutDetails', $this->_params);
+        // contribution is deductible - but there is no product
+        else {
+          return '0.00';
+        }
       }
+      // contribution is NOT deductible
       else {
-        $this->_params = $this->get('getExpressCheckoutDetails');
+        return $params['amount'];
       }
     }
-    else {
-      $this->_params = $this->controller->exportValues('Main');
+  }
 
-      if (!empty($this->_params["billing_state_province_id-{$this->_bltID}"])) {
-        $this->_params["billing_state_province-{$this->_bltID}"] = CRM_Core_PseudoConstant::stateProvinceAbbreviation($this->_params["billing_state_province_id-{$this->_bltID}"]);
-      }
-      if (!empty($this->_params["billing_country_id-{$this->_bltID}"])) {
-        $this->_params["billing_country-{$this->_bltID}"] = CRM_Core_PseudoConstant::countryIsoCode($this->_params["billing_country_id-{$this->_bltID}"]);
-      }
+  /**
+   * Set variables up before form is built.
+   */
+  public function preProcess() {
+    $config = CRM_Core_Config::singleton();
+    parent::preProcess();
 
-      if (isset($this->_params['credit_card_exp_date'])) {
-        $this->_params['year'] = CRM_Core_Payment_Form::getCreditCardExpirationYear($this->_params);
-        $this->_params['month'] = CRM_Core_Payment_Form::getCreditCardExpirationMonth($this->_params);
-      }
+    // lineItem isn't set until Register postProcess
+    $this->_lineItem = $this->get('lineItem');
+    $this->_paymentProcessor = $this->get('paymentProcessor');
+    $this->_params = $this->controller->exportValues('Main');
+    $this->_params['ip_address'] = CRM_Utils_System::ipAddress();
+    $this->_params['amount'] = $this->get('amount');
+    if (isset($this->_params['amount'])) {
+      $this->setFormAmountFields($this->_params['priceSetId']);
+    }
+
+    $this->_params['tax_amount'] = $this->get('tax_amount');
+    $this->_useForMember = $this->get('useForMember');
 
-      $this->_params['ip_address'] = CRM_Utils_System::ipAddress();
-      $this->_params['amount'] = $this->get('amount');
-      $this->_params['tax_amount'] = $this->get('tax_amount');
+    if (isset($this->_params['credit_card_exp_date'])) {
+      $this->_params['year'] = CRM_Core_Payment_Form::getCreditCardExpirationYear($this->_params);
+      $this->_params['month'] = CRM_Core_Payment_Form::getCreditCardExpirationMonth($this->_params);
+    }
 
-      $this->_useForMember = $this->get('useForMember');
+    $this->_params['currencyID'] = $config->defaultCurrency;
 
-      if (isset($this->_params['amount'])) {
-        $this->setFormAmountFields($this->_params['priceSetId']);
-      }
-      $this->_params['currencyID'] = $config->defaultCurrency;
-      $this->_params['payment_action'] = 'Sale';
+    if (!empty($this->_membershipBlock)) {
+      $this->_params['selectMembership'] = $this->get('selectMembership');
+    }
+    if (!empty($this->_paymentProcessor) &&  $this->_paymentProcessor['object']->supports('preApproval')) {
+      $preApprovalParams = $this->_paymentProcessor['object']->getPreApprovalDetails($this->get('pre_approval_parameters'));
+      $this->_params = array_merge($this->_params, $preApprovalParams);
+    }
+
+    // We may have fetched some billing details from the getPreApprovalDetails function so we
+    // want to ensure we set this after that function has been called.
+    CRM_Core_Payment_Form::mapParams($this->_bltID, $this->_params, $this->_params, FALSE);
+    if (!empty($this->_params["billing_state_province_id-{$this->_bltID}"])) {
+      $this->_params["billing_state_province-{$this->_bltID}"] = CRM_Core_PseudoConstant::stateProvinceAbbreviation($this->_params["billing_state_province_id-{$this->_bltID}"]);
+    }
+    if (!empty($this->_params["billing_country_id-{$this->_bltID}"])) {
+      $this->_params["billing_country-{$this->_bltID}"] = CRM_Core_PseudoConstant::countryIsoCode($this->_params["billing_country_id-{$this->_bltID}"]);
     }
 
     $this->_params['is_pay_later'] = $this->get('is_pay_later');
@@ -253,16 +228,9 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
       $this->assign('pay_later_receipt', $this->_values['pay_later_receipt']);
     }
     // if onbehalf-of-organization
-    if (!empty($this->_params['hidden_onbehalf_profile'])) {
+    if (!empty($this->_values['onbehalf_profile_id']) && !empty($this->_params['onbehalf']['organization_name'])) {
       // CRM-15182
-      if (empty($this->_params['org_option']) && empty($this->_params['organization_id'])) {
-        if (!empty($this->_params['onbehalfof_id'])) {
-          $this->_params['organization_id'] = $this->_params['onbehalfof_id'];
-        }
-        else {
-          $this->_params['organization_id'] = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $this->_params['onbehalf']['organization_name'], 'id', 'display_name');
-        }
-      }
+      $this->_params['organization_id'] = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $this->_params['onbehalf']['organization_name'], 'id', 'display_name');
 
       $this->_params['organization_name'] = $this->_params['onbehalf']['organization_name'];
       $addressBlocks = array(
@@ -401,27 +369,7 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
         }
       }
     }
-
-    // if auto renew checkbox is set, initiate a open-ended recurring membership
-    if ((!empty($this->_params['selectMembership']) || !empty($this->_params['priceSetId'])) && !empty($this->_paymentProcessor['is_recur']) &&
-      CRM_Utils_Array::value('auto_renew', $this->_params) && empty($this->_params['is_recur']) && empty($this->_params['frequency_interval'])
-    ) {
-
-      $this->_params['is_recur'] = $this->_values['is_recur'] = 1;
-      // check if price set is not quick config
-      if (!empty($this->_params['priceSetId']) && !CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_params['priceSetId'], 'is_quick_config')) {
-        list($this->_params['frequency_interval'], $this->_params['frequency_unit']) = CRM_Price_BAO_PriceSet::getRecurDetails($this->_params['priceSetId']);
-      }
-      else {
-        // FIXME: set interval and unit based on selected membership type
-        $this->_params['frequency_interval'] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipType',
-          $this->_params['selectMembership'], 'duration_interval'
-        );
-        $this->_params['frequency_unit'] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipType',
-          $this->_params['selectMembership'], 'duration_unit'
-        );
-      }
-    }
+    $this->setRecurringMembershipParams();
 
     if ($this->_pcpId) {
       $params = $this->processPcp($this, $this->_params);
@@ -442,25 +390,22 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
 
   /**
    * Build the form object.
-   *
-   * @return void
    */
   public function buildQuickForm() {
     $this->assignToTemplate();
 
     $params = $this->_params;
     // make sure we have values for it
-    if ($this->_honor_block_is_active && !empty($params['soft_credit_type_id'])) {
+    if (!empty($this->_values['honoree_profile_id']) && !empty($params['soft_credit_type_id'])) {
       $honorName = NULL;
       $softCreditTypes = CRM_Core_OptionGroup::values("soft_credit_type", FALSE);
 
-      $this->assign('honor_block_is_active', $this->_honor_block_is_active);
       $this->assign('soft_credit_type', $softCreditTypes[$params['soft_credit_type_id']]);
-      CRM_Contribute_BAO_ContributionSoft::formatHonoreeProfileFields($this, $params['honor'], $params['honoree_profile_id']);
+      CRM_Contribute_BAO_ContributionSoft::formatHonoreeProfileFields($this, $params['honor']);
 
       $fieldTypes = array('Contact');
-      $fieldTypes[] = CRM_Core_BAO_UFGroup::getContactType($params['honoree_profile_id']);
-      $this->buildCustom($params['honoree_profile_id'], 'honoreeProfileFields', TRUE, 'honor', $fieldTypes);
+      $fieldTypes[] = CRM_Core_BAO_UFGroup::getContactType($this->_values['honoree_profile_id']);
+      $this->buildCustom($this->_values['honoree_profile_id'], 'honoreeProfileFields', TRUE, 'honor', $fieldTypes);
     }
     $this->assign('receiptFromEmail', CRM_Utils_Array::value('receipt_from_email', $this->_values));
     $amount_block_is_active = $this->get('amount_block_is_active');
@@ -498,8 +443,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'],
@@ -516,15 +460,7 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
     $this->buildCustom($this->_values['custom_pre_id'], 'customPre', TRUE);
     $this->buildCustom($this->_values['custom_post_id'], 'customPost', TRUE);
 
-    if (!empty($params['hidden_onbehalf_profile'])) {
-      $ufJoinParams = array(
-        'module' => 'onBehalf',
-        'entity_table' => 'civicrm_contribution_page',
-        'entity_id' => $this->_id,
-      );
-      $OnBehalfProfile = CRM_Core_BAO_UFJoin::getUFGroupIds($ufJoinParams);
-      $profileId = $OnBehalfProfile[0];
-
+    if (!empty($this->_values['onbehalf_profile_id']) && !empty($params['onbehalf'])) {
       $fieldTypes = array('Contact', 'Organization');
       $contactSubType = CRM_Contact_BAO_ContactType::subTypes('Organization');
       $fieldTypes = array_merge($fieldTypes, $contactSubType);
@@ -535,7 +471,7 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
         $fieldTypes = array_merge($fieldTypes, array('Contribution'));
       }
 
-      $this->buildCustom($profileId, 'onbehalfProfile', TRUE, 'onbehalf', $fieldTypes);
+      $this->buildCustom($this->_values['onbehalf_profile_id'], 'onbehalfProfile', TRUE, 'onbehalf', $fieldTypes);
     }
 
     $this->_separateMembershipPayment = $this->get('separateMembershipPayment');
@@ -649,8 +585,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
    */
@@ -668,1413 +605,1708 @@ 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() {
   }
 
   /**
    * Process the form.
-   *
-   * @return void
    */
   public function postProcess() {
     $contactID = $this->getContactID();
-    $isPayLater = $this->_params['is_pay_later'];
-    if (isset($this->_params['payment_processor']) && $this->_params['payment_processor'] == 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']);
-
-    // also add accounting code
-    $this->_params['accountingCode'] = CRM_Utils_Array::value('accountingCode',
-      $this->_values
-    );
-
-    // fix currency ID
-    $this->_params['currencyID'] = CRM_Core_Config::singleton()->defaultCurrency;
+    $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']}"
+      ));
+    }
+    // Presumably this is for hooks to access? Not quite clear & perhaps not required.
+    $this->set('params', $this->_params);
+  }
 
-    //carry payment processor id.
-    if ($paymentProcessorId = CRM_Utils_Array::value('id', $this->_paymentProcessor)) {
-      $this->_params['payment_processor_id'] = $paymentProcessorId;
+  /**
+   * 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'];
     }
-    $premiumParams = $membershipParams = $params = $this->_params;
-    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'
+      );
     }
-    $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;
+
+          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 (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;
-          }
-        }
-      }
+      $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);
 
-      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']);
+      //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);
+  /**
+   * Process the contribution.
+   *
+   * @param CRM_Core_Form $form
+   * @param array $params
+   * @param array $result
+   * @param array $contributionParams
+   *   Parameters to be passed to contribution create action.
+   *   This differs from params in that we are currently adding params to it and 1) ensuring they are being
+   *   passed consistently & 2) documenting them here.
+   *   - contact_id
+   *   - line_item
+   *   - is_test
+   *   - campaign_id
+   *   - contribution_page_id
+   *   - source
+   *   - payment_type_id
+   *   - thankyou_date (not all forms will set this)
+   *
+   * @param CRM_Financial_DAO_FinancialType $financialType
+   * @param bool $online
+   *   Is the form a front end form? If so set a bunch of unpredictable things that should be passed in from the form.
+   *
+   * @param int $billingLocationID
+   *   ID of billing location type.
+   *
+   * @return \CRM_Contribute_DAO_Contribution
+   * @throws \Exception
+   */
+  public static function processFormContribution(
+    &$form,
+    $params,
+    $result,
+    $contributionParams,
+    $financialType,
+    $online,
+    $billingLocationID
+  ) {
+    $transaction = new CRM_Core_Transaction();
+    $contactID = $contributionParams['contact_id'];
 
-      // 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;
+    $isEmailReceipt = !empty($form->_values['is_email_receipt']);
+    $isSeparateMembershipPayment = empty($params['separate_membership_payment']) ? FALSE : TRUE;
+    $pledgeID = empty($params['pledge_id']) ? NULL : $params['pledge_id'];
+    if (!$isSeparateMembershipPayment && !empty($form->_values['pledge_block_id']) &&
+      (!empty($params['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);
+    $contributionParams['address_id'] = CRM_Contribute_BAO_Contribution::createAddress($params, $billingLocationID);
+
+    //@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;
     }
-    // subscribing contact to groups
-    if (!empty($subscribeGroupIds) && $subscriptionEmail['email']) {
-      CRM_Mailing_Event_BAO_Subscribe::commonSubscribe($subscribeGroupIds, $subscriptionEmail, $contactID);
+    $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;
     }
 
-    // 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
+    if (isset($params['amount'])) {
+      $contributionParams = array_merge(self::getContributionParams(
+        $params, $financialType->id, $nonDeductibleAmount, TRUE,
+        $result, $receiptDate,
+        $recurringContributionID), $contributionParams
       );
-    }
-    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;
-    }
+      $contribution = CRM_Contribute_BAO_Contribution::add($contributionParams);
 
-    // 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);
+      $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();
+        // @todo - interrogate the line items passed in on the params array.
+        // No reason to assume line items will be set on the form.
+        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);
+      }
+
+      // lets store it in the form variable so postProcess hook can get to this and use it
+      $form->_contributionID = $contribution->id;
     }
 
-    $this->_useForMember = $this->get('useForMember');
+    // process soft credit / pcp params first
+    CRM_Contribute_BAO_ContributionSoft::formatSoftCreditParams($params, $form);
 
-    // 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;
+    //CRM-13981, processing honor contact into soft-credit contribution
+    CRM_Contribute_BAO_ContributionSoft::processSoftContribution($params, $contribution);
 
-      if (!$this->_useForMember) {
-        $this->assign('membership_assign', TRUE);
-        $this->set('membershipTypeID', $this->_params['selectMembership']);
-      }
+    //handle pledge stuff.
+    if ($isPledge) {
+      if ($pledgeID) {
+        //when user doing pledge payments.
+        //update the schedule when payment(s) are made
+        foreach ($params['pledge_amount'] as $paymentId => $dontCare) {
+          $scheduledAmount = CRM_Core_DAO::getFieldValue(
+            'CRM_Pledge_DAO_PledgePayment',
+            $paymentId,
+            'scheduled_amount',
+            'id'
+          );
 
-      if ($this->_action & CRM_Core_Action::PREVIEW) {
-        $membershipParams['is_test'] = 1;
-      }
-      if ($this->_params['is_pay_later']) {
-        $membershipParams['is_pay_later'] = 1;
-      }
-    }
+          $pledgePaymentParams = array(
+            'id' => $paymentId,
+            'contribution_id' => $contribution->id,
+            'status_id' => $contribution->contribution_status_id,
+            'actual_amount' => $scheduledAmount,
+          );
 
-    if ($processMembership) {
-      CRM_Core_Payment_Form::mapParams($this->_bltID, $this->_params, $membershipParams, TRUE);
+          CRM_Pledge_BAO_PledgePayment::add($pledgePaymentParams);
+        }
 
-      // 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'];
+        //update pledge status according to the new payment statuses
+        CRM_Pledge_BAO_PledgePayment::updatePledgePaymentStatus($pledgeID);
       }
       else {
-        $membershipParams['cms_contactID'] = $contactID;
-      }
-
-      //inherit campaign from contribution page.
-      if (!array_key_exists('campaign_id', $membershipParams)) {
-        $membershipParams['campaign_id'] = CRM_Utils_Array::value('campaign_id', $this->_values);
+        //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'] = CRM_Utils_Array::value('campaign_id', $contributionParams);
+
+        $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 (!empty($membershipParams['onbehalf']) &&
-        is_array($membershipParams['onbehalf']) && !empty($membershipParams['onbehalf']['member_campaign_id'])
+    if ($online && $contribution) {
+      CRM_Core_BAO_CustomValueTable::postProcess($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')
       ) {
-        $this->_params['campaign_id'] = $membershipParams['onbehalf']['member_campaign_id'];
+        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'),
+      );
 
-      $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;
-            }
-          }
-        }
-        $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
-          );
+      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) {
+    $isNotCurrentEmployer = FALSE;
+    $dupeIDs = array();
+    $orgID = NULL;
+    if (!empty($behalfOrganization['organization_id'])) {
+      $orgID = $behalfOrganization['organization_id'];
+      unset($behalfOrganization['organization_id']);
+    }
+    // create employer relationship with $contactID only when new organization is there
+    // else retain the existing relationship
+    else {
+      // get the Employee relationship type id
+      $relTypeId = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_RelationshipType', 'Employee of', 'id', 'name_a_b');
+
+      // keep relationship params ready
+      $relParams['relationship_type_id'] = $relTypeId . '_a_b';
+      $relParams['is_permission_a_b'] = 1;
+      $relParams['is_active'] = 1;
+      $isNotCurrentEmployer = TRUE;
+    }
+
+    // formalities for creating / editing organization.
+    $behalfOrganization['contact_type'] = 'Organization';
+
+    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']);
+      }
     }
     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'];
+      // if found permissioned related organization, allow location edit
+      $behalfOrganization['contact_id'] = $orgID;
+      // don't allow name edit
+      unset($behalfOrganization['organization_name']);
+    }
+
+    // handling for image url
+    if (!empty($behalfOrganization['image_URL'])) {
+      CRM_Contact_BAO_Contact::processImageParams($behalfOrganization);
+    }
+
+    // create organization, add location
+    $orgID = CRM_Contact_BAO_Contact::createProfileContact($behalfOrganization, $fields, $orgID,
+      NULL, NULL, 'Organization'
+    );
+    // create relationship
+    if ($isNotCurrentEmployer) {
+      $relParams['contact_check'][$orgID] = 1;
+      $cid = array('contact' => $contactID);
+      CRM_Contact_BAO_Relationship::legacyCreateMultiple($relParams, $cid);
+    }
+
+    // if multiple match - send a duplicate alert
+    if ($dupeIDs && (count($dupeIDs) > 1)) {
+      $values['onbehalf_dupe_alert'] = 1;
+      // required for IPN
+      $params['onbehalf_dupe_alert'] = 1;
+    }
+
+    // make sure organization-contact-id is considered for recording
+    // contribution/membership etc..
+    if ($contactID != $orgID) {
+      // take a note of contact-id, so we can send the
+      // receipt to individual contact as well.
+
+      // required for mailing/template display ..etc
+      $values['related_contact'] = $contactID;
 
-      $fieldTypes = array();
-      if (!empty($paymentParams['onbehalf']) &&
-        is_array($paymentParams['onbehalf'])
+      //make this employee of relationship as current
+      //employer / employee relationship,  CRM-3532
+      if ($isNotCurrentEmployer &&
+        ($orgID != CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $contactID, 'employer_id'))
       ) {
-        foreach ($paymentParams['onbehalf'] as $key => $value) {
-          if (strstr($key, 'custom_')) {
-            $this->_params[$key] = $value;
-          }
-        }
-        $fieldTypes = array('Contact', 'Organization', 'Contribution');
+        $isNotCurrentEmployer = FALSE;
       }
-      $financialTypeID = $this->wrangleFinancialTypeID($contributionTypeId);
 
-      CRM_Contribute_BAO_Contribution_Utils::processConfirm($this, $paymentParams,
-        $premiumParams, $contactID,
-        $financialTypeID,
-        'contribution',
-        $fieldTypes,
-        ($this->_mode == 'test') ? 1 : 0,
-        $isPayLater
-      );
+      if (!$isNotCurrentEmployer && $orgID) {
+        //build current employer params
+        $currentEmpParams[$contactID] = $orgID;
+        CRM_Contact_BAO_Contact_Utils::setCurrentEmployer($currentEmpParams);
+      }
+
+      // contribution / signup will be done using this
+      // organization id.
+      $contactID = $orgID;
     }
   }
 
   /**
-   * 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.
+   * Function used to send notification mail to pcp owner.
    *
-   * @param int $contributionTypeId
+   * This is used by contribution and also event PCPs.
    *
-   * @return null|string
+   * @param object $contribution
+   * @param object $contributionSoft
+   *   Contribution object.
    */
-  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'
+  public static function pcpNotifyOwner($contribution, $contributionSoft) {
+    $params = array('id' => $contributionSoft->pcp_id);
+    CRM_Core_DAO::commonRetrieve('CRM_PCP_DAO_PCP', $params, $pcpInfo);
+    $ownerNotifyID = CRM_Core_DAO::getFieldValue('CRM_PCP_DAO_PCPBlock', $pcpInfo['pcp_block_id'], 'owner_notify_id');
+
+    if ($ownerNotifyID != CRM_Core_OptionGroup::getValue('pcp_owner_notify', 'no_notifications', 'name') &&
+        (($ownerNotifyID == CRM_Core_OptionGroup::getValue('pcp_owner_notify', 'owner_chooses', 'name') &&
+        CRM_Core_DAO::getFieldValue('CRM_PCP_DAO_PCP', $contributionSoft->pcp_id, 'is_notify')) ||
+        $ownerNotifyID == CRM_Core_OptionGroup::getValue('pcp_owner_notify', 'all_owners', 'name'))) {
+      $pcpInfoURL = CRM_Utils_System::url('civicrm/pcp/info',
+        "reset=1&id={$contributionSoft->pcp_id}",
+        TRUE, NULL, FALSE, TRUE
+      );
+      // set email in the template here
+      // get the billing location type
+      $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id', array(), 'validate');
+      $billingLocationTypeId = array_search('Billing', $locationTypes);
+
+      if ($billingLocationTypeId) {
+        list($donorName, $email) = CRM_Contact_BAO_Contact_Location::getEmailDetails($contribution->contact_id, FALSE, $billingLocationTypeId);
+      }
+      // get primary location email if no email exist( for billing location).
+      if (!$email) {
+        list($donorName, $email) = CRM_Contact_BAO_Contact_Location::getEmailDetails($contribution->contact_id);
+      }
+      list($ownerName, $ownerEmail) = CRM_Contact_BAO_Contact_Location::getEmailDetails($contributionSoft->contact_id);
+      $tplParams = array(
+        'page_title' => $pcpInfo['title'],
+        'receive_date' => $contribution->receive_date,
+        'total_amount' => $contributionSoft->amount,
+        'donors_display_name' => $donorName,
+        '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(
+        'groupName' => 'msg_tpl_workflow_contribution',
+        'valueName' => 'pcp_owner_notify',
+        'contactId' => $contributionSoft->contact_id,
+        'toEmail' => $ownerEmail,
+        'toName' => $ownerName,
+        'from' => "$domainValues[0] <$domainValues[1]>",
+        'tplParams' => $tplParams,
+        'PDFFilename' => 'receipt.pdf',
       );
+      CRM_Core_BAO_MessageTemplate::sendTemplate($sendTemplateParams);
     }
-    return $contributionTypeId;
   }
 
   /**
-   * Process the form.
+   * Function used to se pcp related defaults / params.
    *
-   * @param array $premiumParams
-   * @param $contribution
+   * This is used by contribution and also event PCPs
+   *
+   * @param CRM_Core_Form $page
+   *   Form object.
+   * @param array $params
+   *
+   * @return array
    */
-  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));
-
-      $periodType = $productDAO->period_type;
-
-      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');
-          }
-        }
-
-        $date = explode('-', $startDate);
-        $year = $date[0];
-        $month = $date[1];
-        $day = $date[2];
-
-        switch ($duration_unit) {
-          case 'year':
-            $year = $year + $duration_interval;
-            break;
-
-          case 'month':
-            $month = $month + $duration_interval;
-            break;
-
-          case 'day':
-            $day = $day + $duration_interval;
-            break;
-
-          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);
-      }
-
-      $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;
-      }
-      //Fixed For CRM-3901
-      $daoContrProd = new CRM_Contribute_DAO_ContributionProduct();
-      $daoContrProd->contribution_id = $contribution->id;
-      if ($daoContrProd->find(TRUE)) {
-        $params['id'] = $daoContrProd->id;
-      }
-
-      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);
-      }
+  public static function processPcp(&$page, $params) {
+    $params['pcp_made_through_id'] = $page->_pcpId;
+    $page->assign('pcpBlock', TRUE);
+    if (!empty($params['pcp_display_in_roll']) && empty($params['pcp_roll_nickname'])) {
+      $params['pcp_roll_nickname'] = ts('Anonymous');
+      $params['pcp_is_anonymous'] = 1;
     }
-    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();
+    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]);
       }
     }
+
+    return $params;
   }
 
   /**
-   * Process the contribution.
+   * Process membership.
    *
-   * @param CRM_Core_Form $form
-   * @param array $params
-   * @param array $result
+   * @param array $membershipParams
    * @param int $contactID
-   * @param CRM_Financial_DAO_FinancialType $financialType
-   * @param bool $pending
-   * @param bool $online
-   *
-   * @param bool $isTest
-   * @param array $lineItems
-   *
-   * @throws Exception
-   * @return CRM_Contribute_DAO_Contribution
-   */
-  public static function processContribution(
-    &$form,
-    $params,
-    $result,
-    $contactID,
-    $financialType,
-    $pending,
-    $online,
-    $isTest,
-    $lineItems
-  ) {
-    $transaction = new CRM_Core_Transaction();
-    $contribSoftContactId = $addressID = NULL;
+   * @param array $customFieldsFormatted
+   * @param array $fieldTypes
+   * @param array $premiumParams
+   * @param array $membershipLineItems
+   *   Line items specifically relating to memberships.
+   * @param bool $isPayLater
+   */
+  protected function processMembership($membershipParams, $contactID, $customFieldsFormatted, $fieldTypes, $premiumParams,
+                                $membershipLineItems, $isPayLater) {
 
-    // add these values for the recurringContrib function ,CRM-10188
-    $params['financial_type_id'] = $financialType->id;
+    $membershipTypeIDs = (array) $membershipParams['selectMembership'];
+    $membershipTypes = CRM_Member_BAO_Membership::buildMembershipTypeValues($this, $membershipTypeIDs);
+    $membershipType = empty($membershipTypes) ? array() : reset($membershipTypes);
+    $isPending = $this->getIsPending();
 
-    //create an contribution address
-    if ($form->_contributeMode != 'notify' && empty($params['is_pay_later']) && !empty($form->_values['is_monetary'])) {
-      $addressID = CRM_Contribute_BAO_Contribution::createAddress($params, $form->_bltID);
-    }
+    $this->assign('membership_name', CRM_Utils_Array::value('name', $membershipType));
 
-    //@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']) && !empty($form->_values['is_email_receipt'])) {
-      $params['is_email_receipt'] = CRM_Utils_Array::value('is_email_receipt', $form->_values);
+    $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;
     }
-    $recurringContributionID = self::processRecurringContribution($form, $params, $contactID, $financialType, $online);
+    $isProcessSeparateMembershipTransaction = $this->isSeparateMembershipTransaction($this->_id, $this->_values['amount_block_is_active']);
 
-    // CRM-11885
-    // if non_deductible_amount exists i.e. Additional Details fieldset was opened [and staff typed something] -> keep it.
-    if (isset($params['non_deductible_amount']) && (!empty($params['non_deductible_amount']))) {
-      $nonDeductibleAmount = $params['non_deductible_amount'];
+    if ($this->_values['amount_block_is_active']) {
+      $financialTypeID = $this->_values['financial_type_id'];
     }
-    // if non_deductible_amount does NOT exist - then calculate it depending on:
-    // $contributionType->is_deductible and whether there is a product (premium).
     else {
-      //if ($contributionType->is_deductible && $deductibleMode) {
-      if ($financialType->is_deductible) {
-        if ($online && isset($params['selectProduct'])) {
-          $selectProduct = CRM_Utils_Array::value('selectProduct', $params);
-        }
-        if (!$online && isset($params['product_name'][0])) {
-          $selectProduct = $params['product_name'][0];
-        }
-        // if there is a product - compare the value to the contribution amount
-        if (isset($selectProduct) &&
-          $selectProduct != 'no_thanks'
-        ) {
-          $productDAO = new CRM_Contribute_DAO_Product();
-          $productDAO->id = $selectProduct;
-          $productDAO->find(TRUE);
-          // product value exceeds contribution amount
-          if ($params['amount'] < $productDAO->price) {
-            $nonDeductibleAmount = $params['amount'];
-          }
-          // product value does NOT exceed contribution amount
-          else {
-            $nonDeductibleAmount = $productDAO->price;
-          }
-        }
-        // contribution is deductible - but there is no product
-        else {
-          $nonDeductibleAmount = '0.00';
-        }
-      }
-      // contribution is NOT deductible
-      else {
-        $nonDeductibleAmount = $params['amount'];
-      }
+      $financialTypeID = CRM_Utils_Array::value('financial_type_id', $membershipType, CRM_Utils_Array::value('financial_type_id', $membershipParams));
     }
 
-    $now = date('YmdHis');
-    $receiptDate = CRM_Utils_Array::value('receipt_date', $params);
-    if (!empty($form->_values['is_email_receipt'])) {
-      $receiptDate = $now;
+    if (CRM_Utils_Array::value('membership_source', $this->_params)) {
+      $membershipParams['contribution_source'] = $this->_params['membership_source'];
     }
 
-    //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);
-    }
-
-    // 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);
-      }
+    $this->postProcessMembership($membershipParams, $contactID,
+      $this, $premiumParams, $customFieldsFormatted, $fieldTypes, $membershipType, $membershipTypeIDs, $isPaidMembership, $this->_membershipId, $isProcessSeparateMembershipTransaction, $financialTypeID,
+      $membershipLineItems, $isPayLater, $isPending);
 
-      // Pass these details onto with the contribution to make them
-      // available at hook_post_process, CRM-8908
-      $params['soft_credit_to'] = $contribSoftContactId;
-    }
+    $this->assign('membership_assign', TRUE);
+    $this->set('membershipTypeID', $membershipParams['selectMembership']);
+  }
 
-    if (isset($params['amount'])) {
-      $isMonetary = NULL;
-      if (!empty($form->_values['is_monetary'])) {
-        $isMonetary = $form->_values['is_monetary'];
-      }
-      $contribParams = self::getContributionParams(
-        $params, $contactID, $financialType->id, $online, $contributionPageId, $nonDeductibleAmount, $campaignId, $isMonetary, $pending, $result, $receiptDate,
-        $recurringContributionID, $isTest, $addressID, $contribSoftContactId, $lineItems
+  /**
+   * 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',
+        $isTest
       );
-      $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) {
-        $totalTaxAmount = 0;
-        $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);
-              }
-            }
-          }
+      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']);
         }
-        $smarty = CRM_Core_Smarty::singleton();
-        $smarty->assign('dataArray', $dataArray);
-        $smarty->assign('totalTaxAmount', $params['tax_amount']);
+        $membershipContribution = $this->processSecondaryFinancialTransaction($contactID, $form, $membershipParams,
+          $isTest, $membershipLineItems, CRM_Utils_Array::value('minimum_fee', $membershipDetails, 0), CRM_Utils_Array::value('financial_type_id', $membershipDetails));
       }
-      if (is_a($contribution, 'CRM_Core_Error')) {
-        $message = CRM_Core_Error::getMessages($contribution);
-        CRM_Core_Error::fatal($message);
+      catch (CRM_Core_Exception $e) {
+        $errors[2] = $e->getMessage();
+        $membershipContribution = NULL;
       }
-
-      // lets store it in the form variable so postProcess hook can get to this and use it
-      $form->_contributionID = $contribution->id;
     }
 
-    //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);
+    $membership = NULL;
+    if (!empty($membershipContribution) && !is_a($membershipContribution, 'CRM_Core_Error')) {
+      $membershipContributionID = $membershipContribution->id;
+    }
 
-    //handle pledge stuff.
-    if (empty($form->_params['separate_membership_payment']) && !empty($form->_values['pledge_block_id']) &&
-      (!empty($form->_params['is_pledge']) || !empty($form->_values['pledge_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;
 
-      if (!empty($form->_values['pledge_id'])) {
+        $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);
+          }
+        }
 
-        //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'
+        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')
           );
-
-          $pledgePaymentParams = array(
-            'id' => $paymentId,
-            'contribution_id' => $contribution->id,
-            'status_id' => $contribution->contribution_status_id,
-            'actual_amount' => $scheduledAmount,
+          $form->assign('mem_end_date',
+            CRM_Utils_Date::customFormat($dates['end_date'], '%Y%m%d')
           );
-
-          CRM_Pledge_BAO_PledgePayment::add($pledgePaymentParams);
         }
 
-        //update pledge status according to the new payment statuses
-        CRM_Pledge_BAO_PledgePayment::updatePledgePaymentStatus($form->_values['pledge_id']);
-      }
-      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;
+        if (!empty($membershipContribution)) {
+          // update recurring id for membership record
+          CRM_Member_BAO_Membership::updateRecurMembership($membership, $membershipContribution);
+          CRM_Member_BAO_Membership::linkMembershipPayment($membership, $membershipContribution);
         }
-        $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 ($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 ($online && $contribution) {
-      CRM_Core_BAO_CustomValueTable::postProcess($form->_params,
-        CRM_Core_DAO::$_nullArray,
-        'civicrm_contribution',
-        $contribution->id,
-        'Contribution'
+    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;
     }
-    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);
-      }
+    if (count($createdMemberships) == 1) {
+      //presumably this is only relevant for exactly 1 membership
+      $form->_params['membershipID'] = $createdMembership->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());
+    //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);
+      $paymentActionResult = $payment->doPayment($form->_params, 'contribute');
+      $this->completeTransaction($paymentActionResult, $paymentResult['contribution']->id);
+      // Do not send an email if Recurring transaction is done via Direct Mode
+      // Email will we sent when the IPN is received.
+      return;
     }
 
-    if (isset($params['related_contact'])) {
-      $contactID = $params['related_contact'];
+    //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;
+      }
     }
-    elseif (isset($params['cms_contactID'])) {
-      $contactID = $params['cms_contactID'];
+    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));
+
+    //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_amount', $minimumFee);
+    $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;
+
+    //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;
+    $contributionParams = array(
+      'contact_id' => $contactID,
+      'line_item' => $lineItems,
+      'is_test' => $isTest,
+      'campaign_id' => CRM_Utils_Array::value('campaign_id', $tempParams, CRM_Utils_Array::value('campaign_id',
+        $form->_values)),
+      'contribution_page_id' => $form->_id,
+      'source' => CRM_Utils_Array::value('source', $tempParams, CRM_Utils_Array::value('description', $tempParams)),
+    );
+    $isMonetary = !empty($form->_values['is_monetary']);
+    if ($isMonetary) {
+      if (empty($paymentParams['is_pay_later'])) {
+        $contributionParams['payment_instrument_id'] = $form->_paymentProcessor['payment_instrument_id'];
+      }
+    }
+    $membershipContribution = CRM_Contribute_Form_Contribution_Confirm::processFormContribution($form,
+      $tempParams,
+      $tempParams,
+      $contributionParams,
+      $financialType,
+      TRUE,
+      $form->_bltID
+    );
+
+    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');
+      $form->set('membership_trx_id', $result['trxn_id']);
+      $form->assign('membership_trx_id', $result['trxn_id']);
+      $this->completeTransaction($result, $membershipContribution->id);
     }
 
-    //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;
+    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;
+  }
 
-    // create an activity record
-    if ($contribution) {
-      CRM_Activity_BAO_Activity::addActivity($contribution, NULL, $targetContactID);
+  /**
+   * 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;
     }
-
-    $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-' . $form->_bltID
-    );
-    return $contribution;
+    return FALSE;
   }
 
   /**
-   * Create the recurring contribution record.
+   * This function sets the fields.
    *
-   * @param CRM_Core_Form $form
-   * @param array $params
-   * @param int $contactID
-   * @param string $contributionType
-   * @param bool $online
+   * - $this->_params['amount_level']
+   * - $this->_params['selectMembership']
+   * And under certain circumstances sets
+   * $this->_params['amount'] = null;
    *
-   * @return mixed
+   * @param int $priceSetID
    */
-  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;
-    }
+  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;
 
-    $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);
+    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;
+      }
 
-    // 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'];
+      // 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;
+      }
     }
+  }
 
-    $recurParams['is_test'] = 0;
-    if (($form->_action & CRM_Core_Action::PREVIEW) ||
-      (isset($form->_mode) && ($form->_mode == 'test'))
-    ) {
-      $recurParams['is_test'] = 1;
-    }
+  /**
+   * 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'];
 
-    $recurParams['start_date'] = $recurParams['create_date'] = $recurParams['modified_date'] = date('YmdHis');
-    if (!empty($params['receive_date'])) {
-      $recurParams['start_date'] = $params['receive_date'];
+    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';
+      }
     }
-    $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;
+    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));
+  }
 
-    $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);
+  /**
+   * 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;
       }
-    }
-    $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}";
-        }
+      else {
+        $params['is_pay_later'] = civicrm_api3('contribution_page', 'getvalue', array(
+          'id' => $id,
+          'return' => 'is_pay_later',
+        ));
       }
-      CRM_Utils_System::redirect(CRM_Utils_System::url($urlString, $urlParams));
     }
-
-    return $recurring->id;
+    if (empty($params['price_set_id'])) {
+      $params['price_set_id'] = CRM_Price_BAO_PriceSet::getFor('civicrm_contribution_page', $params['id']);
+    }
+    return $params;
   }
 
   /**
-   * Add on behalf of organization and it's location.
+   * Post form submission handling.
    *
-   * 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.
+   * This is also called from the test suite.
    *
-   * @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.
+   * @return array
    */
-  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;
+  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']);
 
-    // formalities for creating / editing organization.
-    $behalfOrganization['contact_type'] = 'Organization';
+    $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);
+    }
 
-    // get the relationship type id
-    $relType = new CRM_Contact_DAO_RelationshipType();
-    $relType->name_a_b = 'Employee of';
-    $relType->find(TRUE);
-    $relTypeId = $relType->id;
+    $fields = array('email-Primary' => 1);
 
-    // keep relationship params ready
-    $relParams['relationship_type_id'] = $relTypeId . '_a_b';
-    $relParams['is_permission_a_b'] = 1;
-    $relParams['is_active'] = 1;
+    // get the add to groups
+    $addToGroups = array();
 
-    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');
+    // now set the values for the billing location.
+    foreach ($this->_fields as $name => $value) {
+      $fields[$name] = 1;
 
-      // 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']);
+      // 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'];
       }
     }
-    else {
-      // if found permissioned related organization, allow location edit
-      $behalfOrganization['contact_id'] = $orgID;
-      // don't allow name edit
-      unset($behalfOrganization['organization_name']);
-    }
-
-    // handling for image url
-    if (!empty($behalfOrganization['image_URL'])) {
-      CRM_Contact_BAO_Contact::processImageParams($behalfOrganization);
-    }
 
-    // create organization, add location
-    $orgID = CRM_Contact_BAO_Contact::createProfileContact($behalfOrganization, $fields, $orgID,
-      NULL, NULL, 'Organization'
-    );
-    // create relationship
-    $relParams['contact_check'][$orgID] = 1;
-    $cid = array('contact' => $contactID);
-    CRM_Contact_BAO_Relationship::legacyCreateMultiple($relParams, $cid);
+    $fields = $this->formatParamsForPaymentProcessor($fields);
 
-    // if multiple match - send a duplicate alert
-    if ($dupeIDs && (count($dupeIDs) > 1)) {
-      $values['onbehalf_dupe_alert'] = 1;
-      // required for IPN
-      $params['onbehalf_dupe_alert'] = 1;
-    }
+    // billing email address
+    $fields["email-{$this->_bltID}"] = 1;
 
-    // make sure organization-contact-id is considered for recording
-    // contribution/membership etc..
-    if ($contactID != $orgID) {
-      // take a note of contact-id, so we can send the
-      // receipt to individual contact as well.
+    //unset the billing parameters if it is pay later mode
+    //to avoid creation of billing location
+    // @todo - note that elsewhere we don't unset these - we simply make
+    // a sensible decision about including them when building the form
+    // and if they are submitted we handle them. Check out abstractEditPaymentForm.
+    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}",
+      );
 
-      // required for mailing/template display ..etc
-      $values['related_contact'] = $contactID;
-      // required for IPN
-      $params['related_contact'] = $contactID;
+      foreach ($billingFields as $value) {
+        unset($params[$value]);
+        unset($fields[$value]);
+      }
+    }
 
-      //make this employee of relationship as current
-      //employer / employee relationship,  CRM-3532
-      if ($isCurrentEmployer &&
-        ($orgID != CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $contactID, 'employer_id'))
-      ) {
-        $isCurrentEmployer = FALSE;
+    // 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->_values['onbehalf_profile_id'])) {
+      $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 (!$isCurrentEmployer && $orgID) {
-        //build current employer params
-        $currentEmpParams[$contactID] = $orgID;
-        CRM_Contact_BAO_Contact_Utils::setCurrentEmployer($currentEmpParams);
+      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;
+          }
+        }
       }
 
-      // contribution / signup will be done using this
-      // organization id.
-      $contactID = $orgID;
+      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]'];
+      }
     }
-  }
 
-  /**
-   * Function used to save pcp / soft credit entry.
-   *
-   * This is used by contribution and also event pcps
-   *
-   * @param array $params
-   * @param object $contribution
-   *   Contribution object.
-   */
-  public static function processPcpSoft(&$params, &$contribution) {
-    // Add soft contribution due to pcp or Submit Credit / Debit Card Contribution by admin.
-    if (!empty($params['soft_credit_to'])) {
-      $contributionSoftParams = array();
-      foreach (array(
-          'pcp_display_in_roll',
-          'pcp_roll_nickname',
-          'pcp_personal_note',
-          'amount',
-        ) as $val) {
-        if (!empty($params[$val])) {
-          $contributionSoftParams[$val] = $params[$val];
+    // 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]);
+      }
+    }
 
-      $contributionSoftParams['contact_id'] = $params['soft_credit_to'];
-      // add contribution id
-      $contributionSoftParams['contribution_id'] = $contribution->id;
-      // add pcp id
-      $contributionSoftParams['pcp_id'] = $params['pcp_made_through_id'];
+    if (empty($contactID)) {
+      $dupeParams = $params;
+      if (!empty($dupeParams['onbehalf'])) {
+        unset($dupeParams['onbehalf']);
+      }
+      if (!empty($dupeParams['honor'])) {
+        unset($dupeParams['honor']);
+      }
 
-      $contributionSoftParams['soft_credit_type_id'] = CRM_Core_OptionGroup::getValue('soft_credit_type', 'pcp', 'name');
+      $dedupeParams = CRM_Dedupe_Finder::formatParams($dupeParams, 'Individual');
+      $dedupeParams['check_permission'] = FALSE;
+      $ids = CRM_Dedupe_Finder::dupesByParams($dedupeParams, 'Individual');
 
-      $contributionSoft = CRM_Contribute_BAO_ContributionSoft::add($contributionSoftParams);
+      // if we find more than one contact, use the first one
+      $contactID = CRM_Utils_Array::value(0, $ids);
 
-      //Send notification to owner for PCP
-      if ($contributionSoft->id && $contributionSoft->pcp_id) {
-        CRM_Contribute_Form_Contribution_Confirm::pcpNotifyOwner($contribution, $contributionSoft);
+      // 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;
     }
-  }
-
-  /**
-   * Function used to send notification mail to pcp owner.
-   *
-   * This is used by contribution and also event PCPs.
-   *
-   * @param object $contribution
-   * @param object $contributionSoft
-   *   Contribution object.
-   */
-  public static function pcpNotifyOwner($contribution, $contributionSoft) {
-    $params = array('id' => $contributionSoft->pcp_id);
-    CRM_Core_DAO::commonRetrieve('CRM_PCP_DAO_PCP', $params, $pcpInfo);
-    $ownerNotifyID = CRM_Core_DAO::getFieldValue('CRM_PCP_DAO_PCPBlock', $pcpInfo['pcp_block_id'], 'owner_notify_id');
+    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
+    );
 
-    if ($ownerNotifyID != CRM_Core_OptionGroup::getValue('pcp_owner_notify', 'no_notifications', 'name') &&
-        (($ownerNotifyID == CRM_Core_OptionGroup::getValue('pcp_owner_notify', 'owner_chooses', 'name') &&
-        CRM_Core_DAO::getFieldValue('CRM_PCP_DAO_PCP', $contributionSoft->pcp_id, 'is_notify')) ||
-        $ownerNotifyID == CRM_Core_OptionGroup::getValue('pcp_owner_notify', 'all_owners', 'name'))) {
-      $pcpInfoURL = CRM_Utils_System::url('civicrm/pcp/info',
-        "reset=1&id={$contributionSoft->pcp_id}",
-        TRUE, NULL, FALSE, TRUE
-      );
-      // set email in the template here
-      // get the billing location type
-      $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id', array(), 'validate');
-      $billingLocationTypeId = array_search('Billing', $locationTypes);
+    // 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;
 
-      if ($billingLocationTypeId) {
-        list($donorName, $email) = CRM_Contact_BAO_Contact_Location::getEmailDetails($contribution->contact_id, FALSE, $billingLocationTypeId);
-      }
-      // get primary location email if no email exist( for billing location).
-      if (!$email) {
-        list($donorName, $email) = CRM_Contact_BAO_Contact_Location::getEmailDetails($contribution->contact_id);
-      }
-      list($ownerName, $ownerEmail) = CRM_Contact_BAO_Contact_Location::getEmailDetails($contributionSoft->contact_id);
-      $tplParams = array(
-        'page_title' => $pcpInfo['title'],
-        'receive_date' => $contribution->receive_date,
-        'total_amount' => $contributionSoft->amount,
-        'donors_display_name' => $donorName,
-        'donors_email' => $email,
-        'pcpInfoURL' => $pcpInfoURL,
-        'is_honor_roll_enabled' => $contributionSoft->pcp_display_in_roll,
-      );
-      $domainValues = CRM_Core_BAO_Domain::getNameAndEmail();
-      $sendTemplateParams = array(
-        'groupName' => 'msg_tpl_workflow_contribution',
-        'valueName' => 'pcp_owner_notify',
-        'contactId' => $contributionSoft->contact_id,
-        'toEmail' => $ownerEmail,
-        'toName' => $ownerName,
-        'from' => "$domainValues[0] <$domainValues[1]>",
-        'tplParams' => $tplParams,
-        'PDFFilename' => 'receipt.pdf',
+    //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($this->_values['onbehalf_profile_id']) && 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
       );
-      CRM_Core_BAO_MessageTemplate::sendTemplate($sendTemplateParams);
     }
-  }
+    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;
+    }
 
-  /**
-   * Function used to se pcp related defaults / params.
-   *
-   * This is used by contribution and also event PCPs
-   *
-   * @param CRM_Core_Form $page
-   *   Form object.
-   * @param array $params
-   *
-   * @return array
-   */
-  public static function processPcp(&$page, $params) {
-    $params['pcp_made_through_id'] = $page->_pcpId;
-    $page->assign('pcpBlock', TRUE);
-    if (!empty($params['pcp_display_in_roll']) && empty($params['pcp_roll_nickname'])) {
-      $params['pcp_roll_nickname'] = ts('Anonymous');
-      $params['pcp_is_anonymous'] = 1;
+    // 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 {
-      $params['pcp_is_anonymous'] = 0;
+      $session->set('transaction.userID', NULL);
     }
-    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]);
+
+    $this->_useForMember = $this->get('useForMember');
+
+    // 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']);
       }
-    }
 
-    return $params;
-  }
+      if ($this->_action & CRM_Core_Action::PREVIEW) {
+        $membershipParams['is_test'] = 1;
+      }
+      if ($this->_params['is_pay_later']) {
+        $membershipParams['is_pay_later'] = 1;
+      }
 
-  /**
-   * 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));
-
-      $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']) {
-        $contributionTypeId = $this->_values['financial_type_id'];
+      //inherit campaign from contribution page.
+      if (!array_key_exists('campaign_id', $membershipParams)) {
+        $membershipParams['campaign_id'] = CRM_Utils_Array::value('campaign_id', $this->_values);
       }
-      else {
-        $contributionTypeId = CRM_Utils_Array::value('financial_type_id', $membershipType, CRM_Utils_Array::value('financial_type_id', $membershipParams));
+
+      CRM_Core_Payment_Form::mapParams($this->_bltID, $this->_params, $membershipParams, TRUE);
+      $this->doMembershipProcessing($contactID, $membershipParams, $premiumParams, $isPayLater);
+    }
+    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;
+
+      if (!empty($paymentParams['onbehalf']) &&
+        is_array($paymentParams['onbehalf'])
+      ) {
+        foreach ($paymentParams['onbehalf'] as $key => $value) {
+          if (strstr($key, 'custom_')) {
+            $this->_params[$key] = $value;
+          }
+        }
       }
 
-      CRM_Member_BAO_Membership::postProcessMembership($membershipParams, $contactID,
-        $this, $premiumParams, $customFieldsFormatted, $fieldTypes, $membershipType, $membershipTypeIDs, $isPaidMembership, $this->_membershipId, $isProcessSeparateMembershipTransaction, $contributionTypeId,
-        $membershipLineItems, $isPayLater
+      $result = CRM_Contribute_BAO_Contribution_Utils::processConfirm($this, $paymentParams,
+        $contactID,
+        $this->wrangleFinancialTypeID($this->_values['financial_type_id']),
+        'contribution',
+        ($this->_mode == 'test') ? 1 : 0
       );
-      $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']}"));
+
+      if (empty($result['is_payment_failure'])) {
+        // @todo move premium processing to complete transaction if it truly is an 'after' action.
+        $this->postProcessPremium($premiumParams, $result['contribution']);
+      }
+      if (!empty($result['contribution'])) {
+        // Not quite sure why it would be empty at this stage but tests show it can be ... at least in tests.
+        $this->completeTransaction($result, $result['contribution']->id);
+      }
+      return $result;
     }
   }
 
   /**
-   * 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)
+   * Membership processing section.
    *
-   * @param int $formID
-   * @param bool $amountBlockActiveOnForm
+   * This is in a separate function as part of a move towards refactoring.
    *
-   * @return bool
+   * @param int $contactID
+   * @param array $membershipParams
+   * @param array $premiumParams
+   * @param bool $isPayLater
    */
-  public function isSeparateMembershipTransaction($formID, $amountBlockActiveOnForm) {
-    $memBlockDetails = CRM_Member_BAO_Membership::getMembershipBlock($formID);
-    if (!empty($memBlockDetails['is_separate_payment']) && $amountBlockActiveOnForm) {
-      return TRUE;
+  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 {
+      $membershipParams['cms_contactID'] = $contactID;
     }
-    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;
+    if (!empty($membershipParams['onbehalf']) &&
+      is_array($membershipParams['onbehalf']) && !empty($membershipParams['onbehalf']['member_campaign_id'])
+    ) {
+      $this->_params['campaign_id'] = $membershipParams['onbehalf']['member_campaign_id'];
+    }
 
-    while ($priceField->fetch()) {
-      if ($priceField->name == "contribution_amount") {
-        $paramWeDoNotUnderstand = $priceField->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
+          );
+        }
       }
-      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');
+      $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;
+          }
         }
-        if ($priceField->name == "membership_amount") {
-          $this->_params['selectMembership'] = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue',
-            $this->_params["price_{$priceField->id}"], 'membership_type_id');
+      }
+      $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;
+          }
         }
       }
-      // 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;
+      try {
+        $this->processMembership($membershipParams, $contactID, $customFieldsFormatted, $fieldTypes, $premiumParams, $membershipLineItems, $isPayLater);
       }
-
-      // 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;
+      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']}"));
+      }
+      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
+        );
       }
     }
   }
 
   /**
-   * Submit function.
+   * Complete transaction if payment has been processed.
    *
-   * @param array $params
+   * Check the result for a success outcome & if paid then complete the transaction.
    *
-   * @throws CiviCRM_API3_Exception
-   */
-  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'])) {
-      $form->_paymentProcessor = civicrm_api3('payment_processor', 'getsingle', array('id' => $params['payment_processor']));
-      if ($form->_paymentProcessor['billing_mode'] == 1) {
-        $form->_contributeMode = 'direct';
-      }
-      else {
-        $form->_contributeMode = 'notify';
-      }
-    }
-    else {
-      $form->_params['payment_processor'] = 0;
-    }
-    $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.
+   * Completing will trigger update of related entities and emails.
    *
-   * @param int $id
-   * @param array $params
+   * @param array $result
+   * @param int $contributionID
    *
-   * @return array
-   * @throws CiviCRM_API3_Exception
+   * @throws \CRM_Core_Exception
    */
-  public static function getFormParams($id, array $params) {
-    if (!isset($params['is_pay_later'])) {
-      if (!empty($params['payment_processor'])) {
-        $params['is_pay_later'] = 0;
+  protected function completeTransaction($result, $contributionID) {
+    if (CRM_Utils_Array::value('payment_status_id', $result) == 1) {
+      try {
+        civicrm_api3('contribution', 'completetransaction', array(
+            'id' => $contributionID,
+            'trxn_id' => CRM_Utils_Array::value('trxn_id', $result),
+            'payment_processor_id' => $this->_paymentProcessor['id'],
+            'is_transactional' => FALSE,
+            'fee_amount' => CRM_Utils_Array::value('fee_amount', $result),
+          )
+        );
       }
-      else {
-        $params['is_pay_later'] = civicrm_api3('contribution_page', 'getvalue', array(
-          'id' => $id,
-          'return' => 'is_pay_later',
-        ));
+      catch (CiviCRM_API3_Exception $e) {
+        if ($e->getErrorCode() != 'contribution_completed') {
+          throw new CRM_Core_Exception('Failed to update contribution in database');
+        }
       }
     }
-    if (empty($params['price_set_id'])) {
-      $params['price_set_id'] = CRM_Price_BAO_PriceSet::getFor('civicrm_contribution_page', $params['id']);
-    }
-    return $params;
   }
 
 }