3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
13 * File for the MembershipTest class
17 * @author Walt Haas <walt@dharmatech.org> (801) 534-1262
20 use Civi\Api4\FinancialType
;
23 * Test CRM_Member_Form_Membership functions.
28 class CRM_Member_Form_MembershipTest
extends CiviUnitTestCase
{
30 use CRMTraits_Financial_OrderTrait
;
31 use CRMTraits_Financial_PriceSetTrait
;
36 protected $_individualId;
37 protected $_contribution;
38 protected $_financialTypeId = 1;
39 protected $_entity = 'Membership';
42 protected $_paymentProcessorID;
45 * Membership type ID for annual fixed membership.
49 protected $membershipTypeAnnualFixedID;
52 * Parameters to create payment processor.
56 protected $_processorParams = [];
59 * ID of created membership.
63 protected $_membershipID;
66 * Payment instrument mapping.
70 protected $paymentInstruments = [];
78 * Test setup for every test.
80 * Connect to the database, truncate the tables that will be used
81 * and redirect stdin to a temporary file.
83 * @throws \CRM_Core_Exception
84 * @throws \CiviCRM_API3_Exception
86 public function setUp() {
87 $this->_apiversion
= 3;
90 $this->_individualId
= $this->individualCreate();
91 $this->_paymentProcessorID
= $this->processorCreate();
93 $this->ids
['contact']['organization'] = $this->organizationCreate();
94 $this->ids
['contact']['organization2'] = $this->organizationCreate();
95 $this->ids
['relationship_type']['member'] = $this->callAPISuccess('RelationshipType', 'create', [
96 'name_a_b' => 'Member of',
97 'label_a_b' => 'Member of',
98 'name_b_a' => 'Member is',
99 'label_b_a' => 'Member is',
100 'contact_type_a' => 'Individual',
101 'contact_type_b' => 'Organization',
103 $this->ids
['membership_type']['AnnualFixed'] = $this->callAPISuccess('MembershipType', 'create', [
105 'name' => 'AnnualFixed',
106 'member_of_contact_id' => $this->ids
['contact']['organization'],
107 'duration_unit' => 'year',
109 'duration_interval' => 1,
110 'period_type' => 'fixed',
111 'fixed_period_start_day' => '101',
112 'fixed_period_rollover_day' => '1231',
113 'relationship_type_id' => [$this->ids
['relationship_type']['member']],
114 'relationship_direction' => ['b_a'],
115 'financial_type_id' => 2,
118 $this->ids
['membership_type']['AnnualRolling'] = $this->callAPISuccess('MembershipType', 'create', [
119 'name' => 'AnnualRolling',
120 'member_of_contact_id' => $this->ids
['contact']['organization'],
121 'duration_unit' => 'year',
122 'duration_interval' => 1,
123 'period_type' => 'rolling',
124 'relationship_type_id' => [$this->ids
['relationship_type']['member']],
125 'relationship_direction' => ['b_a'],
126 'financial_type_id' => 'Member Dues',
129 $this->ids
['membership_type']['AnnualRollingOrg2'] = $this->callAPISuccess('MembershipType', 'create', [
130 'name' => 'AnnualRolling1',
131 'member_of_contact_id' => $this->ids
['contact']['organization2'],
132 'duration_unit' => 'year',
133 'duration_interval' => 1,
134 'period_type' => 'rolling',
135 'relationship_type_id' => [$this->ids
['relationship_type']['member']],
136 'relationship_direction' => ['b_a'],
137 'financial_type_id' => 'Member Dues',
140 $this->ids
['membership_type']['lifetime'] = $this->callAPISuccess('MembershipType', 'create', [
141 'name' => 'Lifetime',
142 'member_of_contact_id' => $this->ids
['contact']['organization'],
143 'duration_unit' => 'lifetime',
144 'duration_interval' => 1,
145 'relationship_type_id' => $this->ids
['relationship_type']['member'],
146 'relationship_direction' => 'b_a',
147 'financial_type_id' => 'Member Dues',
148 'period_type' => 'rolling',
151 $instruments = $this->callAPISuccess('Contribution', 'getoptions', ['field' => 'payment_instrument_id']);
152 $this->paymentInstruments
= $instruments['values'];
156 * Clean up after each test.
158 * @throws \CRM_Core_Exception
160 public function tearDown() {
161 $this->quickCleanUpFinancialEntities();
164 'civicrm_relationship',
165 'civicrm_membership_type',
166 'civicrm_membership',
171 $this->callAPISuccess('Contact', 'delete', ['id' => $this->ids
['contact']['organization'], 'skip_undelete' => TRUE]);
172 $this->callAPISuccess('RelationshipType', 'delete', ['id' => $this->ids
['relationship_type']['member']]);
176 * Test CRM_Member_Form_Membership::formRule() with a parameter
177 * that has an empty contact_select_id value
179 * @throws \CiviCRM_API3_Exception
180 * @throws \CRM_Core_Exception
182 public function testFormRuleEmptyContact(): void
{
184 'contact_select_id' => 0,
185 'membership_type_id' => [1 => NULL],
188 $obj = new CRM_Member_Form_Membership();
189 $rc = CRM_Member_Form_Membership
::formRule($params, $files, $obj);
190 $this->assertType('array', $rc);
191 $this->assertTrue(array_key_exists('membership_type_id', $rc));
193 $params['membership_type_id'] = [1 => 3];
194 $rc = CRM_Member_Form_Membership
::formRule($params, $files, $obj);
195 $this->assertType('array', $rc);
196 $this->assertTrue(array_key_exists('join_date', $rc));
200 * Test that form rule fails if start date is before join date.
202 * Test CRM_Member_Form_Membership::formRule() with a parameter
203 * that has an start date before the join date and a rolling
206 public function testFormRuleRollingEarlyStart() {
208 $unixYesterday = $unixNow - (24 * 60 * 60);
209 $ymdYesterday = date('Y-m-d', $unixYesterday);
211 'join_date' => date('Y-m-d'),
212 'start_date' => $ymdYesterday,
214 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualRolling']],
217 $obj = new CRM_Member_Form_Membership();
218 $rc = CRM_Member_Form_Membership
::formRule($params, $files, $obj);
219 $this->assertType('array', $rc);
220 $this->assertTrue(array_key_exists('start_date', $rc));
224 * Test CRM_Member_Form_Membership::formRule() with a parameter
225 * that has an end date before the start date and a rolling
228 public function testFormRuleRollingEarlyEnd() {
230 $unixYesterday = $unixNow - (24 * 60 * 60);
231 $ymdYesterday = date('Y-m-d', $unixYesterday);
233 'join_date' => date('Y-m-d'),
234 'start_date' => date('Y-m-d'),
235 'end_date' => $ymdYesterday,
236 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualRolling']],
239 $obj = new CRM_Member_Form_Membership();
240 $rc = CRM_Member_Form_Membership
::formRule($params, $files, $obj);
241 $this->assertType('array', $rc);
242 $this->assertTrue(array_key_exists('end_date', $rc));
246 * Test CRM_Member_Form_Membership::formRule() with end date but no start date and a rolling membership type.
248 public function testFormRuleRollingEndNoStart() {
250 $unixYearFromNow = $unixNow +
(365 * 24 * 60 * 60);
251 $ymdYearFromNow = date('Y-m-d', $unixYearFromNow);
253 'join_date' => date('Y-m-d'),
255 'end_date' => $ymdYearFromNow,
256 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualRolling']],
259 $obj = new CRM_Member_Form_Membership();
260 $rc = $obj::formRule($params, $files, $obj);
261 $this->assertType('array', $rc);
262 $this->assertTrue(array_key_exists('start_date', $rc));
266 * Test CRM_Member_Form_Membership::formRule() with a parameter
267 * that has an end date and a lifetime membership type
269 public function testFormRuleRollingLifetimeEnd() {
271 $unixYearFromNow = $unixNow +
(365 * 24 * 60 * 60);
273 'join_date' => date('Y-m-d'),
274 'start_date' => date('Y-m-d'),
275 'end_date' => date('Y-m-d',
278 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['lifetime']],
281 $obj = new CRM_Member_Form_Membership();
282 $rc = $obj::formRule($params, $files, $obj);
283 $this->assertType('array', $rc);
284 $this->assertTrue(array_key_exists('status_id', $rc));
288 * Test CRM_Member_Form_Membership::formRule() with a parameter
289 * that has permanent override and no status
291 * @throws \CiviCRM_API3_Exception
292 * @throws \CRM_Core_Exception
294 public function testFormRulePermanentOverrideWithNoStatus() {
296 'join_date' => date('Y-m-d'),
297 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualFixed']],
298 'is_override' => TRUE,
301 $obj = new CRM_Member_Form_Membership();
302 $rc = $obj::formRule($params, $files, $obj);
303 $this->assertType('array', $rc);
304 $this->assertTrue(array_key_exists('status_id', $rc));
307 public function testFormRuleUntilDateOverrideWithValidOverrideEndDate() {
309 'join_date' => date('Y-m-d'),
310 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualFixed']],
311 'is_override' => TRUE,
313 'status_override_end_date' => date('Y-m-d'),
316 $membershipForm = new CRM_Member_Form_Membership();
317 $validationResponse = CRM_Member_Form_Membership
::formRule($params, $files, $membershipForm);
318 $this->assertTrue($validationResponse);
321 public function testFormRuleUntilDateOverrideWithNoOverrideEndDate() {
323 'join_date' => date('Y-m-d'),
324 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualFixed']],
325 'is_override' => CRM_Member_StatusOverrideTypes
::UNTIL_DATE
,
329 $membershipForm = new CRM_Member_Form_Membership();
330 $validationResponse = CRM_Member_Form_Membership
::formRule($params, $files, $membershipForm);
331 $this->assertType('array', $validationResponse);
332 $this->assertEquals('Please enter the Membership override end date.', $validationResponse['status_override_end_date']);
336 * Test CRM_Member_Form_Membership::formRule() with a join date
337 * of one month from now and a rolling membership type
339 public function testFormRuleRollingJoin1MonthFromNow() {
341 $unix1MFmNow = $unixNow +
(31 * 24 * 60 * 60);
343 'join_date' => date('Y-m-d', $unix1MFmNow),
346 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualRolling']],
349 $obj = new CRM_Member_Form_Membership();
350 $rc = $obj::formRule($params, $files, $obj);
352 // Should have found no valid membership status.
353 $this->assertType('array', $rc);
354 $this->assertTrue(array_key_exists('_qf_default', $rc));
358 * Test CRM_Member_Form_Membership::formRule() with a join date of today and a rolling membership type.
360 public function testFormRuleRollingJoinToday() {
362 'join_date' => date('Y-m-d'),
365 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualRolling']],
368 $obj = new CRM_Member_Form_Membership();
369 $rc = $obj::formRule($params, $files, $obj);
371 // Should have found New membership status
372 $this->assertTrue($rc);
376 * Test CRM_Member_Form_Membership::formRule() with a join date
377 * of one month ago and a rolling membership type
379 public function testFormRuleRollingJoin1MonthAgo() {
381 $unix1MAgo = $unixNow - (31 * 24 * 60 * 60);
383 'join_date' => date('Y-m-d', $unix1MAgo),
386 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualRolling']],
389 $obj = new CRM_Member_Form_Membership();
390 $rc = $obj::formRule($params, $files, $obj);
392 // Should have found New membership status.
393 $this->assertTrue($rc);
397 * Test CRM_Member_Form_Membership::formRule() with a join date of six months ago and a rolling membership type.
399 public function testFormRuleRollingJoin6MonthsAgo() {
401 $unix6MAgo = $unixNow - (180 * 24 * 60 * 60);
403 'join_date' => date('Y-m-d', $unix6MAgo),
406 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualRolling']],
409 $obj = new CRM_Member_Form_Membership();
410 $rc = $obj::formRule($params, $files, $obj);
412 // Should have found Current membership status.
413 $this->assertTrue($rc);
417 * Test CRM_Member_Form_Membership::formRule() with a join date
418 * of one year+ ago and a rolling membership type
420 public function testFormRuleRollingJoin1YearAgo() {
422 $unix1YAgo = $unixNow - (370 * 24 * 60 * 60);
424 'join_date' => date('Y-m-d', $unix1YAgo),
427 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualRolling']],
430 $obj = new CRM_Member_Form_Membership();
431 $rc = $obj::formRule($params, $files, $obj);
433 // Should have found Grace membership status
434 $this->assertTrue($rc);
438 * Test CRM_Member_Form_Membership::formRule() with a join date
439 * of two years ago and a rolling membership type
441 public function testFormRuleRollingJoin2YearsAgo() {
443 $unix2YAgo = $unixNow - (2 * 365 * 24 * 60 * 60);
445 'join_date' => date('Y-m-d', $unix2YAgo),
448 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualRolling']],
451 $obj = new CRM_Member_Form_Membership();
452 $rc = $obj::formRule($params, $files, $obj);
454 // Should have found Expired membership status
455 $this->assertTrue($rc);
459 * Test CRM_Member_Form_Membership::formRule() with a current status.
461 * The setup is a join date of six months ago and a fixed membership type.
463 public function testFormRuleFixedJoin6MonthsAgo() {
465 $unix6MAgo = $unixNow - (180 * 24 * 60 * 60);
467 'join_date' => date('Y-m-d', $unix6MAgo),
470 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualFixed']],
473 $obj = new CRM_Member_Form_Membership();
474 $rc = $obj::formRule($params, $files, $obj);
476 // Should have found Current membership status
477 $this->assertTrue($rc);
481 * Test the submit function of the membership form.
483 * @param string $thousandSeparator
485 * @throws \CRM_Core_Exception
486 * @throws \CiviCRM_API3_Exception
488 * @dataProvider getThousandSeparators
490 public function testSubmit(string $thousandSeparator) {
491 CRM_Core_Session
::singleton()->getStatus(TRUE);
492 $this->setCurrencySeparators($thousandSeparator);
493 $form = $this->getForm();
495 $this->mut
= new CiviMailUtils($this, TRUE);
496 $form->_mode
= 'test';
497 $this->createLoggedInUser();
499 'cid' => $this->_individualId
,
500 'join_date' => date('Y-m-d'),
503 // This format reflects the organisation & then the type.
504 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualFixed']],
509 'total_amount' => $this->formatMoneyInput(1234.56),
510 //Member dues, see data.xml
511 'financial_type_id' => '2',
512 'soft_credit_type_id' => '',
513 'soft_credit_contact_id' => '',
514 'from_email_address' => '"Demonstrators Anonymous" <info@example.org>',
515 'payment_processor_id' => $this->_paymentProcessorID
,
516 'credit_card_number' => '4111111111111111',
518 'credit_card_exp_date' => [
520 'Y' => date('Y', strtotime('+ 2 years')),
522 'credit_card_type' => 'Visa',
523 'billing_first_name' => 'Test',
524 'billing_middlename' => 'Last',
525 'billing_street_address-5' => '10 Test St',
526 'billing_city-5' => 'Test',
527 'billing_state_province_id-5' => '1003',
528 'billing_postal_code-5' => '90210',
529 'billing_country_id-5' => '1228',
530 'send_receipt' => TRUE,
531 'receipt_text' => 'Receipt text',
533 $form->_contactID
= $this->_individualId
;
534 $form->testSubmit($params);
535 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
536 $this->callAPISuccessGetCount('ContributionRecur', ['contact_id' => $this->_individualId
], 0);
537 $contribution = $this->callAPISuccess('Contribution', 'get', [
538 'contact_id' => $this->_individualId
,
542 //CRM-20264 : Check that CC type and number (last 4 digit) is stored during backoffice membership payment
543 $lastFinancialTrxnId = CRM_Core_BAO_FinancialTrxn
::getFinancialTrxnId($contribution['id'], 'DESC');
544 $financialTrxn = $this->callAPISuccessGetSingle(
547 'id' => $lastFinancialTrxnId['financialTrxnId'],
548 'return' => ['card_type_id', 'pan_truncation'],
551 $this->assertEquals(1, $financialTrxn['card_type_id']);
552 $this->assertEquals(1111, $financialTrxn['pan_truncation']);
554 $this->callAPISuccessGetCount('LineItem', [
555 'entity_id' => $membership['id'],
556 'entity_table' => 'civicrm_membership',
557 'contribution_id' => $contribution['id'],
560 $this->_checkFinancialRecords([
561 'id' => $contribution['id'],
562 'total_amount' => 1234.56,
563 'financial_account_id' => 2,
564 'payment_instrument_id' => $this->callAPISuccessGetValue('PaymentProcessor', [
565 'id' => $this->_paymentProcessorID
,
566 'return' => 'payment_instrument_id',
569 $this->mut
->checkMailLog([
570 CRM_Utils_Money
::format('1234.56'),
574 $this->assertEquals([
576 'text' => 'AnnualFixed membership for Mr. Anthony Anderson II has been added. The new membership End Date is December 31st, ' . date('Y') . '. A membership confirmation and receipt has been sent to anthony_anderson@civicrm.org.',
577 'title' => 'Complete',
581 ], CRM_Core_Session
::singleton()->getStatus());
585 * Test the submit function of the membership form on membership type change.
586 * Check if the related contribuion is also updated if the minimum_fee didn't match
588 * @throws \CRM_Core_Exception
589 * @throws \CiviCRM_API3_Exception
591 public function testContributionUpdateOnMembershipTypeChange(): void
{
592 // Step 1: Create a Membership via backoffice whose with 50.00 payment
593 $form = $this->getForm();
595 $this->mut
= new CiviMailUtils($this, TRUE);
596 $this->createLoggedInUser();
597 $priceSet = $this->callAPISuccess('PriceSet', 'Get', ["extends" => "CiviMember"]);
598 $form->set('priceSetId', $priceSet['id']);
599 CRM_Price_BAO_PriceSet
::buildPriceSet($form);
601 'cid' => $this->_individualId
,
602 'join_date' => date('Y-m-d'),
605 // This format reflects the first being the organisation & the $this->ids['membership_type']['AnnualFixed'] being the type.
606 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualFixed']],
607 'record_contribution' => 1,
608 'total_amount' => 50,
609 'receive_date' => date('Y-m-d', time()) . ' 20:36:00',
610 'payment_instrument_id' => array_search('Check', $this->paymentInstruments
),
611 'contribution_status_id' => CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'),
612 //Member dues, see data.xml
613 'financial_type_id' => '2',
614 'payment_processor_id' => $this->_paymentProcessorID
,
616 $form->_contactID
= $this->_individualId
;
617 $form->testSubmit($params);
618 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
619 // check the membership status after partial payment, if its Pending
620 $this->assertEquals(array_search('New', CRM_Member_PseudoConstant
::membershipStatus()), $membership['status_id']);
621 $contribution = $this->callAPISuccessGetSingle('Contribution', [
622 'contact_id' => $this->_individualId
,
624 $this->assertEquals('Completed', $contribution['contribution_status']);
625 $this->assertEquals(50.00, $contribution['total_amount']);
626 $this->assertEquals(50.00, $contribution['net_amount']);
628 // Step 2: Change the membership type whose minimum free is less than earlier membership
629 $secondMembershipType = $this->callAPISuccess('membership_type', 'create', [
631 'name' => 'Second Test Membership',
632 'member_of_contact_id' => $this->ids
['contact']['organization'],
633 'duration_unit' => 'month',
635 'duration_interval' => 1,
636 'period_type' => 'fixed',
637 'fixed_period_start_day' => '101',
638 'fixed_period_rollover_day' => '1231',
639 'relationship_type_id' => 20,
640 'financial_type_id' => 2,
642 Civi
::settings()->set('update_contribution_on_membership_type_change', TRUE);
643 $form = $this->getForm();
645 $form->_id
= $membership['id'];
646 $form->set('priceSetId', $priceSet['id']);
647 CRM_Price_BAO_PriceSet
::buildPriceSet($form);
648 $form->_action
= CRM_Core_Action
::UPDATE
;
650 'cid' => $this->_individualId
,
651 'join_date' => date('Y-m-d'),
654 // This format reflects the first number being the organisation & the 25 being the type.
655 'membership_type_id' => [$this->ids
['contact']['organization'], $secondMembershipType['id']],
657 'receive_date' => date('Y-m-d', time()) . ' 20:36:00',
658 'payment_instrument_id' => array_search('Check', $this->paymentInstruments
),
659 //Member dues, see data.xml
660 'financial_type_id' => '2',
661 'payment_processor_id' => $this->_paymentProcessorID
,
663 $form->_contactID
= $this->_individualId
;
664 $form->testSubmit($params);
665 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
666 // check the membership status after partial payment, if its Pending
667 $contribution = $this->callAPISuccessGetSingle('Contribution', [
668 'contact_id' => $this->_individualId
,
670 $payment = CRM_Contribute_BAO_Contribution
::getPaymentInfo($membership['id'], 'membership', FALSE);
671 // Check the contribution status on membership type change whose minimum fee was less than earlier membership
672 $this->assertEquals('Pending refund', $contribution['contribution_status']);
673 // Earlier paid amount
674 $this->assertEquals(50, $payment['paid']);
676 $this->assertEquals(-25, $payment['balance']);
680 * Test the submit function of the membership form for partial payment.
682 * @param string $thousandSeparator
683 * punctuation used to refer to thousands.
685 * @throws \CRM_Core_Exception
686 * @throws \CiviCRM_API3_Exception
687 * @dataProvider getThousandSeparators
689 public function testSubmitPartialPayment(string $thousandSeparator): void
{
690 $this->setCurrencySeparators($thousandSeparator);
691 // Step 1: submit a partial payment for a membership via backoffice
692 $form = $this->getForm();
694 $this->mut
= new CiviMailUtils($this, TRUE);
695 $this->createLoggedInUser();
696 $priceSet = $this->callAPISuccess('PriceSet', 'Get', ["extends" => "CiviMember"]);
697 $form->set('priceSetId', $priceSet['id']);
699 CRM_Price_BAO_PriceSet
::buildPriceSet($form);
701 'cid' => $this->_individualId
,
702 'join_date' => date('Y-m-d'),
705 // This format reflects the first number being the organisation & the second being the type.
706 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualFixed']],
707 'receive_date' => date('Y-m-d', time()) . ' 20:36:00',
708 'record_contribution' => 1,
709 'total_amount' => $this->formatMoneyInput(50),
710 'payment_instrument_id' => array_search('Check', $this->paymentInstruments
, TRUE),
711 'contribution_status_id' => CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending'),
712 //Member dues, see data.xml
713 'financial_type_id' => '2',
714 'payment_processor_id' => $this->_paymentProcessorID
,
716 $form->_contactID
= $this->_individualId
;
717 $form->testSubmit($params);
718 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
719 // check the membership status after partial payment, if its Pending
720 $this->assertEquals(array_search('Pending', CRM_Member_PseudoConstant
::membershipStatus(), TRUE), $membership['status_id']);
721 $contribution = $this->callAPISuccessGetSingle('Contribution', ['contact_id' => $this->_individualId
]);
722 $this->callAPISuccess('Payment', 'create', ['contribution_id' => $contribution['id'], 'total_amount' => 25, 'payment_instrument_id' => 'Cash']);
723 $contribution = $this->callAPISuccessGetSingle('Contribution', ['id' => $contribution['id']]);
724 $this->assertEquals('Partially paid', $contribution['contribution_status']);
726 // Step 2: submit the other half of the partial payment
727 // via AdditionalPayment form to complete the related contribution
728 $form = new CRM_Contribute_Form_AdditionalPayment();
730 'contribution_id' => $contribution['contribution_id'],
731 'contact_id' => $this->_individualId
,
732 'total_amount' => $this->formatMoneyInput(25),
734 'financial_type_id' => 2,
735 'receive_date' => '2015-04-21 23:27:00',
736 'trxn_date' => '2017-04-11 13:05:11',
737 'payment_processor_id' => 0,
738 'payment_instrument_id' => array_search('Check', $this->paymentInstruments
, TRUE),
739 'check_number' => 'check-12345',
741 $form->cid
= $this->_individualId
;
742 $form->testSubmit($submitParams);
743 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
744 // check the membership status after additional payment, if its changed to 'New'
745 $this->assertEquals(array_search('New', CRM_Member_PseudoConstant
::membershipStatus(), TRUE), $membership['status_id']);
747 // check the contribution status and net amount after additional payment
748 $contribution = $this->callAPISuccessGetSingle('Contribution', [
749 'contact_id' => $this->_individualId
,
751 $this->assertEquals('Completed', $contribution['contribution_status']);
752 $this->validateAllPayments();
756 * Test the submit function of the membership form.
758 * @throws \API_Exception
759 * @throws \CRM_Core_Exception
760 * @throws \CiviCRM_API3_Exception
762 public function testSubmitRecur(): void
{
763 CRM_Core_Session
::singleton()->getStatus(TRUE);
764 $pendingVal = $this->callAPISuccessGetValue('OptionValue', [
766 'option_group_id' => 'contribution_status',
767 'label' => 'Pending Label**',
769 //Update label for Pending contribution status.
770 $this->callAPISuccess('OptionValue', 'create', [
772 'label' => 'PendingEdited',
775 $this->callAPISuccess('MembershipType', 'create', [
776 'id' => $this->ids
['membership_type']['AnnualFixed'],
777 'duration_unit' => 'month',
778 'duration_interval' => 1,
779 'auto_renew' => TRUE,
781 $params = $this->getBaseSubmitParams();
782 // Change financial_type_id to test our override flows through to the line item.
783 $params['financial_type_id'] = FinancialType
::get(FALSE)->addWhere('id', '!=', $params['financial_type_id'])->addSelect('id')->execute()->first()['id'];
784 $form = $this->getForm();
785 $this->createLoggedInUser();
786 $form->_mode
= 'test';
787 $form->_contactID
= $this->_individualId
;
788 $form->testSubmit($params);
789 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
790 $this->callAPISuccessGetCount('ContributionRecur', ['contact_id' => $this->_individualId
], 1);
792 $contribution = $this->callAPISuccess('Contribution', 'get', [
793 'contact_id' => $this->_individualId
,
797 //Check if Membership Payment is recorded.
798 $this->callAPISuccessGetCount('MembershipPayment', [
799 'membership_id' => $membership['id'],
800 'contribution_id' => $contribution['id'],
804 $this->callAPISuccessGetCount('LineItem', [
805 'entity_id' => $membership['id'],
806 'entity_table' => 'civicrm_membership',
807 'contribution_id' => $contribution['id'],
808 'financial_type_id' => $params['financial_type_id'],
810 $this->assertEquals([
812 'text' => 'AnnualFixed membership for Mr. Anthony Anderson II has been added. The new membership End Date is ' . date('F jS, Y', strtotime('last day of this month')) . '.',
813 'title' => 'Complete',
817 ], CRM_Core_Session
::singleton()->getStatus());
821 * Test submit recurring with two line items.
823 * @throws \CRM_Core_Exception
824 * @throws \CiviCRM_API3_Exception
826 public function testSubmitRecurTwoRows(): void
{
827 $pfvIDs = $this->createMembershipPriceSet();
828 $form = $this->getForm();
829 $form->_mode
= 'live';
831 'price_' . $this->getPriceFieldID() => $pfvIDs,
832 'price_set_id' => $this->getPriceSetID(),
833 'frequency_interval' => 1,
834 'frequency_unit' => 'month',
835 'membership_type_id' => NULL,
836 // Set financial type id to null to check it is retrieved from the price set.
837 'financial_type_id' => NULL,
839 $form->testSubmit(array_merge($this->getBaseSubmitParams(), $priceParams));
840 $memberships = $this->callAPISuccess('Membership', 'get')['values'];
841 $this->assertCount(2, $memberships);
842 $this->callAPISuccessGetSingle('Contribution', ['financial_type_id' => 1]);
843 $this->callAPISuccessGetCount('MembershipPayment', [], 2);
844 $lines = $this->callAPISuccess('LineItem', 'get', ['sequential' => 1])['values'];
845 $this->assertCount(2, $lines);
846 $this->assertEquals('civicrm_membership', $lines[0]['entity_table']);
847 $this->assertEquals('civicrm_membership', $lines[1]['entity_table']);
852 * CRM-20946: Test the financial entires especially the reversed amount,
853 * after related Contribution is cancelled
855 * @throws \CRM_Core_Exception
856 * @throws \CiviCRM_API3_Exception
858 public function testFinancialEntriesOnCancelledContribution(): void
{
859 // Create two memberships for individual $this->_individualId, via a price set in the back end.
860 $this->createTwoMembershipsViaPriceSetInBackEnd($this->_individualId
);
862 // cancel the related contribution via API
863 $contribution = $this->callAPISuccessGetSingle('Contribution', [
864 'contact_id' => $this->_individualId
,
865 'contribution_status_id' => 2,
867 $this->callAPISuccess('Contribution', 'create', [
868 'id' => $contribution['id'],
869 'contribution_status_id' => CRM_Core_PseudoConstant
::getKey('CRM_Contribute_DAO_Contribution', 'contribution_status_id', 'Cancelled'),
872 // fetch financial_trxn ID of the related contribution
873 $sql = "SELECT financial_trxn_id
874 FROM civicrm_entity_financial_trxn
875 WHERE entity_id = %1 AND entity_table = 'civicrm_contribution'
879 $financialTrxnID = CRM_Core_DAO
::singleValueQuery($sql, [1 => [$contribution['id'], 'Int']]);
881 // fetch entity_financial_trxn records and compare their cancelled records
882 $result = $this->callAPISuccess('EntityFinancialTrxn', 'Get', [
883 'financial_trxn_id' => $financialTrxnID,
884 'entity_table' => 'civicrm_financial_item',
886 // compare the reversed amounts of respective memberships after cancelling contribution
887 $cancelledMembershipAmounts = [
892 foreach ($result['values'] as $record) {
893 $this->assertEquals($cancelledMembershipAmounts[$count], $record['amount']);
899 * Test the submit function of the membership form.
901 * @throws \CRM_Core_Exception
903 public function testSubmitPayLaterWithBilling() {
904 $form = $this->getForm();
906 $this->createLoggedInUser();
908 'cid' => $this->_individualId
,
909 'join_date' => date('Y-m-d'),
912 // This format reflects the first number being the organisation & the second being the type.
913 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualFixed']],
918 'total_amount' => '50.00',
919 //Member dues, see data.xml
920 'financial_type_id' => '2',
921 'soft_credit_type_id' => '',
922 'soft_credit_contact_id' => '',
923 'payment_instrument_id' => 4,
924 'from_email_address' => '"Demonstrators Anonymous" <info@example.org>',
925 'receipt_text_signup' => 'Thank you text',
926 'payment_processor_id' => $this->_paymentProcessorID
,
927 'record_contribution' => TRUE,
929 'contribution_status_id' => 2,
930 'billing_first_name' => 'Test',
931 'billing_middlename' => 'Last',
932 'billing_street_address-5' => '10 Test St',
933 'billing_city-5' => 'Test',
934 'billing_state_province_id-5' => '1003',
935 'billing_postal_code-5' => '90210',
936 'billing_country_id-5' => '1228',
938 $form->_contactID
= $this->_individualId
;
940 $form->testSubmit($params);
941 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
942 $contribution = $this->callAPISuccessGetSingle('Contribution', [
943 'contact_id' => $this->_individualId
,
944 'contribution_status_id' => 2,
946 $this->assertEquals($contribution['trxn_id'], 777);
948 $this->callAPISuccessGetCount('LineItem', [
949 'entity_id' => $membership['id'],
950 'entity_table' => 'civicrm_membership',
951 'contribution_id' => $contribution['id'],
953 $this->callAPISuccessGetSingle('address', [
954 'contact_id' => $this->_individualId
,
955 'street_address' => '10 Test St',
956 'postal_code' => 90210,
961 * Test if membership is updated to New after contribution
962 * is updated from Partially paid to Completed.
964 * @throws \CRM_Core_Exception
965 * @throws \CiviCRM_API3_Exception
967 public function testSubmitUpdateMembershipFromPartiallyPaid() {
968 $memStatus = CRM_Member_BAO_Membership
::buildOptions('status_id', 'validate');
970 //Perform a pay later membership contribution.
971 $this->testSubmitPayLaterWithBilling();
972 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
973 $this->assertEquals($membership['status_id'], array_search('Pending', $memStatus));
974 $contribution = $this->callAPISuccessGetSingle('MembershipPayment', [
975 'membership_id' => $membership['id'],
977 $prevContribution = $this->callAPISuccessGetSingle('Contribution', ['id' => $contribution['id']]);
978 $this->callAPISuccess('Payment', 'create', [
979 'contribution_id' => $contribution['contribution_id'],
980 'payment_instrument_id' => 'Cash',
984 // Complete the contribution from offline form.
985 $form = new CRM_Contribute_Form_Contribution();
987 'id' => $contribution['contribution_id'],
988 'contribution_status_id' => CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'),
991 $fields = ['total_amount', 'net_amount', 'financial_type_id', 'receive_date', 'contact_id', 'payment_instrument_id'];
992 foreach ($fields as $val) {
993 $submitParams[$val] = $prevContribution[$val];
995 $form->testSubmit($submitParams, CRM_Core_Action
::UPDATE
);
997 //Check if Membership is updated to New.
998 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
999 $this->assertEquals($membership['status_id'], array_search('New', $memStatus));
1003 * Test the submit function of the membership form.
1005 * @throws \CiviCRM_API3_Exception
1006 * @throws \CRM_Core_Exception
1008 public function testSubmitRecurCompleteInstant() {
1009 $form = $this->getForm();
1010 $mut = new CiviMailUtils($this, TRUE);
1011 $processor = Civi\Payment\System
::singleton()->getById($this->_paymentProcessorID
);
1012 $processor->setDoDirectPaymentResult([
1013 'payment_status_id' => 1,
1014 'trxn_id' => 'kettles boil water',
1015 'fee_amount' => .14,
1017 $processorDetail = $processor->getPaymentProcessor();
1018 $this->callAPISuccess('MembershipType', 'create', [
1019 'id' => $this->ids
['membership_type']['AnnualFixed'],
1020 'duration_unit' => 'month',
1021 'duration_interval' => 1,
1022 'auto_renew' => TRUE,
1024 $form->preProcess();
1025 $this->createLoggedInUser();
1026 $params = $this->getBaseSubmitParams();
1027 $form->_mode
= 'test';
1028 $form->_contactID
= $this->_individualId
;
1029 $form->testSubmit($params);
1030 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
1031 $this->callAPISuccessGetCount('ContributionRecur', ['contact_id' => $this->_individualId
], 1);
1033 $contribution = $this->callAPISuccess('Contribution', 'getsingle', [
1034 'contact_id' => $this->_individualId
,
1038 $this->assertEquals(.14, $contribution['fee_amount']);
1039 $this->assertEquals('kettles boil water', $contribution['trxn_id']);
1040 $this->assertEquals($processorDetail['payment_instrument_id'], $contribution['payment_instrument_id']);
1042 $this->callAPISuccessGetCount('LineItem', [
1043 'entity_id' => $membership['id'],
1044 'entity_table' => 'civicrm_membership',
1045 'contribution_id' => $contribution['id'],
1047 $mut->checkMailLog([
1048 '===========================================================
1049 Billing Name and Address
1050 ===========================================================
1055 '===========================================================
1056 Membership Information
1057 ===========================================================
1058 Membership Type: AnnualFixed
1059 Membership Start Date: ',
1060 '===========================================================
1061 Credit Card Information
1062 ===========================================================
1072 * CRM-20955, CRM-20966:
1073 * Test creating two memberships with inheritance via price set in the back end,
1074 * checking that the correct primary & secondary memberships, contributions, line items
1075 * & membership_payment records are created.
1076 * Uses some data from tests/phpunit/CRM/Member/Form/dataset/data.xml .
1078 * @throws \CRM_Core_Exception
1079 * @throws \CiviCRM_API3_Exception
1081 public function testTwoInheritedMembershipsViaPriceSetInBackend(): void
{
1082 // Create an organization and give it a "Member of" relationship to $this->_individualId.
1083 $orgID = $this->organizationCreate();
1084 $relationship = $this->callAPISuccess('Relationship', 'create', [
1085 'contact_id_a' => $this->_individualId
,
1086 'contact_id_b' => $orgID,
1087 'relationship_type_id' => $this->ids
['relationship_type']['member'],
1091 // Create two memberships for the organization, via a price set in the back end.
1092 $this->createTwoMembershipsViaPriceSetInBackEnd($orgID);
1094 // Check the primary memberships on the organization.
1095 $orgMembershipResult = $this->callAPISuccess('membership', 'get', [
1096 'contact_id' => $orgID,
1098 $this->assertEquals(2, $orgMembershipResult['count'], '2 primary memberships should have been created on the organization.');
1099 $primaryMembershipIds = [];
1100 foreach ($orgMembershipResult['values'] as $membership) {
1101 $primaryMembershipIds[] = $membership['id'];
1102 $this->assertTrue(empty($membership['owner_membership_id']), 'Membership on the organization has owner_membership_id so is inherited.');
1105 // CRM-20955: check that correct inherited memberships were created for the individual,
1106 // for both of the primary memberships.
1107 $individualMembershipResult = $this->callAPISuccess('membership', 'get', [
1108 'contact_id' => $this->_individualId
,
1110 $this->assertEquals(2, $individualMembershipResult['count'], "2 inherited memberships should have been created on the individual.");
1111 foreach ($individualMembershipResult['values'] as $membership) {
1112 $this->assertNotEmpty($membership['owner_membership_id'], "Membership on the individual lacks owner_membership_id so is not inherited.");
1113 $this->assertNotContains($membership['id'], $primaryMembershipIds, "Inherited membership id should not be the id of a primary membership.");
1114 $this->assertContains($membership['owner_membership_id'], $primaryMembershipIds, "Inherited membership owner_membership_id should be the id of a primary membership.");
1117 // CRM-20966: check that the correct membership contribution, line items
1118 // & membership_payment records were created for the organization.
1119 $contributionResult = $this->callAPISuccess('contribution', 'get', [
1120 'contact_id' => $orgID,
1122 'api.line_item.get' => [],
1123 'api.membership_payment.get' => [],
1125 $this->assertEquals(1, $contributionResult['count'], "One contribution should have been created for the organization's memberships.");
1127 $this->assertEquals(2, $contributionResult['values'][0]['api.line_item.get']['count'], "2 line items should have been created for the organization's memberships.");
1128 foreach ($contributionResult['values'][0]['api.line_item.get']['values'] as $lineItem) {
1129 $this->assertEquals('civicrm_membership', $lineItem['entity_table'], "Membership line item's entity_table should be 'civicrm_membership'.");
1130 $this->assertContains($lineItem['entity_id'], $primaryMembershipIds, "Membership line item's entity_id should be the id of a primary membership.");
1133 $this->assertEquals(2, $contributionResult['values'][0]['api.membership_payment.get']['count'], "2 membership payment records should have been created for the organization's memberships.");
1134 foreach ($contributionResult['values'][0]['api.membership_payment.get']['values'] as $membershipPayment) {
1135 $this->assertEquals($contributionResult['values'][0]['id'], $membershipPayment['contribution_id'], "membership payment's contribution ID should be the ID of the organization's membership contribution.");
1136 $this->assertContains($membershipPayment['membership_id'], $primaryMembershipIds, "membership payment's membership ID should be the ID of a primary membership.");
1138 // Check for orphan line items.
1139 $this->callAPISuccessGetCount('LineItem', [], 2);
1141 // CRM-20966: check that deleting relationship used for inheritance does not delete contribution.
1142 $this->callAPISuccess('relationship', 'delete', [
1143 'id' => $relationship['id'],
1146 $contributionResultAfterRelationshipDelete = $this->callAPISuccess('contribution', 'get', [
1147 'id' => $contributionResult['values'][0]['id'],
1148 'contact_id' => $orgID,
1150 $this->assertEquals(1, $contributionResultAfterRelationshipDelete['count'], "Contribution has been wrongly deleted.");
1154 * dev/core/issues/860:
1155 * Test creating two memberships via price set in the back end with a discount,
1156 * checking that the line items have correct amounts.
1158 * @throws \CRM_Core_Exception
1159 * @throws \CiviCRM_API3_Exception
1161 public function testTwoMembershipsViaPriceSetInBackendWithDiscount(): void
{
1162 // Register buildAmount hook to apply discount.
1163 $this->hookClass
->setHook('civicrm_buildAmount', [$this, 'buildAmountMembershipDiscount']);
1164 $this->enableTaxAndInvoicing();
1165 $this->addTaxAccountToFinancialType(2);
1166 // Create two memberships for individual $this->_individualId, via a price set in the back end.
1167 $this->createTwoMembershipsViaPriceSetInBackEnd($this->_individualId
);
1168 $contribution = $this->callAPISuccessGetSingle('Contribution', [
1169 'contact_id' => $this->_individualId
,
1172 // Note: we can't check for the contribution total being discounted, because the total is set
1173 // when the contribution is created via $form->testSubmit(), but buildAmount isn't called
1174 // until testSubmit() runs. Fixing that might involve making testSubmit() more sophisticated,
1175 // or just hacking total_amount for this case.
1177 $lineItemResult = $this->callAPISuccess('LineItem', 'get', [
1178 'contribution_id' => $contribution['id'],
1180 $this->assertEquals(2, $lineItemResult['count']);
1181 $discountedItems = 0;
1182 foreach ($lineItemResult['values'] as $lineItem) {
1183 $this->assertEquals($lineItem['line_total'] * .1, $lineItem['tax_amount']);
1184 if (CRM_Utils_String
::startsWith($lineItem['label'], 'Long Haired Goat')) {
1185 $this->assertEquals(15.0, $lineItem['line_total']);
1186 $this->assertEquals('Long Haired Goat - one leg free!', $lineItem['label']);
1190 $this->assertEquals(1, $discountedItems);
1194 * Implements hook_civicrm_buildAmount() for testTwoMembershipsViaPriceSetInBackendWithDiscount().
1196 public function buildAmountMembershipDiscount($pageType, &$form, &$amount) {
1197 foreach ($amount as $id => $priceField) {
1198 if (is_array($priceField['options'])) {
1199 foreach ($priceField['options'] as $optionId => $option) {
1200 if ($option['membership_type_id'] == $this->ids
['membership_type']['AnnualRolling']) {
1201 // Long Haired Goat membership discount.
1202 $amount[$id]['options'][$optionId]['amount'] = $option['amount'] * 0.75;
1203 $amount[$id]['options'][$optionId]['label'] = $option['label'] . ' - one leg free!';
1211 * Get a membership form object.
1213 * We need to instantiate the form to run preprocess, which means we have to
1214 * trick it about the request method.
1216 * @return \CRM_Member_Form_Membership
1217 * @throws \CRM_Core_Exception
1218 * @throws \CiviCRM_API3_Exception
1220 protected function getForm() {
1221 if (isset($_REQUEST['cid'])) {
1222 unset($_REQUEST['cid']);
1224 $form = $this->getFormObject('CRM_Member_Form_Membership');
1225 $form->preProcess();
1232 protected function getBaseSubmitParams() {
1234 'cid' => $this->_individualId
,
1235 'price_set_id' => 0,
1236 'join_date' => date('Y-m-d'),
1239 'campaign_id' => '',
1240 // This format reflects the first number being the organisation & the second being the type.
1241 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualFixed']],
1242 'auto_renew' => '1',
1247 'total_amount' => '77.00',
1248 //Member dues, see data.xml
1249 'financial_type_id' => '2',
1250 'soft_credit_type_id' => 11,
1251 'soft_credit_contact_id' => '',
1252 'from_email_address' => '"Demonstrators Anonymous" <info@example.org>',
1253 'receipt_text' => 'Thank you text',
1254 'payment_processor_id' => $this->_paymentProcessorID
,
1255 'credit_card_number' => '4111111111111111',
1257 'credit_card_exp_date' => [
1259 'Y' => date('Y') +
1,
1261 'credit_card_type' => 'Visa',
1262 'billing_first_name' => 'Test',
1263 'billing_middlename' => 'Last',
1264 'billing_street_address-5' => '10 Test St',
1265 'billing_city-5' => 'Test',
1266 'billing_state_province_id-5' => '1003',
1267 'billing_postal_code-5' => '90210',
1268 'billing_country_id-5' => '1228',
1269 'send_receipt' => 1,
1276 * create two memberships for the same individual, via a price set in the back end.
1278 * @param int $contactId Id of contact on which the memberships will be created.
1280 * @throws \CRM_Core_Exception
1281 * @throws \CiviCRM_API3_Exception
1283 protected function createTwoMembershipsViaPriceSetInBackEnd($contactId): void
{
1284 $form = $this->getForm();
1285 $form->preProcess();
1286 $this->createLoggedInUser();
1287 $pfvIDs = $this->createMembershipPriceSet();
1289 // register for both of these memberships via backoffice membership form submission
1291 'cid' => $contactId,
1292 'join_date' => date('Y-m-d'),
1295 "price_" . $this->getPriceFieldID() => $pfvIDs,
1296 'price_set_id' => $this->getPriceSetID(),
1297 'membership_type_id' => [1 => 0],
1298 'auto_renew' => '0',
1299 'max_related' => '',
1302 'total_amount' => '30.00',
1303 //Member dues, see data.xml
1304 'financial_type_id' => '2',
1305 'soft_credit_type_id' => '',
1306 'soft_credit_contact_id' => '',
1307 'payment_instrument_id' => 4,
1308 'from_email_address' => '"Demonstrators Anonymous" <info@example.org>',
1309 'receipt_text_signup' => 'Thank you text',
1310 'payment_processor_id' => $this->_paymentProcessorID
,
1311 'record_contribution' => TRUE,
1313 'contribution_status_id' => CRM_Core_PseudoConstant
::getKey('CRM_Contribute_DAO_Contribution', 'contribution_status_id', 'Pending'),
1314 'billing_first_name' => 'Test',
1315 'billing_middlename' => 'Last',
1316 'billing_street_address-5' => '10 Test St',
1317 'billing_city-5' => 'Test',
1318 'billing_state_province_id-5' => '1003',
1319 'billing_postal_code-5' => '90210',
1320 'billing_country_id-5' => '1228',
1322 $form->testSubmit($params);
1326 * Test membership status overrides when contribution is cancelled.
1328 * @throws \CRM_Core_Exception
1329 * @throws \CiviCRM_API3_Exception
1331 public function testContributionFormStatusUpdate(): void
{
1333 $this->_contactID
= $this->createLoggedInUser();
1334 $this->createContributionAndMembershipOrder();
1337 'total_amount' => 50,
1338 'financial_type_id' => 2,
1339 'contact_id' => $this->_contactID
,
1340 'payment_instrument_id' => CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'payment_instrument_id', 'Check'),
1341 'contribution_status_id' => CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Cancelled'),
1344 //Update Contribution to Cancelled.
1345 $form = new CRM_Contribute_Form_Contribution();
1346 $form->_id
= $params['id'] = $this->ids
['Contribution'][0];
1347 $form->_mode
= NULL;
1348 $form->_contactID
= $this->_individualId
;
1349 $form->testSubmit($params, CRM_Core_Action
::UPDATE
);
1350 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_contactID
]);
1352 //Assert membership status overrides when the contribution cancelled.
1353 $this->assertEquals(TRUE, $membership['is_override']);
1354 $this->assertEquals($membership['status_id'], $this->callAPISuccessGetValue('MembershipStatus', [
1356 'name' => 'Cancelled',
1361 * CRM-21656: Test the submit function of the membership form if Sales Tax is enabled.
1362 * This test simulates what happens when one hits Edit on a Contribution that has both LineItems and Sales Tax components
1363 * Without making any Edits -> check that the LineItem data remain the same
1364 * In addition (a data-integrity check) -> check that the LineItem data add up to the data at the Contribution level
1366 * @throws \CRM_Core_Exception
1367 * @throws \CiviCRM_API3_Exception
1369 public function testLineItemAmountOnSalesTax() {
1370 $this->enableTaxAndInvoicing();
1371 $this->addTaxAccountToFinancialType(2);
1372 $form = $this->getForm();
1373 $form->preProcess();
1374 $this->mut
= new CiviMailUtils($this, TRUE);
1375 $this->createLoggedInUser();
1376 $priceSet = $this->callAPISuccess('PriceSet', 'Get', ['extends' => 'CiviMember']);
1377 $form->set('priceSetId', $priceSet['id']);
1378 // we are simulating the creation of a Price Set in Administer -> CiviContribute -> Manage Price Sets so set is_quick_config = 0
1379 $this->callAPISuccess('PriceSet', 'Create', ['id' => $priceSet['id'], 'is_quick_config' => 0]);
1380 // clean the price options static variable to repopulate the options, in order to fetch tax information
1381 \Civi
::$statics['CRM_Price_BAO_PriceField']['priceOptions'] = NULL;
1382 CRM_Price_BAO_PriceSet
::buildPriceSet($form);
1383 // rebuild the price set form variable to include the tax information against each price options
1384 $form->_priceSet
= current(CRM_Price_BAO_PriceSet
::getSetDetail($priceSet['id']));
1386 'cid' => $this->_individualId
,
1387 'join_date' => date('Y-m-d'),
1390 // This format reflects the first number being the organisation & the second being the type.
1391 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualFixed']],
1392 'record_contribution' => 1,
1393 'total_amount' => 55,
1394 'receive_date' => date('Y-m-d') . ' 20:36:00',
1395 'payment_instrument_id' => array_search('Check', $this->paymentInstruments
, TRUE),
1396 'contribution_status_id' => CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'),
1397 //Member dues, see data.xml
1398 'financial_type_id' => 2,
1399 'payment_processor_id' => $this->_paymentProcessorID
,
1401 $form->_contactID
= $this->_individualId
;
1402 $form->testSubmit($params);
1404 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
1405 $lineItem = $this->callAPISuccessGetSingle('LineItem', ['entity_id' => $membership['id'], 'entity_table' => 'civicrm_membership']);
1406 $this->assertEquals(1, $lineItem['qty']);
1407 $this->assertEquals(50.00, $lineItem['unit_price']);
1408 $this->assertEquals(50.00, $lineItem['line_total']);
1409 $this->assertEquals(5.00, $lineItem['tax_amount']);
1411 // Simply save the 'Edit Contribution' form
1412 $form = new CRM_Contribute_Form_Contribution();
1413 $form->_context
= 'membership';
1414 $form->_values
= $this->callAPISuccessGetSingle('Contribution', ['id' => $lineItem['contribution_id'], 'return' => ['total_amount', 'net_amount', 'fee_amount', 'tax_amount']]);
1416 'contact_id' => $this->_individualId
,
1417 'id' => $lineItem['contribution_id'],
1418 'financial_type_id' => 2,
1419 'contribution_status_id' => CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'),
1421 CRM_Core_Action
::UPDATE
);
1423 // ensure that the LineItem data remain the same
1424 $lineItem = $this->callAPISuccessGetSingle('LineItem', ['entity_id' => $membership['id'], 'entity_table' => 'civicrm_membership']);
1425 $this->assertEquals(1, $lineItem['qty']);
1426 $this->assertEquals(50.00, $lineItem['unit_price']);
1427 $this->assertEquals(50.00, $lineItem['line_total']);
1428 $this->assertEquals(5.00, $lineItem['tax_amount']);
1430 // ensure that the LineItem data add up to the data at the Contribution level
1431 $contribution = $this->callAPISuccessGetSingle('Contribution',
1433 'contribution_id' => 1,
1434 'return' => ['tax_amount', 'total_amount'],
1437 $this->assertEquals($contribution['total_amount'], $lineItem['line_total'] +
$lineItem['tax_amount']);
1438 $this->assertEquals($contribution['tax_amount'], $lineItem['tax_amount']);
1440 $financialItems = $this->callAPISuccess('FinancialItem', 'get', []);
1441 $financialItems_sum = 0;
1442 foreach ($financialItems['values'] as $financialItem) {
1443 $financialItems_sum +
= $financialItem['amount'];
1445 $this->assertEquals($contribution['total_amount'], $financialItems_sum);
1449 * Test that membership end_date is correct for multiple terms for pending contribution
1451 * @throws CiviCRM_API3_Exception
1452 * @throws \CRM_Core_Exception
1453 * @throws \Exception
1455 public function testCreatePendingWithMultipleTerms() {
1456 CRM_Core_Session
::singleton()->getStatus(TRUE);
1457 $this->mut
= new CiviMailUtils($this, TRUE);
1458 $this->createLoggedInUser();
1459 $membershipTypeAnnualRolling = $this->callAPISuccess('membership_type', 'create', [
1461 'name' => 'AnnualRollingNew',
1462 'member_of_contact_id' => $this->ids
['contact']['organization'],
1463 'duration_unit' => 'year',
1464 'minimum_fee' => 50,
1465 'duration_interval' => 1,
1466 'period_type' => 'rolling',
1467 'relationship_type_id' => 20,
1468 'relationship_direction' => 'b_a',
1469 'financial_type_id' => 2,
1472 'cid' => $this->_individualId
,
1473 'join_date' => date('Y-m-d'),
1476 'contribution_status_id' => CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending'),
1477 'membership_type_id' => [$this->ids
['contact']['organization'], $membershipTypeAnnualRolling['id']],
1478 'max_related' => '',
1480 'record_contribution' => 1,
1482 'total_amount' => $this->formatMoneyInput(150.00),
1483 'financial_type_id' => '2',
1484 'soft_credit_type_id' => '11',
1485 'soft_credit_contact_id' => '',
1486 'from_email_address' => '"Demonstrators Anonymous" <info@example.org>',
1487 'receipt_text' => '',
1489 $form = $this->getForm();
1490 $form->preProcess();
1491 $form->_contactID
= $this->_individualId
;
1492 $form->testSubmit($params);
1493 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
1494 $contribution = $this->callAPISuccess('Contribution', 'get', [
1495 'contact_id' => $this->_individualId
,
1497 $endDate = (new DateTime(date('Y-m-d')))->modify('+3 years')->modify('-1 day');
1498 $endDate = $endDate->format("Y-m-d");
1500 $this->assertEquals($endDate, $membership['end_date'], 'Membership end date should be ' . $endDate);
1501 $this->assertEquals(1, count($contribution['values']), 'Pending contribution should be created.');
1502 $contribution = $contribution['values'][$contribution['id']];
1503 $additionalPaymentForm = new CRM_Contribute_Form_AdditionalPayment();
1504 $additionalPaymentForm->testSubmit([
1505 'total_amount' => 150.00,
1506 'trxn_date' => date("Y-m-d H:i:s"),
1507 'payment_instrument_id' => array_search('Check', $this->paymentInstruments
),
1508 'check_number' => 'check-12345',
1510 'currency' => 'USD',
1512 'financial_type_id' => 1,
1514 'payment_processor_id' => 0,
1515 'contact_id' => $this->_individualId
,
1516 'contribution_id' => $contribution['id'],
1518 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
1519 $contribution = $this->callAPISuccess('Contribution', 'get', [
1520 'contact_id' => $this->_individualId
,
1521 'contribution_status_id' => CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'),
1523 $this->assertEquals($endDate, $membership['end_date'], 'Membership end date should be same (' . $endDate . ') after payment');
1524 $this->assertCount(1, $contribution['values'], 'Completed contribution should be fetched.');
1528 * Test Membership Payment owned by other contact, membership view should show all contribution records in listing.
1531 * @throws \CRM_Core_Exception
1532 * @throws \CiviCRM_API3_Exception
1533 * @throws \Exception
1535 public function testMembershipViewContributionOwnerDifferent() {
1537 $contactId1 = $this->individualCreate();
1539 // Contribution Onwer
1540 $contactId2 = $this->individualCreate();
1542 // create new membership type
1543 $membershipTypeAnnualFixed = $this->callAPISuccess('MembershipType', 'create', [
1545 'name' => 'AnnualFixed 2',
1546 'member_of_contact_id' => $this->organizationCreate(),
1547 'duration_unit' => 'year',
1548 'minimum_fee' => 50,
1549 'duration_interval' => 1,
1550 'period_type' => 'fixed',
1551 'fixed_period_start_day' => '101',
1552 'fixed_period_rollover_day' => '1231',
1553 'financial_type_id' => 2,
1556 // create Membership
1557 $membershipId = $this->contactMembershipCreate([
1558 'contact_id' => $contactId1,
1559 'membership_type_id' => $membershipTypeAnnualFixed['id'],
1560 'status_id' => 'New',
1565 'membership_id' => $membershipId,
1566 'total_amount' => 25,
1567 'financial_type_id' => 2,
1568 'contact_id' => $contactId2,
1569 'receive_date' => '2020-08-08',
1571 $contribution1 = CRM_Member_BAO_Membership
::recordMembershipContribution($contriParams);
1575 'membership_id' => $membershipId,
1576 'total_amount' => 25,
1577 'financial_type_id' => 2,
1578 'contact_id' => $contactId2,
1579 'receive_date' => '2020-07-08',
1581 $contribution2 = CRM_Member_BAO_Membership
::recordMembershipContribution($contriParams);
1583 // View Membership record
1584 $membershipViewForm = new CRM_Member_Form_MembershipView();
1585 $membershipViewForm->controller
= new CRM_Core_Controller_Simple('CRM_Member_Form_MembershipView', 'View Membership');
1586 $membershipViewForm->set('id', $membershipId);
1587 $membershipViewForm->set('context', 'membership');
1588 $membershipViewForm->controller
->setEmbedded(TRUE);
1589 $membershipViewForm->preProcess();
1591 // get contribution rows related to membership payments
1592 $templateVar = $membershipViewForm::getTemplate()->get_template_vars('rows');
1594 $this->assertEquals($templateVar[0]['contribution_id'], $contribution1->id
);
1595 $this->assertEquals($templateVar[0]['contact_id'], $contactId2);
1597 $this->assertEquals($templateVar[1]['contribution_id'], $contribution2->id
);
1598 $this->assertEquals($templateVar[1]['contact_id'], $contactId2);
1599 $this->assertEquals(count($templateVar), 2);