Merge branch '4.6' of https://github.com/civicrm/civicrm-core
authorEileen McNaughton <eileen@fuzion.co.nz>
Fri, 10 Jul 2015 04:59:42 +0000 (16:59 +1200)
committerEileen McNaughton <eileen@fuzion.co.nz>
Fri, 10 Jul 2015 04:59:42 +0000 (16:59 +1200)
Conflicts:
CRM/Contribute/Form/Contribution.php
CRM/Member/BAO/Membership.php
CRM/Member/Form.php
CRM/Member/Form/MembershipRenewal.php
tests/phpunit/api/v3/ContributionPageTest.php

12 files changed:
1  2 
CRM/Contribute/BAO/Contribution.php
CRM/Contribute/BAO/Contribution/Utils.php
CRM/Contribute/Form/Contribution.php
CRM/Core/BAO/UFGroup.php
CRM/Event/Form/ManageEvent/Registration.php
CRM/Member/BAO/Membership.php
CRM/Member/Form.php
CRM/Member/Form/Membership.php
CRM/Member/Form/MembershipRenewal.php
js/model/crm.uf.js
tests/phpunit/WebTest/Event/PCPAddTest.php
tests/phpunit/api/v3/ContributionPageTest.php

Simple merge
index 5f8a9e2ef125597d96ffdf333dd9a7bd0ff2d917,174e12218128529a888914148a5e1c8ace66c7bf..503480b80861a9eb860dae5b2271881ee6cf67e1
@@@ -283,16 -315,40 +283,21 @@@ class CRM_Contribute_BAO_Contribution_U
          $form->_params['source'] = $paymentParams['contribution_source'];
        }
  
 -      // check if pending was set to true by payment processor
 -      $pending = FALSE;
 -      if (!empty($form->_params['contribution_status_pending'])) {
 -        $pending = TRUE;
 -      }
 -      if (!(!empty($paymentParams['is_recur']) && $form->_contributeMode == 'direct')) {
 -        $contribution = CRM_Contribute_Form_Contribution_Confirm::processContribution($form,
 -          $form->_params, $result,
 -          $contactID, $contributionType,
 -          $pending, TRUE,
 -          $isTest,
 -          $lineItems
 -        );
 -      }
        $form->postProcessPremium($premiumParams, $contribution);
-       if (is_array($result) && !empty($result['trxn_id'])) {
-         $contribution->trxn_id = $result['trxn_id'];
+       if (is_array($result)) {
+         if (!empty($result['trxn_id'])) {
+           $contribution->trxn_id = $result['trxn_id'];
+         }
+         if (!empty($result['payment_status_id'])) {
+           $contribution->payment_status_id = $result['payment_status_id'];
+         }
        }
 -      $membershipResult[1] = $contribution;
 +      $result['contribution'] = $contribution;
      }
 -
 -    if ($component == 'membership') {
 -      return $membershipResult;
 -    }
 -
      //Do not send an email if Recurring contribution is done via Direct Mode
      //We will send email once the IPN is received.
 -    if (!empty($paymentParams['is_recur']) && $form->_contributeMode == 'direct') {
 -      return TRUE;
 +    if ($form->_contributeMode == 'direct') {
 +      return $result;
      }
  
      // get the price set values for receipt.
index 812ec435cef4a5e236bafdcc4d1f15bd3e6efaa4,9476ac6433c28019430bbdb1d95cfbf71bcf03e1..bf323fc4b92f013952c9d36e66c4c9939727be88
@@@ -1313,523 -1876,4 +1313,521 @@@ class CRM_Contribute_Form_Contribution 
      }
    }
  
-       if (!empty($softParams)) {
-         $params['soft_credit'] = $softParams;
-         $params['soft_credit_ids'] = $softIDs;
-       }
 +  /**
 +   * Wrapper for unit testing the post process submit function.
 +   *
 +   * (If we expose through api we can get default additions 'for free').
 +   *
 +   * @param array $params
 +   * @param int $action
 +   * @param string|null $creditCardMode
 +   *
 +   * @throws \CiviCRM_API3_Exception
 +   */
 +  public function testSubmit($params, $action, $creditCardMode = NULL) {
 +    $defaults = array(
 +      'soft_credit_contact_id' => array(),
 +      'receipt_date' => '',
 +      'receipt_date_time' => '',
 +      'cancel_date' => '',
 +      'cancel_date_time' => '',
 +      'hidden_Premium' => 1,
 +    );
 +    $this->_bltID = 5;
 +    if (!empty($params['id'])) {
 +      $existingContribution = civicrm_api3('contribution', 'getsingle', array(
 +        'id' => $params['id'],
 +      ));
 +      $this->_id = $params['id'];
 +    }
 +    else {
 +      $existingContribution = array();
 +    }
 +
 +    $this->_defaults['contribution_status_id'] = CRM_Utils_Array::value('contribution_status_id',
 +      $existingContribution
 +    );
 +
 +    $this->_defaults['total_amount'] = CRM_Utils_Array::value('total_amount',
 +      $existingContribution
 +    );
 +
 +    if ($creditCardMode) {
 +      $this->_mode = $creditCardMode;
 +    }
 +
 +    // Required because processCreditCard calls set method on this.
 +    $_SERVER['REQUEST_METHOD'] = 'GET';
 +    $this->controller = new CRM_Core_Controller();
 +
 +    CRM_Contribute_Form_AdditionalInfo::buildPremium($this);
 +
 +    $this->_fields = array();
 +    $this->submit(array_merge($defaults, $params), $action, CRM_Utils_Array::value('pledge_payment_id', $params));
 +
 +  }
 +
 +  /**
 +   * @param array $submittedValues
 +   *
 +   * @param int $action
 +   *   Action constant
 +   *    - CRM_Core_Action::UPDATE
 +   *
 +   * @param $pledgePaymentID
 +   *
 +   * @return array
 +   * @throws \Exception
 +   */
 +  protected function submit($submittedValues, $action, $pledgePaymentID) {
 +    $softParams = $softIDs = array();
 +    $pId = $contribution = $isRelatedId = FALSE;
 +
 +    if (!empty($submittedValues['price_set_id']) && $action & CRM_Core_Action::UPDATE) {
 +      $line = CRM_Price_BAO_LineItem::getLineItems($this->_id, 'contribution');
 +      $lineID = key($line);
 +      $priceSetId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceField', CRM_Utils_Array::value('price_field_id', $line[$lineID]), 'price_set_id');
 +      $quickConfig = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $priceSetId, 'is_quick_config');
 +      if ($quickConfig) {
 +        CRM_Price_BAO_LineItem::deleteLineItems($this->_id, 'civicrm_contribution');
 +      }
 +    }
 +
 +    // Process price set and get total amount and line items.
 +    $lineItem = array();
 +    $priceSetId = CRM_Utils_Array::value('price_set_id', $submittedValues);
 +    if (empty($priceSetId) && !$this->_id) {
 +      $this->_priceSetId = $priceSetId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', 'default_contribution_amount', 'id', 'name');
 +      $this->_priceSet = current(CRM_Price_BAO_PriceSet::getSetDetail($priceSetId));
 +      $fieldID = key($this->_priceSet['fields']);
 +      $fieldValueId = key($this->_priceSet['fields'][$fieldID]['options']);
 +      $this->_priceSet['fields'][$fieldID]['options'][$fieldValueId]['amount'] = $submittedValues['total_amount'];
 +      $submittedValues['price_' . $fieldID] = 1;
 +    }
 +
 +    if ($priceSetId) {
 +      CRM_Price_BAO_PriceSet::processAmount($this->_priceSet['fields'],
 +        $submittedValues, $lineItem[$priceSetId]);
 +
 +      // Unset tax amount for offline 'is_quick_config' contribution.
 +      if ($this->_priceSet['is_quick_config'] &&
 +        !array_key_exists($submittedValues['financial_type_id'], CRM_Core_PseudoConstant::getTaxRates())
 +      ) {
 +        unset($submittedValues['tax_amount']);
 +      }
 +      $submittedValues['total_amount'] = CRM_Utils_Array::value('amount', $submittedValues);
 +    }
 +    if ($this->_id) {
 +      if ($this->_compId) {
 +        if ($this->_context == 'participant') {
 +          $pId = $this->_compId;
 +        }
 +        elseif ($this->_context == 'membership') {
 +          $isRelatedId = TRUE;
 +        }
 +        else {
 +          $pId = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_ParticipantPayment', $this->_id, 'participant_id', 'contribution_id');
 +        }
 +      }
 +      else {
 +        $contributionDetails = CRM_Contribute_BAO_Contribution::getComponentDetails($this->_id);
 +        if (array_key_exists('membership', $contributionDetails)) {
 +          $isRelatedId = TRUE;
 +        }
 +        elseif (array_key_exists('participant', $contributionDetails)) {
 +          $pId = $contributionDetails['participant'];
 +        }
 +      }
 +    }
 +    if (!$priceSetId && !empty($submittedValues['total_amount']) && $this->_id) {
 +      // CRM-10117 update the line items for participants.
 +      if ($pId) {
 +        $entityTable = 'participant';
 +        $entityID = $pId;
 +        $isRelatedId = FALSE;
 +        $participantParams = array(
 +          'fee_amount' => $submittedValues['total_amount'],
 +          'id' => $entityID,
 +        );
 +        CRM_Event_BAO_Participant::add($participantParams);
 +        if (empty($this->_lineItems)) {
 +          $this->_lineItems[] = CRM_Price_BAO_LineItem::getLineItems($entityID, 'participant', 1);
 +        }
 +      }
 +      else {
 +        $entityTable = 'contribution';
 +        $entityID = $this->_id;
 +      }
 +
 +      $lineItems = CRM_Price_BAO_LineItem::getLineItems($entityID, $entityTable, NULL, TRUE, $isRelatedId);
 +      foreach (array_keys($lineItems) as $id) {
 +        $lineItems[$id]['id'] = $id;
 +      }
 +      $itemId = key($lineItems);
 +      if ($itemId && !empty($lineItems[$itemId]['price_field_id'])) {
 +        $this->_priceSetId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceField', $lineItems[$itemId]['price_field_id'], 'price_set_id');
 +      }
 +
 +      if ($this->_priceSetId && CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId, 'is_quick_config')) {
 +        $lineItems[$itemId]['unit_price'] = $lineItems[$itemId]['line_total'] = CRM_Utils_Rule::cleanMoney(CRM_Utils_Array::value('total_amount', $submittedValues));
 +
 +        // Update line total and total amount with tax on edit.
 +        $financialItemsId = CRM_Core_PseudoConstant::getTaxRates();
 +        if (array_key_exists($submittedValues['financial_type_id'], $financialItemsId)) {
 +          $lineItems[$itemId]['tax_rate'] = $financialItemsId[$submittedValues['financial_type_id']];
 +        }
 +        else {
 +          $lineItems[$itemId]['tax_rate'] = $lineItems[$itemId]['tax_amount'] = "";
 +          $submittedValues['tax_amount'] = 'null';
 +        }
 +        if ($lineItems[$itemId]['tax_rate']) {
 +          $lineItems[$itemId]['tax_amount'] = ($lineItems[$itemId]['tax_rate'] / 100) * $lineItems[$itemId]['line_total'];
 +          $submittedValues['total_amount'] = $lineItems[$itemId]['line_total'] + $lineItems[$itemId]['tax_amount'];
 +          $submittedValues['tax_amount'] = $lineItems[$itemId]['tax_amount'];
 +        }
 +      }
 +      // CRM-10117 update the line items for participants.
 +      if (!empty($lineItems[$itemId]['price_field_id'])) {
 +        $lineItem[$this->_priceSetId] = $lineItems;
 +      }
 +    }
 +
 +    $isQuickConfig = 0;
 +    if ($this->_priceSetId && CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId, 'is_quick_config')) {
 +      $isQuickConfig = 1;
 +    }
 +    //CRM-11529 for quick config back office transactions
 +    //when financial_type_id is passed in form, update the
 +    //line items with the financial type selected in form
 +    if ($isQuickConfig && !empty($submittedValues['financial_type_id']) && CRM_Utils_Array::value($this->_priceSetId, $lineItem)
 +    ) {
 +      foreach ($lineItem[$this->_priceSetId] as &$values) {
 +        $values['financial_type_id'] = $submittedValues['financial_type_id'];
 +      }
 +    }
 +
 +    if (!isset($submittedValues['total_amount'])) {
 +      $submittedValues['total_amount'] = CRM_Utils_Array::value('total_amount', $this->_values);
 +    }
 +    $this->assign('lineItem', !empty($lineItem) && !$isQuickConfig ? $lineItem : FALSE);
 +
 +    if (!empty($submittedValues['pcp_made_through_id'])) {
 +      $pcp = array();
 +      $fields = array(
 +        'pcp_made_through_id',
 +        'pcp_display_in_roll',
 +        'pcp_roll_nickname',
 +        'pcp_personal_note',
 +      );
 +      foreach ($fields as $f) {
 +        $pcp[$f] = CRM_Utils_Array::value($f, $submittedValues);
 +      }
 +    }
 +
 +    $isEmpty = array_keys(array_flip($submittedValues['soft_credit_contact_id']));
 +    if ($this->_id && count($isEmpty) == 1 && key($isEmpty) == NULL) {
 +      //Delete existing soft credit records if soft credit list is empty on update
 +      CRM_Contribute_BAO_ContributionSoft::del(array('contribution_id' => $this->_id));
 +    }
 +    else {
 +      //build soft credit params
 +      foreach ($submittedValues['soft_credit_contact_id'] as $key => $val) {
 +        if ($val && $submittedValues['soft_credit_amount'][$key]) {
 +          $softParams[$key]['contact_id'] = $val;
 +          $softParams[$key]['amount'] = CRM_Utils_Rule::cleanMoney($submittedValues['soft_credit_amount'][$key]);
 +          $softParams[$key]['soft_credit_type_id'] = $submittedValues['soft_credit_type'][$key];
 +          if (!empty($submittedValues['soft_credit_id'][$key])) {
 +            $softIDs[] = $softParams[$key]['id'] = $submittedValues['soft_credit_id'][$key];
 +          }
 +        }
 +      }
 +    }
 +
 +    // set the contact, when contact is selected
 +    if (!empty($submittedValues['contact_id'])) {
 +      $this->_contactID = $submittedValues['contact_id'];
 +    }
 +    $formValues = $submittedValues;
 +
 +    // Credit Card Contribution.
 +    if ($this->_mode) {
 +      $paramsSetByPaymentProcessingSubsystem = array(
 +        'trxn_id',
 +        'payment_instrument_id',
 +        'contribution_status_id',
 +        'cancel_date',
 +        'cancel_reason',
 +      );
 +      foreach ($paramsSetByPaymentProcessingSubsystem as $key) {
 +        if (isset($formValues[$key])) {
 +          unset($formValues[$key]);
 +        }
 +      }
 +      $contribution = $this->processCreditCard($formValues, $lineItem, $this->_contactID);
 +      foreach ($paramsSetByPaymentProcessingSubsystem as $key) {
 +        $formValues[$key] = $contribution->$key;
 +      }
 +    }
 +    else {
 +      // Offline Contribution.
 +      $submittedValues = $this->unsetCreditCardFields($submittedValues);
 +
 +      // get the required field value only.
 +
 +      $params = $ids = array();
 +
 +      $params['contact_id'] = $this->_contactID;
 +      $params['currency'] = $this->getCurrency($submittedValues);
 +
 +      $fields = array(
 +        'financial_type_id',
 +        'contribution_status_id',
 +        'payment_instrument_id',
 +        'cancel_reason',
 +        'source',
 +        'check_number',
 +      );
 +      foreach ($fields as $f) {
 +        $params[$f] = CRM_Utils_Array::value($f, $formValues);
 +      }
 +
 +      if (!empty($pcp)) {
 +        $params['pcp'] = $pcp;
 +      }
++      $params['soft_credit'] = !empty($softParams) ? $softParams : array();
++      $params['soft_credit_ids'] = !empty($softIDs) ? $softIDs : array();
 +
 +      // CRM-5740 if priceset is used, no need to cleanup money.
 +      if ($priceSetId) {
 +        $params['skipCleanMoney'] = 1;
 +      }
 +
 +      $dates = array(
 +        'receive_date',
 +        'receipt_date',
 +        'cancel_date',
 +      );
 +
 +      foreach ($dates as $d) {
 +        $params[$d] = CRM_Utils_Date::processDate($formValues[$d], $formValues[$d . '_time'], TRUE);
 +      }
 +
 +      if (!empty($formValues['is_email_receipt'])) {
 +        $params['receipt_date'] = date("Y-m-d");
 +      }
 +
 +      if ($params['contribution_status_id'] == CRM_Core_OptionGroup::getValue('contribution_status', 'Cancelled', 'name')
 +        || $params['contribution_status_id'] == CRM_Core_OptionGroup::getValue('contribution_status', 'Refunded', 'name')
 +      ) {
 +        if (CRM_Utils_System::isNull(CRM_Utils_Array::value('cancel_date', $params))) {
 +          $params['cancel_date'] = date('Y-m-d');
 +        }
 +      }
 +      else {
 +        $params['cancel_date'] = $params['cancel_reason'] = 'null';
 +      }
 +
 +      // Set is_pay_later flag for back-office offline Pending status contributions CRM-8996
 +      // else if contribution_status is changed to Completed is_pay_later flag is changed to 0, CRM-15041
 +      if ($params['contribution_status_id'] == CRM_Core_OptionGroup::getValue('contribution_status', 'Pending', 'name')) {
 +        $params['is_pay_later'] = 1;
 +      }
 +      elseif ($params['contribution_status_id'] == CRM_Core_OptionGroup::getValue('contribution_status', 'Completed', 'name')) {
 +        $params['is_pay_later'] = 0;
 +      }
 +
 +      $ids['contribution'] = $params['id'] = $this->_id;
 +
 +      // Add Additional common information to formatted params.
 +      CRM_Contribute_Form_AdditionalInfo::postProcessCommon($formValues, $params, $this);
 +      if ($pId) {
 +        $params['contribution_mode'] = 'participant';
 +        $params['participant_id'] = $pId;
 +        $params['skipLineItem'] = 1;
 +      }
 +      elseif ($isRelatedId) {
 +        $params['contribution_mode'] = 'membership';
 +      }
 +      $params['line_item'] = $lineItem;
 +      $params['payment_processor_id'] = $params['payment_processor'] = CRM_Utils_Array::value('id', $this->_paymentProcessor);
 +      if (isset($submittedValues['tax_amount'])) {
 +        $params['tax_amount'] = $submittedValues['tax_amount'];
 +      }
 +      //create contribution.
 +      if ($isQuickConfig) {
 +        $params['is_quick_config'] = 1;
 +      }
 +      $params['non_deductible_amount'] = $this->calculateNonDeductibleAmount($params, $formValues);
 +
 +      $contribution = CRM_Contribute_BAO_Contribution::create($params, $ids);
 +
 +      // process associated membership / participant, CRM-4395
 +      if ($contribution->id && $action & CRM_Core_Action::UPDATE) {
 +        $this->statusMessage[] = $this->updateRelatedComponent($contribution->id,
 +          $contribution->contribution_status_id,
 +          CRM_Utils_Array::value('contribution_status_id',
 +            $this->_values
 +          ),
 +          $contribution->receive_date
 +        );
 +      }
 +
 +      array_unshift($this->statusMessage, ts('The contribution record has been saved.'));
 +
 +      $this->invoicingPostProcessHook($submittedValues, $action, $lineItem);
 +
 +      //send receipt mail.
 +      if ($contribution->id && !empty($formValues['is_email_receipt'])) {
 +        $formValues['contact_id'] = $this->_contactID;
 +        $formValues['contribution_id'] = $contribution->id;
 +
 +        $formValues += CRM_Contribute_BAO_ContributionSoft::getSoftContribution($contribution->id);
 +
 +        // to get 'from email id' for send receipt
 +        $this->fromEmailId = $formValues['from_email_address'];
 +        if (CRM_Contribute_Form_AdditionalInfo::emailReceipt($this, $formValues)) {
 +          $this->statusMessage[] = ts('A receipt has been emailed to the contributor.');
 +        }
 +      }
 +
 +      $this->statusMessageTitle = ts('Saved');
 +
 +    }
 +
 +    if ($contribution->id && !empty($formValues['product_name'][0])) {
 +      CRM_Contribute_Form_AdditionalInfo::processPremium($submittedValues, $contribution->id,
 +        $this->_premiumID, $this->_options
 +      );
 +    }
 +
 +    if ($contribution->id && isset($submittedValues['note'])) {
 +      CRM_Contribute_Form_AdditionalInfo::processNote($submittedValues, $this->_contactID, $contribution->id, $this->_noteID);
 +    }
 +
 +    CRM_Core_Session::setStatus(implode(' ', $this->statusMessage), $this->statusMessageTitle, 'success');
 +
 +    CRM_Contribute_BAO_Contribution::updateRelatedPledge(
 +      $action,
 +      $pledgePaymentID,
 +      $contribution->id,
 +      (CRM_Utils_Array::value('option_type', $formValues) == 2) ? TRUE : FALSE,
 +      $formValues['total_amount'],
 +      CRM_Utils_Array::value('total_amount', $this->_defaults),
 +      $formValues['contribution_status_id'],
 +      CRM_Utils_Array::value('contribution_status_id', $this->_defaults)
 +    );
 +    return $contribution;
 +  }
 +
 +  /**
 +   * Assign tax calculations to contribution receipts.
 +   *
 +   * @param array $submittedValues
 +   * @param int $action
 +   * @param array $lineItem
 +   */
 +  protected function invoicingPostProcessHook($submittedValues, $action, $lineItem) {
 +
 +    $invoiceSettings = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME, 'contribution_invoice_settings');
 +    if (!CRM_Utils_Array::value('invoicing', $invoiceSettings)) {
 +      return;
 +    }
 +    $taxRate = array();
 +    $getTaxDetails = FALSE;
 +    if ($action & CRM_Core_Action::ADD) {
 +      $line = $lineItem;
 +    }
 +    elseif ($action & CRM_Core_Action::UPDATE) {
 +      $line = $this->_lineItems;
 +    }
 +    foreach ($line as $key => $value) {
 +      foreach ($value as $v) {
 +        if (isset($taxRate[(string) CRM_Utils_Array::value('tax_rate', $v)])) {
 +          $taxRate[(string) $v['tax_rate']] = $taxRate[(string) $v['tax_rate']] + CRM_Utils_Array::value('tax_amount', $v);
 +        }
 +        else {
 +          if (isset($v['tax_rate'])) {
 +            $taxRate[(string) $v['tax_rate']] = CRM_Utils_Array::value('tax_amount', $v);
 +            $getTaxDetails = TRUE;
 +          }
 +        }
 +      }
 +    }
 +
 +    if ($action & CRM_Core_Action::UPDATE) {
 +      if (isset($submittedValues['tax_amount'])) {
 +        $totalTaxAmount = $submittedValues['tax_amount'];
 +      }
 +      else {
 +        $totalTaxAmount = $this->_values['tax_amount'];
 +      }
 +      $this->assign('totalTaxAmount', $totalTaxAmount);
 +      $this->assign('dataArray', $taxRate);
 +    }
 +    else {
 +      if (!empty($submittedValues['price_set_id'])) {
 +        $this->assign('totalTaxAmount', $submittedValues['tax_amount']);
 +        $this->assign('getTaxDetails', $getTaxDetails);
 +        $this->assign('dataArray', $taxRate);
 +        $this->assign('taxTerm', CRM_Utils_Array::value('tax_term', $invoiceSettings));
 +      }
 +      else {
 +        $this->assign('totalTaxAmount', CRM_Utils_Array::value('tax_amount', $submittedValues));
 +      }
 +    }
 +  }
 +
 +  /**
 +   * Calculate non deductible amount.
 +   *
 +   * CRM-11956
 +   * if non_deductible_amount exists i.e. Additional Details field set was opened [and staff typed something] -
 +   * if non_deductible_amount does NOT exist - then calculate it depending on:
 +   * $financialType->is_deductible and whether there is a product (premium).
 +   *
 +   * @param $params
 +   * @param $formValues
 +   *
 +   * @return array
 +   */
 +  protected function calculateNonDeductibleAmount($params, $formValues) {
 +    if (!empty($params['non_deductible_amount'])) {
 +      return $params['non_deductible_amount'];
 +    }
 +    if (empty($params['non_deductible_amount'])) {
 +      $contributionType = new CRM_Financial_DAO_FinancialType();
 +      $contributionType->id = $params['financial_type_id'];
 +
 +      if ($contributionType->is_deductible) {
 +
 +        if (isset($formValues['product_name'][0])) {
 +          $selectProduct = $formValues['product_name'][0];
 +        }
 +        // if there is a product - compare the value to the contribution amount
 +        if (isset($selectProduct)) {
 +          $productDAO = new CRM_Contribute_DAO_Product();
 +          $productDAO->id = $selectProduct;
 +          $productDAO->find(TRUE);
 +          // product value exceeds contribution amount
 +          if ($params['total_amount'] < $productDAO->price) {
 +            return $params['total_amount'];
 +          }
 +          // product value does NOT exceed contribution amount
 +          else {
 +            return $productDAO->price;
 +          }
 +        }
 +        // contribution is deductible - but there is no product
 +        else {
 +          return '0.00';
 +        }
 +      }
 +      // contribution is NOT deductible
 +      else {
 +        return $params['total_amount'];
 +      }
 +    }
 +    return 0;
 +  }
 +
  }
Simple merge
index 6115e4b8452542ea0e9a6a5ea58ca4dc1d701fc1,31e293d5e5c2f5cb42a11e82d83c82a1107ba330..9c67bf3f054cbf36c8c161c18be7862544143724
@@@ -1307,13 -1449,20 +1307,20 @@@ AND civicrm_membership.is_test = %2"
        $form->_values['contribution_id'] = $membershipContributionID;
      }
  
-     if ($form->_contributeMode == 'direct') {
-       if (CRM_Utils_Array::value('contribution_status_id', $paymentResult) == 1) {
+     // Refer to CRM-16737. Payment processors 'should' return payment_status_id
+     // to denote the outcome of the transaction.
+     //
+     // In 4.7 trxn_id will no longer denote the outcome & all processor transactions must return an array
+     // containing payment_status_id.
+     // In 4.6 support (such as there was) for other ways of denoting payment outcome is retained but the use
+     // of payment_status_id is strongly encouraged.
+     if (!empty($form->_params['is_recur']) && $form->_contributeMode == 'direct') {
 -      if (!empty($membershipContribution->trxn_id) && !isset($membershipContribution->payment_status_id)
 -        || (!empty($membershipContribution->payment_status_id) && $membershipContribution->payment_status_id == 1)) {
++      if (!isset($membershipContribution->payment_status_id) && $membershipContribution->payment_status_id == 1) {
          try {
            civicrm_api3('contribution', 'completetransaction', array(
 -            'id' => $membershipContribution->id,
 -            'trxn_id' => $membershipContribution->trxn_id,
 +            'id' => $paymentResult['contribution']->id,
 +            'trxn_id' => $paymentResult['contribution']->trxn_id,
 +            'is_transactional' => FALSE,
            ));
          }
          catch (CiviCRM_API3_Exception $e) {
index 808cba5611fc04ce2e1dc9ab49845189f7818d79,6bdd3c62e869d660243b162b2df95917a35cabb2..0c86ccfb6ff41fce837b200e27c9e577df92b887
@@@ -263,29 -223,50 +263,75 @@@ class CRM_Member_Form extends CRM_Contr
      }
    }
  
 +  protected function setContextVariables($params) {
 +    $variables = array(
 +      'action' => '_action',
 +      'context' => '_context',
 +      'id' => '_id',
 +      'cid' => '_contactID',
 +      'mode' => '_mode',
 +    );
 +    foreach ($variables as $paramKey => $classVar) {
 +      if (isset($params[$paramKey]) && !isset($this->$classVar)) {
 +        $this->$classVar = $params[$paramKey];
 +      }
 +    }
 +
 +    if ($this->_mode) {
 +      $this->assignPaymentRelatedVariables();
 +    }
 +
 +    if ($this->_id) {
 +      $this->_memType = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_Membership', $this->_id, 'membership_type_id');
 +      $this->_membershipIDs[] = $this->_id;
 +    }
 +    $this->_fromEmails = CRM_Core_BAO_Email::getFromEmail();
 +  }
 +
+   /**
+    * Create a recurring contribution record.
+    *
+    * Recurring contribution parameters are set explicitly rather than merging paymentParams because it's hard
+    * to know the downstream impacts if we keep passing around the same array.
+    *
+    * @param $paymentParams
+    *
+    * @return array
+    * @throws \CiviCRM_API3_Exception
+    */
+   protected function processRecurringContribution($paymentParams) {
+     $membershipID = $paymentParams['membership_type_id'][1];
+     $contributionRecurParams = array(
+       'contact_id' => $paymentParams['contactID'],
+       'amount' => $paymentParams['total_amount'],
+       'payment_processor_id' => $paymentParams['payment_processor_id'],
+       'campaign_id' => CRM_Utils_Array::value('campaign_id', $paymentParams),
+       'financial_type_id' => $paymentParams['financial_type_id'],
+       'is_email_receipt' => CRM_Utils_Array::value('is_email_receipt', $paymentParams),
+       // This is not great as it could also be direct debit - but is consistent with elsewhere & all need fixing.
+       'payment_instrument_id' => 1,
+       'invoice_id' => CRM_Utils_Array::value('invoiceID ', $paymentParams),
+     );
+     $mapping = array(
+       'frequency_interval' => 'duration_interval',
+       'frequency_unit' => 'duration_unit',
+     );
+     $membershipType = civicrm_api3('MembershipType', 'getsingle', array(
+       'id' => $membershipID,
+       'return' => $mapping,
+     ));
+     foreach ($mapping as $recurringFieldName => $membershipTypeFieldName) {
+       $contributionRecurParams[$recurringFieldName] = $membershipType[$membershipTypeFieldName];
+     }
+     $contributionRecur = civicrm_api3('ContributionRecur', 'create', $contributionRecurParams);
+     $returnParams = array(
+       'contributionRecurID' => $contributionRecur['id'],
+       'is_recur' => TRUE,
+     );
+     return $returnParams;
+   }
  }
Simple merge
index 20f52c159085438e4b1424cdb142d195a7008345,7a1c5c7a8e9961a69aa54231da60305639b76c6a..bd812c5b9e0842fbedc0dc9b66781a6e1cd27755
@@@ -559,8 -580,9 +559,8 @@@ class CRM_Member_Form_MembershipRenewa
        $this->_params['description'] = ts('Office Credit Card Membership Renewal Contribution');
        $this->_params['ip_address'] = CRM_Utils_System::ipAddress();
        $this->_params['amount'] = $formValues['total_amount'];
 -      $this->_params['currencyID'] = $config->defaultCurrency;
 -      $this->_params['payment_action'] = 'Sale';
 +      $this->_params['currencyID'] = CRM_Core_Config::singleton()->defaultCurrency;
-       $this->_params['invoiceID'] = md5(uniqid(rand(), TRUE));
+       $paymentParams['invoiceID'] = $this->_params['invoiceID'] = md5(uniqid(rand(), TRUE));
  
        // 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 passed params
  
        $payment = CRM_Core_Payment::singleton($this->_mode, $this->_paymentProcessor, $this);
  
 -      $result = &$payment->doDirectPayment($paymentParams);
+       if (!empty($paymentParams['auto_renew'])) {
+         $contributionRecurParams = $this->processRecurringContribution($paymentParams);
+         $paymentParams = array_merge($paymentParams, $contributionRecurParams);
+       }
 +      $result = $payment->doDirectPayment($paymentParams);
  
        if (is_a($result, 'CRM_Core_Error')) {
          CRM_Core_Error::displaySessionError($result);
Simple merge
index d2d1dfa926b9d39f197c7dee49d80e6c0a3e9c9c,c42bc70703d4f62ee2a38e3cfbf5faf541cc6f3c..96e8225415500addc275c9186ac8f32b02728c32
@@@ -299,13 -328,13 +299,13 @@@ class api_v3_ContributionPageTest exten
     * - the first creates a new membership, completed contribution, in progress recurring. Check these
     * - create another - end date should be extended
     */
-   public function testSubmitMembershipPriceSetPaymentPaymentProcessorRecurNow() {
 -  public function testLegacySubmitMembershipPriceSetPaymentPaymentProcessorRecur() {
++  public function testSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPayment() {
      $this->params['is_recur'] = 1;
      $var = array();
      $this->params['recur_frequency_unit'] = 'month';
      $this->setUpMembershipContributionPage();
-     $dummyPP = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor);
-     $dummyPP->setDoDirectPaymentResult(array('contribution_status_id' => 1, 'trxn_id' => 'create_first_success'));
+     $dummyPP = CRM_Core_Payment::singleton('live', $this->_paymentProcessor);
 -    $dummyPP->setDoDirectPaymentResult(array('contribution_status_id' => 1, 'trxn_id' => 'create_first_success'));
++    $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 1, 'trxn_id' => 'create_first_success'));
  
      $submitParams = array(
        'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
      //$this->callAPISuccess('line_item', 'getsingle', array('contribution_id' => $contribution['id'], 'entity_id' => $membership['id']));
      //renew it with processor setting completed - should extend membership
      $submitParams['contact_id'] = $contribution['contact_id'];
--    $dummyPP->setDoDirectPaymentResult(array('contribution_status_id' => 1, 'trxn_id' => 'create_second_success'));
++    $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 1, 'trxn_id' => 'create_second_success'));
      $this->callAPISuccess('contribution_page', 'submit', $submitParams);
      $this->callAPISuccess('contribution', 'getsingle', array(
        'id' => array('NOT IN' => array($contribution['id'])),
      $this->params['is_recur'] = 1;
      $this->params['recur_frequency_unit'] = 'month';
      $this->setUpMembershipContributionPage();
 -    $dummyPP = CRM_Core_Payment::singleton('live', $this->_paymentProcessor);
 +    $dummyPP = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor);
-     $dummyPP->setDoDirectPaymentResult(array('contribution_status_id' => 2));
+     $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 2));
  
      $submitParams = array(
        'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),