Merge pull request #9449 from eileenmcnaughton/member
authorEileen McNaughton <eileen@mcnaughty.com>
Thu, 24 Nov 2016 23:16:47 +0000 (12:16 +1300)
committerGitHub <noreply@github.com>
Thu, 24 Nov 2016 23:16:47 +0000 (12:16 +1300)
CRM-19594 fix line item memberships for price sets with multiple memb…

CRM/Contribute/Form/Contribution/Confirm.php
CRM/Contribute/Form/Contribution/Main.php
tests/phpunit/CiviTest/CiviUnitTestCase.php
tests/phpunit/api/v3/ContributionPageTest.php
tests/phpunit/api/v3/ContributionTest.php

index 807a1e0cd4ec47c1d479291b5f7704ca9d490432..e4fcfd4817b8b8c0b585c19a3ccb655b5c8b0eb6 100644 (file)
@@ -1456,6 +1456,7 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
     $errors = $paymentResults = array();
     $form->_values['isMembership'] = TRUE;
     $isRecurForFirstTransaction = CRM_Utils_Array::value('is_recur', $form->_values, CRM_Utils_Array::value('is_recur', $membershipParams));
+    $unprocessedLineItems = $form->_lineItem;
     $totalAmount = $membershipParams['amount'];
 
     if ($isPaidMembership) {
@@ -1521,7 +1522,26 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
     //@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());
+
+      $membershipLines = $nonMembershipLines = array();
+      foreach ($unprocessedLineItems as $priceSetID => $lines) {
+        foreach ($lines as $line) {
+          if (!empty($line['membership_type_id'])) {
+            $membershipLines[$line['membership_type_id']] = $line['price_field_value_id'];
+          }
+        }
+      }
+
+      $i = 1;
       foreach ($membershipTypeIDs as $memType) {
+        if ($i < count($membershipTypeIDs)) {
+          $membershipLineItems[$priceSetID][$membershipLines[$memType]] = $unprocessedLineItems[$priceSetID][$membershipLines[$memType]];
+          unset($unprocessedLineItems[$priceSetID][$membershipLines[$memType]]);
+        }
+        else {
+          $membershipLineItems = $unprocessedLineItems;
+        }
+        $i++;
         $numTerms = CRM_Utils_Array::value($memType, $typesTerms, 1);
         if (!empty($membershipContribution)) {
           $pendingStatus = CRM_Core_OptionGroup::getValue('contribution_status', 'Pending', 'name');
@@ -1557,7 +1577,7 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
           $customFieldsFormatted,
           $numTerms, $membershipID, $pending,
           $contributionRecurID, $membershipSource, $isPayLater, $campaignId, array(), $membershipContribution,
-          $form->_lineItem
+          $membershipLineItems
         );
 
         $form->set('renewal_mode', $renewalMode);
@@ -1936,9 +1956,20 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
     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);
+    $membershipPriceFieldIDs = array();
+    foreach ((array) $lineItems as $lineItem) {
+      if (!empty($lineItem['membership_type_id'])) {
+        $form->set('useForMember', 1);
+        $form->_useForMember = 1;
+        $membershipPriceFieldIDs['id'] = $priceSetID;
+        $membershipPriceFieldIDs[] = $lineItem['price_field_value_id'];
+      }
+    }
+    $form->set('memberPriceFieldIDS', $membershipPriceFieldIDs);
     $form->processFormSubmission(CRM_Utils_Array::value('contact_id', $params));
   }
 
@@ -2325,7 +2356,7 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
     $priceFieldIds = $this->get('memberPriceFieldIDS');
 
     if (!empty($priceFieldIds)) {
-      $contributionTypeID = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $priceFieldIds['id'], 'financial_type_id');
+      $financialTypeID = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $priceFieldIds['id'], 'financial_type_id');
       unset($priceFieldIds['id']);
       $membershipTypeIds = array();
       $membershipTypeTerms = array();
@@ -2344,7 +2375,7 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
         }
       }
       $membershipParams['selectMembership'] = $membershipTypeIds;
-      $membershipParams['financial_type_id'] = $contributionTypeID;
+      $membershipParams['financial_type_id'] = $financialTypeID;
       $membershipParams['types_terms'] = $membershipTypeTerms;
     }
     if (!empty($membershipParams['selectMembership'])) {
index 981fbfa105101af7e5abb717f205017481e14536..363ca318909cf0b8db43ccafc0fcac2ff845fa0c 100644 (file)
@@ -1054,7 +1054,7 @@ class CRM_Contribute_Form_Contribution_Main extends CRM_Contribute_Form_Contribu
 
     $params['currencyID'] = CRM_Core_Config::singleton()->defaultCurrency;
 
-    $is_quick_config = 0;
+    // @todo refactor this & leverage it from the unit tests.
     if (!empty($params['priceSetId'])) {
       $is_quick_config = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId, 'is_quick_config');
       if ($is_quick_config) {
@@ -1118,7 +1118,7 @@ class CRM_Contribute_Form_Contribution_Main extends CRM_Contribute_Form_Contribu
       }
     }
     //If the membership & contribution is used in contribution page & not separate payment
-    $fieldId = $memPresent = $membershipLabel = $fieldOption = $is_quick_config = NULL;
+    $memPresent = $membershipLabel = $fieldOption = $is_quick_config = NULL;
     $proceFieldAmount = 0;
     if (property_exists($this, '_separateMembershipPayment') && $this->_separateMembershipPayment == 0) {
       $is_quick_config = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId, 'is_quick_config');
index 1fd3c05d1d777cf867af78a63c59b75faee6f994..6e9e6c1541b436424e26a7148209fa2fd6ca9b08 100644 (file)
@@ -3647,4 +3647,78 @@ AND    ( TABLE_NAME LIKE 'civicrm_value_%' )
     return Civi::settings()->set('contribution_invoice_settings', $contributeSetting);
   }
 
+  /**
+   * Create price set with contribution test for test setup.
+   *
+   * This could be merged with 4.5 function setup in api_v3_ContributionPageTest::setUpContributionPage
+   * on parent class at some point (fn is not in 4.4).
+   *
+   * @param $entity
+   * @param array $params
+   */
+  public function createPriceSetWithPage($entity = NULL, $params = array()) {
+    $membershipTypeID = $this->membershipTypeCreate(array('name' => 'Special'));
+    $contributionPageResult = $this->callAPISuccess('contribution_page', 'create', array(
+      'title' => "Test Contribution Page",
+      'financial_type_id' => 1,
+      'currency' => 'NZD',
+      'goal_amount' => 50,
+      'is_pay_later' => 1,
+      'is_monetary' => TRUE,
+      'is_email_receipt' => FALSE,
+    ));
+    $priceSet = $this->callAPISuccess('price_set', 'create', array(
+      'is_quick_config' => 0,
+      'extends' => 'CiviMember',
+      'financial_type_id' => 1,
+      'title' => 'my Page',
+    ));
+    $priceSetID = $priceSet['id'];
+
+    CRM_Price_BAO_PriceSet::addTo('civicrm_contribution_page', $contributionPageResult['id'], $priceSetID);
+    $priceField = $this->callAPISuccess('price_field', 'create', array(
+      'price_set_id' => $priceSetID,
+      'label' => 'Goat Breed',
+      'html_type' => 'Radio',
+    ));
+    $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
+        'price_set_id' => $priceSetID,
+        'price_field_id' => $priceField['id'],
+        'label' => 'Long Haired Goat',
+        'amount' => 20,
+        'financial_type_id' => 'Donation',
+        'membership_type_id' => $membershipTypeID,
+        'membership_num_terms' => 1,
+      )
+    );
+    $this->_ids['price_field_value'] = array($priceFieldValue['id']);
+    $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
+        'price_set_id' => $priceSetID,
+        'price_field_id' => $priceField['id'],
+        'label' => 'Shoe-eating Goat',
+        'amount' => 10,
+        'financial_type_id' => 'Donation',
+        'membership_type_id' => $membershipTypeID,
+        'membership_num_terms' => 2,
+      )
+    );
+    $this->_ids['price_field_value'][] = $priceFieldValue['id'];
+
+    $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
+        'price_set_id' => $priceSetID,
+        'price_field_id' => $priceField['id'],
+        'label' => 'Shoe-eating Goat',
+        'amount' => 10,
+        'financial_type_id' => 'Donation',
+      )
+    );
+    $this->_ids['price_field_value']['cont'] = $priceFieldValue['id'];
+
+    $this->_ids['price_set'] = $priceSetID;
+    $this->_ids['contribution_page'] = $contributionPageResult['id'];
+    $this->_ids['price_field'] = array($priceField['id']);
+
+    $this->_ids['membership_type'] = $membershipTypeID;
+  }
+
 }
index 014da9d8ff890ed89cfbfabfdb36790fb1d3aaef..fbe0e5c8bc24f5caa2b535c57a40d1cf18fd2ea5 100644 (file)
@@ -664,7 +664,7 @@ class api_v3_ContributionPageTest extends CiviUnitTestCase {
    * - the first creates a new membership, completed contribution, in progress recurring. Check these
    * - create another - end date should be extended
    */
-  public function testSubmitMembershipComplexPriceSetPaymentPaymentProcessorRecurInstantPayment() {
+  public function testSubmitMembershipComplexNonPriceSetPaymentPaymentProcessorRecurInstantPayment() {
     $this->params['is_recur'] = 1;
     $this->params['recur_frequency_unit'] = 'month';
     // Add a membership so membership & contribution are not both 1.
@@ -740,6 +740,140 @@ class api_v3_ContributionPageTest extends CiviUnitTestCase {
     $this->assertEquals(5, $recurringContribution['contribution_status_id']);
   }
 
+  /**
+   * Test submit recurring membership with immediate confirmation (IATS style).
+   *
+   * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
+   * processor (IATS style - denoted by returning trxn_id)
+   * - the first creates a new membership, completed contribution, in progress recurring. Check these
+   * - create another - end date should be extended
+   */
+  public function testSubmitMembershipComplexPriceSetPaymentPaymentProcessorRecurInstantPayment() {
+    $this->params['is_recur'] = 1;
+    $this->params['recur_frequency_unit'] = 'month';
+    // Add a membership so membership & contribution are not both 1.
+    $preExistingMembershipID = $this->contactMembershipCreate(array('contact_id' => $this->contactIds[0]));
+    $this->createPriceSetWithPage();
+    $this->addSecondOrganizationMembershipToPriceSet();
+    $this->setupPaymentProcessor();
+
+    $dummyPP = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor);
+    $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 1, 'trxn_id' => 'create_first_success'));
+    $processor = $dummyPP->getPaymentProcessor();
+
+    $submitParams = array(
+      'price_' . $this->_ids['price_field'][0] => $this->_ids['price_field_value']['cont'],
+      'price_' . $this->_ids['price_field']['org1'] => $this->_ids['price_field_value']['org1'],
+      'price_' . $this->_ids['price_field']['org2'] => $this->_ids['price_field_value']['org2'],
+      'id' => (int) $this->_ids['contribution_page'],
+      'amount' => 10,
+      'billing_first_name' => 'Billy',
+      'billing_middle_name' => 'Goat',
+      'billing_last_name' => 'Gruff',
+      'email' => 'billy@goat.gruff',
+      'selectMembership' => NULL,
+      'payment_processor_id' => 1,
+      'credit_card_number' => '4111111111111111',
+      'credit_card_type' => 'Visa',
+      'credit_card_exp_date' => array('M' => 9, 'Y' => 2040),
+      'cvv2' => 123,
+      'frequency_interval' => 1,
+      'frequency_unit' => 'month',
+    );
+
+    $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
+    $contribution = $this->callAPISuccess('contribution', 'getsingle', array(
+      'contribution_page_id' => $this->_ids['contribution_page'],
+      'contribution_status_id' => 1,
+    ));
+    $this->assertEquals($processor['payment_instrument_id'], $contribution['payment_instrument_id']);
+
+    $this->assertEquals('create_first_success', $contribution['trxn_id']);
+    $membershipPayments = $this->callAPISuccess('membership_payment', 'get', array(
+      'sequential' => 1,
+      'contribution_id' => $contribution['id'],
+    ));
+    $this->assertEquals(2, $membershipPayments['count']);
+    $lines = $this->callAPISuccess('line_item', 'get', array('sequential' => 1, 'contribution_id' => $contribution['id']));
+    $this->assertEquals(3, $lines['count']);
+    $this->assertEquals('civicrm_membership', $lines['values'][0]['entity_table']);
+    $this->assertEquals($preExistingMembershipID + 1, $lines['values'][0]['entity_id']);
+    $this->assertEquals('civicrm_contribution', $lines['values'][1]['entity_table']);
+    $this->assertEquals($contribution['id'], $lines['values'][1]['entity_id']);
+    $this->assertEquals('civicrm_membership', $lines['values'][2]['entity_table']);
+    $this->assertEquals($preExistingMembershipID + 2, $lines['values'][2]['entity_id']);
+
+    $this->callAPISuccessGetSingle('MembershipPayment', array('contribution_id' => $contribution['id'], 'membership_id' => $preExistingMembershipID + 1));
+    $membership = $this->callAPISuccessGetSingle('membership', array('id' => $preExistingMembershipID + 1));
+
+    //renew it with processor setting completed - should extend membership
+    $submitParams['contact_id'] = $contribution['contact_id'];
+    $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 1, 'trxn_id' => 'create_second_success'));
+    $this->callAPISuccess('contribution_page', 'submit', $submitParams);
+    $renewContribution = $this->callAPISuccess('contribution', 'getsingle', array(
+      'id' => array('NOT IN' => array($contribution['id'])),
+      'contribution_page_id' => $this->_ids['contribution_page'],
+      'contribution_status_id' => 1,
+    ));
+    $lines = $this->callAPISuccess('line_item', 'get', array('sequential' => 1, 'contribution_id' => $renewContribution['id']));
+    $this->assertEquals(3, $lines['count']);
+    $this->assertEquals('civicrm_membership', $lines['values'][0]['entity_table']);
+    $this->assertEquals($preExistingMembershipID + 1, $lines['values'][0]['entity_id']);
+    $this->assertEquals('civicrm_contribution', $lines['values'][1]['entity_table']);
+    $this->assertEquals($renewContribution['id'], $lines['values'][1]['entity_id']);
+
+    $renewedMembership = $this->callAPISuccessGetSingle('membership', array('id' => $preExistingMembershipID + 1));
+    $this->assertEquals(date('Y-m-d', strtotime('+ 1 year', strtotime($membership['end_date']))), $renewedMembership['end_date']);
+  }
+
+  /**
+   * Extend the price set with a second organisation's membership.
+   */
+  public function addSecondOrganizationMembershipToPriceSet() {
+    $organization2ID = $this->organizationCreate();
+    $membershipTypes = $this->callAPISuccess('MembershipType', 'get', array());
+    $this->_ids['membership_type'] = array_keys($membershipTypes['values']);
+    $this->_ids['membership_type']['org2'] = $this->membershipTypeCreate(array('contact_id' => $organization2ID, 'name' => 'Org 2'));
+    $priceField = $this->callAPISuccess('PriceField', 'create', array(
+      'price_set_id' => $this->_ids['price_set'],
+      'html_type' => 'Radio',
+      'name' => 'Org1 Price',
+      'label' => 'Org1Price',
+    ));
+    $this->_ids['price_field']['org1'] = $priceField['id'];
+
+    $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
+      'name' => 'org1 amount',
+      'label' => 'org 1 Amount',
+      'amount' => 2,
+      'financial_type_id' => 'Member Dues',
+      'format.only_id' => TRUE,
+      'membership_type_id' => reset($this->_ids['membership_type']),
+      'price_field_id' => $priceField['id'],
+    ));
+    $this->_ids['price_field_value']['org1'] = $priceFieldValue;
+
+    $priceField = $this->callAPISuccess('PriceField', 'create', array(
+      'price_set_id' => $this->_ids['price_set'],
+      'html_type' => 'Radio',
+      'name' => 'Org2 Price',
+      'label' => 'Org2Price',
+    ));
+    $this->_ids['price_field']['org2'] = $priceField['id'];
+
+    $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
+      'name' => 'org2 amount',
+      'label' => 'org 2 Amount',
+      'amount' => 200,
+      'financial_type_id' => 'Member Dues',
+      'format.only_id' => TRUE,
+      'membership_type_id' => $this->_ids['membership_type']['org2'],
+      'price_field_id' => $priceField['id'],
+    ));
+    $this->_ids['price_field_value']['org2'] = $priceFieldValue;
+
+  }
+
   /**
    * Test submit recurring membership with immediate confirmation (IATS style).
    *
@@ -944,7 +1078,7 @@ class api_v3_ContributionPageTest extends CiviUnitTestCase {
       $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
         'name' => 'membership_amount',
         'label' => 'Membership Amount',
-        'amount' => 1,
+        'amount' => 2,
         'financial_type_id' => 'Donation',
         'format.only_id' => TRUE,
         'membership_type_id' => $membershipTypeID,
@@ -952,6 +1086,27 @@ class api_v3_ContributionPageTest extends CiviUnitTestCase {
       ));
       $this->_ids['price_field_value'][] = $priceFieldValue;
     }
+    if (!empty($this->_ids['membership_type']['org2'])) {
+      $priceField = $this->callAPISuccess('price_field', 'create', array(
+        'price_set_id' => reset($this->_ids['price_set']),
+        'name' => 'membership_org2',
+        'label' => 'Membership Org2',
+        'html_type' => 'Checkbox',
+        'sequential' => 1,
+      ));
+      $this->_ids['price_field']['org2'] = $priceField['id'];
+
+      $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
+        'name' => 'membership_org2',
+        'label' => 'Membership org 2',
+        'amount' => 55,
+        'financial_type_id' => 'Member Dues',
+        'format.only_id' => TRUE,
+        'membership_type_id' => $this->_ids['membership_type']['org2'],
+        'price_field_id' => $priceField['id'],
+      ));
+      $this->_ids['price_field_value']['org2'] = $priceFieldValue;
+    }
     $priceField = $this->callAPISuccess('price_field', 'create', array(
       'price_set_id' => reset($this->_ids['price_set']),
       'name' => 'Contribution',
index 573c7595a97332e6bcb91e10ec366acc8d2f751e..b757fc877aa3dcb29a5f1185ec63f00a0e090e72 100644 (file)
@@ -1708,7 +1708,6 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
     $this->revertTemplateToReservedTemplate();
   }
 
-
   /**
    * Test to check whether contact billing address is used when no contribution address
    */
@@ -2503,70 +2502,6 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
     $this->contactDelete($this->_ids['contact']);
   }
 
-
-  /**
-   * Create price set with contribution test for test setup.
-   *
-   * This could be merged with 4.5 function setup in api_v3_ContributionPageTest::setUpContributionPage
-   * on parent class at some point (fn is not in 4.4).
-   *
-   * @param $entity
-   * @param array $params
-   */
-  public function createPriceSetWithPage($entity, $params = array()) {
-    $membershipTypeID = $this->membershipTypeCreate();
-    $contributionPageResult = $this->callAPISuccess('contribution_page', 'create', array(
-      'title' => "Test Contribution Page",
-      'financial_type_id' => 1,
-      'currency' => 'NZD',
-      'goal_amount' => 50,
-      'is_pay_later' => 1,
-      'is_monetary' => TRUE,
-      'is_email_receipt' => FALSE,
-    ));
-    $priceSet = $this->callAPISuccess('price_set', 'create', array(
-      'is_quick_config' => 0,
-      'extends' => 'CiviMember',
-      'financial_type_id' => 1,
-      'title' => 'my Page',
-    ));
-    $priceSetID = $priceSet['id'];
-
-    CRM_Price_BAO_PriceSet::addTo('civicrm_contribution_page', $contributionPageResult['id'], $priceSetID);
-    $priceField = $this->callAPISuccess('price_field', 'create', array(
-      'price_set_id' => $priceSetID,
-      'label' => 'Goat Breed',
-      'html_type' => 'Radio',
-    ));
-    $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
-        'price_set_id' => $priceSetID,
-        'price_field_id' => $priceField['id'],
-        'label' => 'Long Haired Goat',
-        'amount' => 20,
-        'financial_type_id' => 'Donation',
-        'membership_type_id' => $membershipTypeID,
-        'membership_num_terms' => 1,
-      )
-    );
-    $this->_ids['price_field_value'] = array($priceFieldValue['id']);
-    $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
-        'price_set_id' => $priceSetID,
-        'price_field_id' => $priceField['id'],
-        'label' => 'Shoe-eating Goat',
-        'amount' => 10,
-        'financial_type_id' => 'Donation',
-        'membership_type_id' => $membershipTypeID,
-        'membership_num_terms' => 2,
-      )
-    );
-    $this->_ids['price_field_value'][] = $priceFieldValue['id'];
-    $this->_ids['price_set'] = $priceSetID;
-    $this->_ids['contribution_page'] = $contributionPageResult['id'];
-    $this->_ids['price_field'] = array($priceField['id']);
-
-    $this->_ids['membership_type'] = $membershipTypeID;
-  }
-
   /**
    * Set up a pending transaction with a specific price field id.
    *