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
21 * Test CRM_Member_Form_Membership functions.
26 class CRM_Member_Form_MembershipTest
extends CiviUnitTestCase
{
28 use CRMTraits_Financial_OrderTrait
;
29 use CRMTraits_Financial_PriceSetTrait
;
34 protected $_individualId;
35 protected $_contribution;
36 protected $_financialTypeId = 1;
37 protected $_entity = 'Membership';
40 protected $_paymentProcessorID;
43 * Membership type ID for annual fixed membership.
47 protected $membershipTypeAnnualFixedID;
50 * Parameters to create payment processor.
54 protected $_processorParams = [];
57 * ID of created membership.
61 protected $_membershipID;
64 * Payment instrument mapping.
68 protected $paymentInstruments = [];
76 * Test setup for every test.
78 * Connect to the database, truncate the tables that will be used
79 * and redirect stdin to a temporary file.
81 * @throws \CRM_Core_Exception
82 * @throws \CiviCRM_API3_Exception
84 public function setUp() {
85 $this->_apiversion
= 3;
88 $this->_individualId
= $this->individualCreate();
89 $this->_paymentProcessorID
= $this->processorCreate();
91 $this->ids
['contact']['organization'] = $this->organizationCreate();
92 $this->ids
['contact']['organization2'] = $this->organizationCreate();
93 $this->ids
['relationship_type']['member'] = $this->callAPISuccess('RelationshipType', 'create', [
94 'name_a_b' => 'Member of',
95 'label_a_b' => 'Member of',
96 'name_b_a' => 'Member is',
97 'label_b_a' => 'Member is',
98 'contact_type_a' => 'Individual',
99 'contact_type_b' => 'Organization',
101 $this->ids
['membership_type']['AnnualFixed'] = $this->callAPISuccess('MembershipType', 'create', [
103 'name' => 'AnnualFixed',
104 'member_of_contact_id' => $this->ids
['contact']['organization'],
105 'duration_unit' => 'year',
107 'duration_interval' => 1,
108 'period_type' => 'fixed',
109 'fixed_period_start_day' => '101',
110 'fixed_period_rollover_day' => '1231',
111 'relationship_type_id' => [$this->ids
['relationship_type']['member']],
112 'relationship_direction' => ['b_a'],
113 'financial_type_id' => 2,
116 $this->ids
['membership_type']['AnnualRolling'] = $this->callAPISuccess('MembershipType', 'create', [
117 'name' => 'AnnualRolling',
118 'member_of_contact_id' => $this->ids
['contact']['organization'],
119 'duration_unit' => 'year',
120 'duration_interval' => 1,
121 'period_type' => 'rolling',
122 'relationship_type_id' => [$this->ids
['relationship_type']['member']],
123 'relationship_direction' => ['b_a'],
124 'financial_type_id' => 'Member Dues',
127 $this->ids
['membership_type']['AnnualRollingOrg2'] = $this->callAPISuccess('MembershipType', 'create', [
128 'name' => 'AnnualRolling1',
129 'member_of_contact_id' => $this->ids
['contact']['organization2'],
130 'duration_unit' => 'year',
131 'duration_interval' => 1,
132 'period_type' => 'rolling',
133 'relationship_type_id' => [$this->ids
['relationship_type']['member']],
134 'relationship_direction' => ['b_a'],
135 'financial_type_id' => 'Member Dues',
138 $this->ids
['membership_type']['lifetime'] = $this->callAPISuccess('MembershipType', 'create', [
139 'name' => 'Lifetime',
140 'member_of_contact_id' => $this->ids
['contact']['organization'],
141 'duration_unit' => 'lifetime',
142 'duration_interval' => 1,
143 'relationship_type_id' => $this->ids
['relationship_type']['member'],
144 'relationship_direction' => 'b_a',
145 'financial_type_id' => 'Member Dues',
146 'period_type' => 'rolling',
149 $instruments = $this->callAPISuccess('Contribution', 'getoptions', ['field' => 'payment_instrument_id']);
150 $this->paymentInstruments
= $instruments['values'];
154 * Clean up after each test.
156 * @throws \CRM_Core_Exception
158 public function tearDown() {
159 $this->quickCleanUpFinancialEntities();
162 'civicrm_relationship',
163 'civicrm_membership_type',
164 'civicrm_membership',
169 $this->callAPISuccess('Contact', 'delete', ['id' => $this->ids
['contact']['organization'], 'skip_undelete' => TRUE]);
170 $this->callAPISuccess('RelationshipType', 'delete', ['id' => $this->ids
['relationship_type']['member']]);
174 * Test CRM_Member_Form_Membership::formRule() with a parameter
175 * that has an empty contact_select_id value
177 * @throws \CiviCRM_API3_Exception
178 * @throws \CRM_Core_Exception
180 public function testFormRuleEmptyContact(): void
{
182 'contact_select_id' => 0,
183 'membership_type_id' => [1 => NULL],
186 $obj = new CRM_Member_Form_Membership();
187 $rc = CRM_Member_Form_Membership
::formRule($params, $files, $obj);
188 $this->assertType('array', $rc);
189 $this->assertTrue(array_key_exists('membership_type_id', $rc));
191 $params['membership_type_id'] = [1 => 3];
192 $rc = CRM_Member_Form_Membership
::formRule($params, $files, $obj);
193 $this->assertType('array', $rc);
194 $this->assertTrue(array_key_exists('join_date', $rc));
198 * Test that form rule fails if start date is before join date.
200 * Test CRM_Member_Form_Membership::formRule() with a parameter
201 * that has an start date before the join date and a rolling
204 public function testFormRuleRollingEarlyStart() {
206 $unixYesterday = $unixNow - (24 * 60 * 60);
207 $ymdYesterday = date('Y-m-d', $unixYesterday);
209 'join_date' => date('Y-m-d'),
210 'start_date' => $ymdYesterday,
212 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualRolling']],
215 $obj = new CRM_Member_Form_Membership();
216 $rc = CRM_Member_Form_Membership
::formRule($params, $files, $obj);
217 $this->assertType('array', $rc);
218 $this->assertTrue(array_key_exists('start_date', $rc));
222 * Test CRM_Member_Form_Membership::formRule() with a parameter
223 * that has an end date before the start date and a rolling
226 public function testFormRuleRollingEarlyEnd() {
228 $unixYesterday = $unixNow - (24 * 60 * 60);
229 $ymdYesterday = date('Y-m-d', $unixYesterday);
231 'join_date' => date('Y-m-d'),
232 'start_date' => date('Y-m-d'),
233 'end_date' => $ymdYesterday,
234 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualRolling']],
237 $obj = new CRM_Member_Form_Membership();
238 $rc = CRM_Member_Form_Membership
::formRule($params, $files, $obj);
239 $this->assertType('array', $rc);
240 $this->assertTrue(array_key_exists('end_date', $rc));
244 * Test CRM_Member_Form_Membership::formRule() with end date but no start date and a rolling membership type.
246 public function testFormRuleRollingEndNoStart() {
248 $unixYearFromNow = $unixNow +
(365 * 24 * 60 * 60);
249 $ymdYearFromNow = date('Y-m-d', $unixYearFromNow);
251 'join_date' => date('Y-m-d'),
253 'end_date' => $ymdYearFromNow,
254 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualRolling']],
257 $obj = new CRM_Member_Form_Membership();
258 $rc = $obj::formRule($params, $files, $obj);
259 $this->assertType('array', $rc);
260 $this->assertTrue(array_key_exists('start_date', $rc));
264 * Test CRM_Member_Form_Membership::formRule() with a parameter
265 * that has an end date and a lifetime membership type
267 public function testFormRuleRollingLifetimeEnd() {
269 $unixYearFromNow = $unixNow +
(365 * 24 * 60 * 60);
271 'join_date' => date('Y-m-d'),
272 'start_date' => date('Y-m-d'),
273 'end_date' => date('Y-m-d',
276 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['lifetime']],
279 $obj = new CRM_Member_Form_Membership();
280 $rc = $obj::formRule($params, $files, $obj);
281 $this->assertType('array', $rc);
282 $this->assertTrue(array_key_exists('status_id', $rc));
286 * Test CRM_Member_Form_Membership::formRule() with a parameter
287 * that has permanent override and no status
289 * @throws \CiviCRM_API3_Exception
290 * @throws \CRM_Core_Exception
292 public function testFormRulePermanentOverrideWithNoStatus() {
294 'join_date' => date('Y-m-d'),
295 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualFixed']],
296 'is_override' => TRUE,
299 $obj = new CRM_Member_Form_Membership();
300 $rc = $obj::formRule($params, $files, $obj);
301 $this->assertType('array', $rc);
302 $this->assertTrue(array_key_exists('status_id', $rc));
305 public function testFormRuleUntilDateOverrideWithValidOverrideEndDate() {
307 'join_date' => date('Y-m-d'),
308 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualFixed']],
309 'is_override' => TRUE,
311 'status_override_end_date' => date('Y-m-d'),
314 $membershipForm = new CRM_Member_Form_Membership();
315 $validationResponse = CRM_Member_Form_Membership
::formRule($params, $files, $membershipForm);
316 $this->assertTrue($validationResponse);
319 public function testFormRuleUntilDateOverrideWithNoOverrideEndDate() {
321 'join_date' => date('Y-m-d'),
322 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualFixed']],
323 'is_override' => CRM_Member_StatusOverrideTypes
::UNTIL_DATE
,
327 $membershipForm = new CRM_Member_Form_Membership();
328 $validationResponse = CRM_Member_Form_Membership
::formRule($params, $files, $membershipForm);
329 $this->assertType('array', $validationResponse);
330 $this->assertEquals('Please enter the Membership override end date.', $validationResponse['status_override_end_date']);
334 * Test CRM_Member_Form_Membership::formRule() with a join date
335 * of one month from now and a rolling membership type
337 public function testFormRuleRollingJoin1MonthFromNow() {
339 $unix1MFmNow = $unixNow +
(31 * 24 * 60 * 60);
341 'join_date' => date('Y-m-d', $unix1MFmNow),
344 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualRolling']],
347 $obj = new CRM_Member_Form_Membership();
348 $rc = $obj::formRule($params, $files, $obj);
350 // Should have found no valid membership status.
351 $this->assertType('array', $rc);
352 $this->assertTrue(array_key_exists('_qf_default', $rc));
356 * Test CRM_Member_Form_Membership::formRule() with a join date of today and a rolling membership type.
358 public function testFormRuleRollingJoinToday() {
360 'join_date' => date('Y-m-d'),
363 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualRolling']],
366 $obj = new CRM_Member_Form_Membership();
367 $rc = $obj::formRule($params, $files, $obj);
369 // Should have found New membership status
370 $this->assertTrue($rc);
374 * Test CRM_Member_Form_Membership::formRule() with a join date
375 * of one month ago and a rolling membership type
377 public function testFormRuleRollingJoin1MonthAgo() {
379 $unix1MAgo = $unixNow - (31 * 24 * 60 * 60);
381 'join_date' => date('Y-m-d', $unix1MAgo),
384 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualRolling']],
387 $obj = new CRM_Member_Form_Membership();
388 $rc = $obj::formRule($params, $files, $obj);
390 // Should have found New membership status.
391 $this->assertTrue($rc);
395 * Test CRM_Member_Form_Membership::formRule() with a join date of six months ago and a rolling membership type.
397 public function testFormRuleRollingJoin6MonthsAgo() {
399 $unix6MAgo = $unixNow - (180 * 24 * 60 * 60);
401 'join_date' => date('Y-m-d', $unix6MAgo),
404 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualRolling']],
407 $obj = new CRM_Member_Form_Membership();
408 $rc = $obj::formRule($params, $files, $obj);
410 // Should have found Current membership status.
411 $this->assertTrue($rc);
415 * Test CRM_Member_Form_Membership::formRule() with a join date
416 * of one year+ ago and a rolling membership type
418 public function testFormRuleRollingJoin1YearAgo() {
420 $unix1YAgo = $unixNow - (370 * 24 * 60 * 60);
422 'join_date' => date('Y-m-d', $unix1YAgo),
425 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualRolling']],
428 $obj = new CRM_Member_Form_Membership();
429 $rc = $obj::formRule($params, $files, $obj);
431 // Should have found Grace membership status
432 $this->assertTrue($rc);
436 * Test CRM_Member_Form_Membership::formRule() with a join date
437 * of two years ago and a rolling membership type
439 public function testFormRuleRollingJoin2YearsAgo() {
441 $unix2YAgo = $unixNow - (2 * 365 * 24 * 60 * 60);
443 'join_date' => date('Y-m-d', $unix2YAgo),
446 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualRolling']],
449 $obj = new CRM_Member_Form_Membership();
450 $rc = $obj::formRule($params, $files, $obj);
452 // Should have found Expired membership status
453 $this->assertTrue($rc);
457 * Test CRM_Member_Form_Membership::formRule() with a current status.
459 * The setup is a join date of six months ago and a fixed membership type.
461 public function testFormRuleFixedJoin6MonthsAgo() {
463 $unix6MAgo = $unixNow - (180 * 24 * 60 * 60);
465 'join_date' => date('Y-m-d', $unix6MAgo),
468 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualFixed']],
471 $obj = new CRM_Member_Form_Membership();
472 $rc = $obj::formRule($params, $files, $obj);
474 // Should have found Current membership status
475 $this->assertTrue($rc);
479 * Test the submit function of the membership form.
481 * @param string $thousandSeparator
483 * @throws \CRM_Core_Exception
484 * @throws \CiviCRM_API3_Exception
486 * @dataProvider getThousandSeparators
488 public function testSubmit(string $thousandSeparator) {
489 CRM_Core_Session
::singleton()->getStatus(TRUE);
490 $this->setCurrencySeparators($thousandSeparator);
491 $form = $this->getForm();
493 $this->mut
= new CiviMailUtils($this, TRUE);
494 $form->_mode
= 'test';
495 $this->createLoggedInUser();
497 'cid' => $this->_individualId
,
498 'join_date' => date('Y-m-d'),
501 // This format reflects the organisation & then the type.
502 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualFixed']],
507 'total_amount' => $this->formatMoneyInput(1234.56),
508 //Member dues, see data.xml
509 'financial_type_id' => '2',
510 'soft_credit_type_id' => '',
511 'soft_credit_contact_id' => '',
512 'from_email_address' => '"Demonstrators Anonymous" <info@example.org>',
513 'payment_processor_id' => $this->_paymentProcessorID
,
514 'credit_card_number' => '4111111111111111',
516 'credit_card_exp_date' => [
518 'Y' => date('Y', strtotime('+ 2 years')),
520 'credit_card_type' => 'Visa',
521 'billing_first_name' => 'Test',
522 'billing_middlename' => 'Last',
523 'billing_street_address-5' => '10 Test St',
524 'billing_city-5' => 'Test',
525 'billing_state_province_id-5' => '1003',
526 'billing_postal_code-5' => '90210',
527 'billing_country_id-5' => '1228',
528 'send_receipt' => TRUE,
529 'receipt_text' => 'Receipt text',
531 $form->_contactID
= $this->_individualId
;
532 $form->testSubmit($params);
533 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
534 $this->callAPISuccessGetCount('ContributionRecur', ['contact_id' => $this->_individualId
], 0);
535 $contribution = $this->callAPISuccess('Contribution', 'get', [
536 'contact_id' => $this->_individualId
,
540 //CRM-20264 : Check that CC type and number (last 4 digit) is stored during backoffice membership payment
541 $lastFinancialTrxnId = CRM_Core_BAO_FinancialTrxn
::getFinancialTrxnId($contribution['id'], 'DESC');
542 $financialTrxn = $this->callAPISuccessGetSingle(
545 'id' => $lastFinancialTrxnId['financialTrxnId'],
546 'return' => ['card_type_id', 'pan_truncation'],
549 $this->assertEquals(1, $financialTrxn['card_type_id']);
550 $this->assertEquals(1111, $financialTrxn['pan_truncation']);
552 $this->callAPISuccessGetCount('LineItem', [
553 'entity_id' => $membership['id'],
554 'entity_table' => 'civicrm_membership',
555 'contribution_id' => $contribution['id'],
558 $this->_checkFinancialRecords([
559 'id' => $contribution['id'],
560 'total_amount' => 1234.56,
561 'financial_account_id' => 2,
562 'payment_instrument_id' => $this->callAPISuccessGetValue('PaymentProcessor', [
563 'id' => $this->_paymentProcessorID
,
564 'return' => 'payment_instrument_id',
567 $this->mut
->checkMailLog([
568 CRM_Utils_Money
::format('1234.56'),
572 $this->assertEquals([
574 '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.',
575 'title' => 'Complete',
579 ], CRM_Core_Session
::singleton()->getStatus());
583 * Test the submit function of the membership form on membership type change.
584 * Check if the related contribuion is also updated if the minimum_fee didn't match
586 * @throws \CRM_Core_Exception
587 * @throws \CiviCRM_API3_Exception
589 public function testContributionUpdateOnMembershipTypeChange(): void
{
590 // Step 1: Create a Membership via backoffice whose with 50.00 payment
591 $form = $this->getForm();
593 $this->mut
= new CiviMailUtils($this, TRUE);
594 $this->createLoggedInUser();
595 $priceSet = $this->callAPISuccess('PriceSet', 'Get', ["extends" => "CiviMember"]);
596 $form->set('priceSetId', $priceSet['id']);
597 CRM_Price_BAO_PriceSet
::buildPriceSet($form);
599 'cid' => $this->_individualId
,
600 'join_date' => date('Y-m-d'),
603 // This format reflects the first being the organisation & the $this->ids['membership_type']['AnnualFixed'] being the type.
604 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualFixed']],
605 'record_contribution' => 1,
606 'total_amount' => 50,
607 'receive_date' => date('Y-m-d', time()) . ' 20:36:00',
608 'payment_instrument_id' => array_search('Check', $this->paymentInstruments
),
609 'contribution_status_id' => CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'),
610 //Member dues, see data.xml
611 'financial_type_id' => '2',
612 'payment_processor_id' => $this->_paymentProcessorID
,
614 $form->_contactID
= $this->_individualId
;
615 $form->testSubmit($params);
616 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
617 // check the membership status after partial payment, if its Pending
618 $this->assertEquals(array_search('New', CRM_Member_PseudoConstant
::membershipStatus()), $membership['status_id']);
619 $contribution = $this->callAPISuccessGetSingle('Contribution', [
620 'contact_id' => $this->_individualId
,
622 $this->assertEquals('Completed', $contribution['contribution_status']);
623 $this->assertEquals(50.00, $contribution['total_amount']);
624 $this->assertEquals(50.00, $contribution['net_amount']);
626 // Step 2: Change the membership type whose minimum free is less than earlier membership
627 $secondMembershipType = $this->callAPISuccess('membership_type', 'create', [
629 'name' => 'Second Test Membership',
630 'member_of_contact_id' => $this->ids
['contact']['organization'],
631 'duration_unit' => 'month',
633 'duration_interval' => 1,
634 'period_type' => 'fixed',
635 'fixed_period_start_day' => '101',
636 'fixed_period_rollover_day' => '1231',
637 'relationship_type_id' => 20,
638 'financial_type_id' => 2,
640 Civi
::settings()->set('update_contribution_on_membership_type_change', TRUE);
641 $form = $this->getForm();
643 $form->_id
= $membership['id'];
644 $form->set('priceSetId', $priceSet['id']);
645 CRM_Price_BAO_PriceSet
::buildPriceSet($form);
646 $form->_action
= CRM_Core_Action
::UPDATE
;
648 'cid' => $this->_individualId
,
649 'join_date' => date('Y-m-d'),
652 // This format reflects the first number being the organisation & the 25 being the type.
653 'membership_type_id' => [$this->ids
['contact']['organization'], $secondMembershipType['id']],
655 'receive_date' => date('Y-m-d', time()) . ' 20:36:00',
656 'payment_instrument_id' => array_search('Check', $this->paymentInstruments
),
657 //Member dues, see data.xml
658 'financial_type_id' => '2',
659 'payment_processor_id' => $this->_paymentProcessorID
,
661 $form->_contactID
= $this->_individualId
;
662 $form->testSubmit($params);
663 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
664 // check the membership status after partial payment, if its Pending
665 $contribution = $this->callAPISuccessGetSingle('Contribution', [
666 'contact_id' => $this->_individualId
,
668 $payment = CRM_Contribute_BAO_Contribution
::getPaymentInfo($membership['id'], 'membership', FALSE);
669 // Check the contribution status on membership type change whose minimum fee was less than earlier membership
670 $this->assertEquals('Pending refund', $contribution['contribution_status']);
671 // Earlier paid amount
672 $this->assertEquals(50, $payment['paid']);
674 $this->assertEquals(-25, $payment['balance']);
678 * Test the submit function of the membership form for partial payment.
680 * @param string $thousandSeparator
681 * punctuation used to refer to thousands.
683 * @throws \CRM_Core_Exception
684 * @throws \CiviCRM_API3_Exception
685 * @dataProvider getThousandSeparators
687 public function testSubmitPartialPayment(string $thousandSeparator): void
{
688 $this->setCurrencySeparators($thousandSeparator);
689 // Step 1: submit a partial payment for a membership via backoffice
690 $form = $this->getForm();
692 $this->mut
= new CiviMailUtils($this, TRUE);
693 $this->createLoggedInUser();
694 $priceSet = $this->callAPISuccess('PriceSet', 'Get', ["extends" => "CiviMember"]);
695 $form->set('priceSetId', $priceSet['id']);
697 CRM_Price_BAO_PriceSet
::buildPriceSet($form);
699 'cid' => $this->_individualId
,
700 'join_date' => date('Y-m-d'),
703 // This format reflects the first number being the organisation & the second being the type.
704 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualFixed']],
705 'receive_date' => date('Y-m-d', time()) . ' 20:36:00',
706 'record_contribution' => 1,
707 'total_amount' => $this->formatMoneyInput(50),
708 'payment_instrument_id' => array_search('Check', $this->paymentInstruments
, TRUE),
709 'contribution_status_id' => CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending'),
710 //Member dues, see data.xml
711 'financial_type_id' => '2',
712 'payment_processor_id' => $this->_paymentProcessorID
,
714 $form->_contactID
= $this->_individualId
;
715 $form->testSubmit($params);
716 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
717 // check the membership status after partial payment, if its Pending
718 $this->assertEquals(array_search('Pending', CRM_Member_PseudoConstant
::membershipStatus(), TRUE), $membership['status_id']);
719 $contribution = $this->callAPISuccessGetSingle('Contribution', ['contact_id' => $this->_individualId
]);
720 $this->callAPISuccess('Payment', 'create', ['contribution_id' => $contribution['id'], 'total_amount' => 25, 'payment_instrument_id' => 'Cash']);
721 $contribution = $this->callAPISuccessGetSingle('Contribution', ['id' => $contribution['id']]);
722 $this->assertEquals('Partially paid', $contribution['contribution_status']);
724 // Step 2: submit the other half of the partial payment
725 // via AdditionalPayment form to complete the related contribution
726 $form = new CRM_Contribute_Form_AdditionalPayment();
728 'contribution_id' => $contribution['contribution_id'],
729 'contact_id' => $this->_individualId
,
730 'total_amount' => $this->formatMoneyInput(25),
732 'financial_type_id' => 2,
733 'receive_date' => '2015-04-21 23:27:00',
734 'trxn_date' => '2017-04-11 13:05:11',
735 'payment_processor_id' => 0,
736 'payment_instrument_id' => array_search('Check', $this->paymentInstruments
, TRUE),
737 'check_number' => 'check-12345',
739 $form->cid
= $this->_individualId
;
740 $form->testSubmit($submitParams);
741 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
742 // check the membership status after additional payment, if its changed to 'New'
743 $this->assertEquals(array_search('New', CRM_Member_PseudoConstant
::membershipStatus(), TRUE), $membership['status_id']);
745 // check the contribution status and net amount after additional payment
746 $contribution = $this->callAPISuccessGetSingle('Contribution', [
747 'contact_id' => $this->_individualId
,
749 $this->assertEquals('Completed', $contribution['contribution_status']);
750 $this->validateAllPayments();
754 * Test the submit function of the membership form.
756 * @throws \CRM_Core_Exception
757 * @throws \CiviCRM_API3_Exception
759 public function testSubmitRecur(): void
{
760 CRM_Core_Session
::singleton()->getStatus(TRUE);
761 $pendingVal = $this->callAPISuccessGetValue('OptionValue', [
763 'option_group_id' => 'contribution_status',
764 'label' => 'Pending Label**',
766 //Update label for Pending contribution status.
767 $this->callAPISuccess('OptionValue', 'create', [
769 'label' => 'PendingEdited',
772 $form = $this->getForm();
774 $this->callAPISuccess('MembershipType', 'create', [
775 'id' => $this->ids
['membership_type']['AnnualFixed'],
776 'duration_unit' => 'month',
777 'duration_interval' => 1,
778 'auto_renew' => TRUE,
781 $this->createLoggedInUser();
782 $params = $this->getBaseSubmitParams();
783 $form->_mode
= 'test';
784 $form->_contactID
= $this->_individualId
;
785 $form->testSubmit($params);
786 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
787 $this->callAPISuccessGetCount('ContributionRecur', ['contact_id' => $this->_individualId
], 1);
789 $contribution = $this->callAPISuccess('Contribution', 'get', [
790 'contact_id' => $this->_individualId
,
794 //Check if Membership Payment is recorded.
795 $this->callAPISuccessGetCount('MembershipPayment', [
796 'membership_id' => $membership['id'],
797 'contribution_id' => $contribution['id'],
801 $this->callAPISuccessGetCount('LineItem', [
802 'entity_id' => $membership['id'],
803 'entity_table' => 'civicrm_membership',
804 'contribution_id' => $contribution['id'],
806 $this->assertEquals([
808 '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')) . '.',
809 'title' => 'Complete',
813 ], CRM_Core_Session
::singleton()->getStatus());
816 public function testSubmitRecurTwoRows() {
817 $this->createMembershipPriceSet();
818 $form = $this->getForm();
822 * CRM-20946: Test the financial entires especially the reversed amount,
823 * after related Contribution is cancelled
825 * @throws \CRM_Core_Exception
826 * @throws \CiviCRM_API3_Exception
828 public function testFinancialEntriesOnCancelledContribution(): void
{
829 // Create two memberships for individual $this->_individualId, via a price set in the back end.
830 $this->createTwoMembershipsViaPriceSetInBackEnd($this->_individualId
);
832 // cancel the related contribution via API
833 $contribution = $this->callAPISuccessGetSingle('Contribution', [
834 'contact_id' => $this->_individualId
,
835 'contribution_status_id' => 2,
837 $this->callAPISuccess('Contribution', 'create', [
838 'id' => $contribution['id'],
839 'contribution_status_id' => CRM_Core_PseudoConstant
::getKey('CRM_Contribute_DAO_Contribution', 'contribution_status_id', 'Cancelled'),
842 // fetch financial_trxn ID of the related contribution
843 $sql = "SELECT financial_trxn_id
844 FROM civicrm_entity_financial_trxn
845 WHERE entity_id = %1 AND entity_table = 'civicrm_contribution'
849 $financialTrxnID = CRM_Core_DAO
::singleValueQuery($sql, [1 => [$contribution['id'], 'Int']]);
851 // fetch entity_financial_trxn records and compare their cancelled records
852 $result = $this->callAPISuccess('EntityFinancialTrxn', 'Get', [
853 'financial_trxn_id' => $financialTrxnID,
854 'entity_table' => 'civicrm_financial_item',
856 // compare the reversed amounts of respective memberships after cancelling contribution
857 $cancelledMembershipAmounts = [
862 foreach ($result['values'] as $record) {
863 $this->assertEquals($cancelledMembershipAmounts[$count], $record['amount']);
869 * Test the submit function of the membership form.
871 * @throws \CRM_Core_Exception
873 public function testSubmitPayLaterWithBilling() {
874 $form = $this->getForm();
876 $this->createLoggedInUser();
878 'cid' => $this->_individualId
,
879 'join_date' => date('Y-m-d'),
882 // This format reflects the first number being the organisation & the second being the type.
883 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualFixed']],
888 'total_amount' => '50.00',
889 //Member dues, see data.xml
890 'financial_type_id' => '2',
891 'soft_credit_type_id' => '',
892 'soft_credit_contact_id' => '',
893 'payment_instrument_id' => 4,
894 'from_email_address' => '"Demonstrators Anonymous" <info@example.org>',
895 'receipt_text_signup' => 'Thank you text',
896 'payment_processor_id' => $this->_paymentProcessorID
,
897 'record_contribution' => TRUE,
899 'contribution_status_id' => 2,
900 'billing_first_name' => 'Test',
901 'billing_middlename' => 'Last',
902 'billing_street_address-5' => '10 Test St',
903 'billing_city-5' => 'Test',
904 'billing_state_province_id-5' => '1003',
905 'billing_postal_code-5' => '90210',
906 'billing_country_id-5' => '1228',
908 $form->_contactID
= $this->_individualId
;
910 $form->testSubmit($params);
911 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
912 $contribution = $this->callAPISuccessGetSingle('Contribution', [
913 'contact_id' => $this->_individualId
,
914 'contribution_status_id' => 2,
916 $this->assertEquals($contribution['trxn_id'], 777);
918 $this->callAPISuccessGetCount('LineItem', [
919 'entity_id' => $membership['id'],
920 'entity_table' => 'civicrm_membership',
921 'contribution_id' => $contribution['id'],
923 $this->callAPISuccessGetSingle('address', [
924 'contact_id' => $this->_individualId
,
925 'street_address' => '10 Test St',
926 'postal_code' => 90210,
931 * Test if membership is updated to New after contribution
932 * is updated from Partially paid to Completed.
934 * @throws \CRM_Core_Exception
935 * @throws \CiviCRM_API3_Exception
937 public function testSubmitUpdateMembershipFromPartiallyPaid() {
938 $memStatus = CRM_Member_BAO_Membership
::buildOptions('status_id', 'validate');
940 //Perform a pay later membership contribution.
941 $this->testSubmitPayLaterWithBilling();
942 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
943 $this->assertEquals($membership['status_id'], array_search('Pending', $memStatus));
944 $contribution = $this->callAPISuccessGetSingle('MembershipPayment', [
945 'membership_id' => $membership['id'],
947 $prevContribution = $this->callAPISuccessGetSingle('Contribution', ['id' => $contribution['id']]);
948 $this->callAPISuccess('Payment', 'create', [
949 'contribution_id' => $contribution['contribution_id'],
950 'payment_instrument_id' => 'Cash',
954 // Complete the contribution from offline form.
955 $form = new CRM_Contribute_Form_Contribution();
957 'id' => $contribution['contribution_id'],
958 'contribution_status_id' => CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'),
961 $fields = ['total_amount', 'net_amount', 'financial_type_id', 'receive_date', 'contact_id', 'payment_instrument_id'];
962 foreach ($fields as $val) {
963 $submitParams[$val] = $prevContribution[$val];
965 $form->testSubmit($submitParams, CRM_Core_Action
::UPDATE
);
967 //Check if Membership is updated to New.
968 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
969 $this->assertEquals($membership['status_id'], array_search('New', $memStatus));
973 * Test the submit function of the membership form.
975 * @throws \CiviCRM_API3_Exception
976 * @throws \CRM_Core_Exception
978 public function testSubmitRecurCompleteInstant() {
979 $form = $this->getForm();
980 $mut = new CiviMailUtils($this, TRUE);
981 $processor = Civi\Payment\System
::singleton()->getById($this->_paymentProcessorID
);
982 $processor->setDoDirectPaymentResult([
983 'payment_status_id' => 1,
984 'trxn_id' => 'kettles boil water',
987 $processorDetail = $processor->getPaymentProcessor();
988 $this->callAPISuccess('MembershipType', 'create', [
989 'id' => $this->ids
['membership_type']['AnnualFixed'],
990 'duration_unit' => 'month',
991 'duration_interval' => 1,
992 'auto_renew' => TRUE,
995 $this->createLoggedInUser();
996 $params = $this->getBaseSubmitParams();
997 $form->_mode
= 'test';
998 $form->_contactID
= $this->_individualId
;
999 $form->testSubmit($params);
1000 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
1001 $this->callAPISuccessGetCount('ContributionRecur', ['contact_id' => $this->_individualId
], 1);
1003 $contribution = $this->callAPISuccess('Contribution', 'getsingle', [
1004 'contact_id' => $this->_individualId
,
1008 $this->assertEquals(.14, $contribution['fee_amount']);
1009 $this->assertEquals('kettles boil water', $contribution['trxn_id']);
1010 $this->assertEquals($processorDetail['payment_instrument_id'], $contribution['payment_instrument_id']);
1012 $this->callAPISuccessGetCount('LineItem', [
1013 'entity_id' => $membership['id'],
1014 'entity_table' => 'civicrm_membership',
1015 'contribution_id' => $contribution['id'],
1017 $mut->checkMailLog([
1018 '===========================================================
1019 Billing Name and Address
1020 ===========================================================
1025 '===========================================================
1026 Membership Information
1027 ===========================================================
1028 Membership Type: AnnualFixed
1029 Membership Start Date: ',
1030 '===========================================================
1031 Credit Card Information
1032 ===========================================================
1042 * CRM-20955, CRM-20966:
1043 * Test creating two memberships with inheritance via price set in the back end,
1044 * checking that the correct primary & secondary memberships, contributions, line items
1045 * & membership_payment records are created.
1046 * Uses some data from tests/phpunit/CRM/Member/Form/dataset/data.xml .
1048 * @throws \CRM_Core_Exception
1049 * @throws \CiviCRM_API3_Exception
1051 public function testTwoInheritedMembershipsViaPriceSetInBackend(): void
{
1052 // Create an organization and give it a "Member of" relationship to $this->_individualId.
1053 $orgID = $this->organizationCreate();
1054 $relationship = $this->callAPISuccess('Relationship', 'create', [
1055 'contact_id_a' => $this->_individualId
,
1056 'contact_id_b' => $orgID,
1057 'relationship_type_id' => $this->ids
['relationship_type']['member'],
1061 // Create two memberships for the organization, via a price set in the back end.
1062 $this->createTwoMembershipsViaPriceSetInBackEnd($orgID);
1064 // Check the primary memberships on the organization.
1065 $orgMembershipResult = $this->callAPISuccess('membership', 'get', [
1066 'contact_id' => $orgID,
1068 $this->assertEquals(2, $orgMembershipResult['count'], '2 primary memberships should have been created on the organization.');
1069 $primaryMembershipIds = [];
1070 foreach ($orgMembershipResult['values'] as $membership) {
1071 $primaryMembershipIds[] = $membership['id'];
1072 $this->assertTrue(empty($membership['owner_membership_id']), 'Membership on the organization has owner_membership_id so is inherited.');
1075 // CRM-20955: check that correct inherited memberships were created for the individual,
1076 // for both of the primary memberships.
1077 $individualMembershipResult = $this->callAPISuccess('membership', 'get', [
1078 'contact_id' => $this->_individualId
,
1080 $this->assertEquals(2, $individualMembershipResult['count'], "2 inherited memberships should have been created on the individual.");
1081 foreach ($individualMembershipResult['values'] as $membership) {
1082 $this->assertNotEmpty($membership['owner_membership_id'], "Membership on the individual lacks owner_membership_id so is not inherited.");
1083 $this->assertNotContains($membership['id'], $primaryMembershipIds, "Inherited membership id should not be the id of a primary membership.");
1084 $this->assertContains($membership['owner_membership_id'], $primaryMembershipIds, "Inherited membership owner_membership_id should be the id of a primary membership.");
1087 // CRM-20966: check that the correct membership contribution, line items
1088 // & membership_payment records were created for the organization.
1089 $contributionResult = $this->callAPISuccess('contribution', 'get', [
1090 'contact_id' => $orgID,
1092 'api.line_item.get' => [],
1093 'api.membership_payment.get' => [],
1095 $this->assertEquals(1, $contributionResult['count'], "One contribution should have been created for the organization's memberships.");
1097 $this->assertEquals(2, $contributionResult['values'][0]['api.line_item.get']['count'], "2 line items should have been created for the organization's memberships.");
1098 foreach ($contributionResult['values'][0]['api.line_item.get']['values'] as $lineItem) {
1099 $this->assertEquals('civicrm_membership', $lineItem['entity_table'], "Membership line item's entity_table should be 'civicrm_membership'.");
1100 $this->assertContains($lineItem['entity_id'], $primaryMembershipIds, "Membership line item's entity_id should be the id of a primary membership.");
1103 $this->assertEquals(2, $contributionResult['values'][0]['api.membership_payment.get']['count'], "2 membership payment records should have been created for the organization's memberships.");
1104 foreach ($contributionResult['values'][0]['api.membership_payment.get']['values'] as $membershipPayment) {
1105 $this->assertEquals($contributionResult['values'][0]['id'], $membershipPayment['contribution_id'], "membership payment's contribution ID should be the ID of the organization's membership contribution.");
1106 $this->assertContains($membershipPayment['membership_id'], $primaryMembershipIds, "membership payment's membership ID should be the ID of a primary membership.");
1108 // Check for orphan line items.
1109 $this->callAPISuccessGetCount('LineItem', [], 2);
1111 // CRM-20966: check that deleting relationship used for inheritance does not delete contribution.
1112 $this->callAPISuccess('relationship', 'delete', [
1113 'id' => $relationship['id'],
1116 $contributionResultAfterRelationshipDelete = $this->callAPISuccess('contribution', 'get', [
1117 'id' => $contributionResult['values'][0]['id'],
1118 'contact_id' => $orgID,
1120 $this->assertEquals(1, $contributionResultAfterRelationshipDelete['count'], "Contribution has been wrongly deleted.");
1124 * dev/core/issues/860:
1125 * Test creating two memberships via price set in the back end with a discount,
1126 * checking that the line items have correct amounts.
1128 * @throws \CRM_Core_Exception
1129 * @throws \CiviCRM_API3_Exception
1131 public function testTwoMembershipsViaPriceSetInBackendWithDiscount(): void
{
1132 // Register buildAmount hook to apply discount.
1133 $this->hookClass
->setHook('civicrm_buildAmount', [$this, 'buildAmountMembershipDiscount']);
1135 // Create two memberships for individual $this->_individualId, via a price set in the back end.
1136 $this->createTwoMembershipsViaPriceSetInBackEnd($this->_individualId
);
1137 $contribution = $this->callAPISuccessGetSingle('Contribution', [
1138 'contact_id' => $this->_individualId
,
1140 // Note: we can't check for the contribution total being discounted, because the total is set
1141 // when the contribution is created via $form->testSubmit(), but buildAmount isn't called
1142 // until testSubmit() runs. Fixing that might involve making testSubmit() more sophisticated,
1143 // or just hacking total_amount for this case.
1145 $lineItemResult = $this->callAPISuccess('LineItem', 'get', [
1146 'contribution_id' => $contribution['id'],
1148 $this->assertEquals(2, $lineItemResult['count']);
1149 $discountedItems = 0;
1150 foreach ($lineItemResult['values'] as $lineItem) {
1151 if (CRM_Utils_String
::startsWith($lineItem['label'], 'Long Haired Goat')) {
1152 $this->assertEquals(15.0, $lineItem['line_total']);
1153 $this->assertEquals('Long Haired Goat - one leg free!', $lineItem['label']);
1157 $this->assertEquals(1, $discountedItems);
1161 * Implements hook_civicrm_buildAmount() for testTwoMembershipsViaPriceSetInBackendWithDiscount().
1163 public function buildAmountMembershipDiscount($pageType, &$form, &$amount) {
1164 foreach ($amount as $id => $priceField) {
1165 if (is_array($priceField['options'])) {
1166 foreach ($priceField['options'] as $optionId => $option) {
1167 if ($option['membership_type_id'] == $this->ids
['membership_type']['AnnualRolling']) {
1168 // Long Haired Goat membership discount.
1169 $amount[$id]['options'][$optionId]['amount'] = $option['amount'] * 0.75;
1170 $amount[$id]['options'][$optionId]['label'] = $option['label'] . ' - one leg free!';
1178 * Get a membership form object.
1180 * We need to instantiate the form to run preprocess, which means we have to trick it about the request method.
1182 * @return \CRM_Member_Form_Membership
1183 * @throws \CRM_Core_Exception
1185 protected function getForm() {
1186 if (isset($_REQUEST['cid'])) {
1187 unset($_REQUEST['cid']);
1189 $form = new CRM_Member_Form_Membership();
1190 $_SERVER['REQUEST_METHOD'] = 'GET';
1191 $form->controller
= new CRM_Core_Controller();
1198 protected function getBaseSubmitParams() {
1200 'cid' => $this->_individualId
,
1201 'price_set_id' => 0,
1202 'join_date' => date('Y-m-d'),
1205 'campaign_id' => '',
1206 // This format reflects the first number being the organisation & the second being the type.
1207 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualFixed']],
1208 'auto_renew' => '1',
1213 'total_amount' => '77.00',
1214 //Member dues, see data.xml
1215 'financial_type_id' => '2',
1216 'soft_credit_type_id' => 11,
1217 'soft_credit_contact_id' => '',
1218 'from_email_address' => '"Demonstrators Anonymous" <info@example.org>',
1219 'receipt_text' => 'Thank you text',
1220 'payment_processor_id' => $this->_paymentProcessorID
,
1221 'credit_card_number' => '4111111111111111',
1223 'credit_card_exp_date' => [
1225 'Y' => date('Y') +
1,
1227 'credit_card_type' => 'Visa',
1228 'billing_first_name' => 'Test',
1229 'billing_middlename' => 'Last',
1230 'billing_street_address-5' => '10 Test St',
1231 'billing_city-5' => 'Test',
1232 'billing_state_province_id-5' => '1003',
1233 'billing_postal_code-5' => '90210',
1234 'billing_country_id-5' => '1228',
1235 'send_receipt' => 1,
1242 * create two memberships for the same individual, via a price set in the back end.
1244 * @param int $contactId Id of contact on which the memberships will be created.
1246 * @throws \CRM_Core_Exception
1247 * @throws \CiviCRM_API3_Exception
1249 protected function createTwoMembershipsViaPriceSetInBackEnd($contactId): void
{
1250 $form = $this->getForm();
1251 $form->preProcess();
1252 $this->createLoggedInUser();
1253 $pfvIDs = $this->createMembershipPriceSet();
1255 // register for both of these memberships via backoffice membership form submission
1257 'cid' => $contactId,
1258 'join_date' => date('Y-m-d'),
1261 // This format reflects the 23 being the organisation & the 25 being the type.
1262 "price_" . $this->getPriceFieldID() => $pfvIDs,
1263 'price_set_id' => $this->getPriceSetID(),
1264 'membership_type_id' => [1 => 0],
1265 'auto_renew' => '0',
1266 'max_related' => '',
1269 'total_amount' => '30.00',
1270 //Member dues, see data.xml
1271 'financial_type_id' => '2',
1272 'soft_credit_type_id' => '',
1273 'soft_credit_contact_id' => '',
1274 'payment_instrument_id' => 4,
1275 'from_email_address' => '"Demonstrators Anonymous" <info@example.org>',
1276 'receipt_text_signup' => 'Thank you text',
1277 'payment_processor_id' => $this->_paymentProcessorID
,
1278 'record_contribution' => TRUE,
1280 'contribution_status_id' => CRM_Core_PseudoConstant
::getKey('CRM_Contribute_DAO_Contribution', 'contribution_status_id', 'Pending'),
1281 'billing_first_name' => 'Test',
1282 'billing_middlename' => 'Last',
1283 'billing_street_address-5' => '10 Test St',
1284 'billing_city-5' => 'Test',
1285 'billing_state_province_id-5' => '1003',
1286 'billing_postal_code-5' => '90210',
1287 'billing_country_id-5' => '1228',
1289 $form->testSubmit($params);
1293 * Test membership status overrides when contribution is cancelled.
1295 * @throws \CRM_Core_Exception
1296 * @throws \CiviCRM_API3_Exception
1298 public function testContributionFormStatusUpdate(): void
{
1300 $this->_contactID
= $this->createLoggedInUser();
1301 $this->createContributionAndMembershipOrder();
1304 'total_amount' => 50,
1305 'financial_type_id' => 2,
1306 'contact_id' => $this->_contactID
,
1307 'payment_instrument_id' => CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'payment_instrument_id', 'Check'),
1308 'contribution_status_id' => CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Cancelled'),
1311 //Update Contribution to Cancelled.
1312 $form = new CRM_Contribute_Form_Contribution();
1313 $form->_id
= $params['id'] = $this->ids
['Contribution'][0];
1314 $form->_mode
= NULL;
1315 $form->_contactID
= $this->_individualId
;
1316 $form->testSubmit($params, CRM_Core_Action
::UPDATE
);
1317 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_contactID
]);
1319 //Assert membership status overrides when the contribution cancelled.
1320 $this->assertEquals(TRUE, $membership['is_override']);
1321 $this->assertEquals($membership['status_id'], $this->callAPISuccessGetValue('MembershipStatus', [
1323 'name' => 'Cancelled',
1328 * CRM-21656: Test the submit function of the membership form if Sales Tax is enabled.
1329 * This test simulates what happens when one hits Edit on a Contribution that has both LineItems and Sales Tax components
1330 * Without making any Edits -> check that the LineItem data remain the same
1331 * In addition (a data-integrity check) -> check that the LineItem data add up to the data at the Contribution level
1333 * @throws \CRM_Core_Exception
1334 * @throws \CiviCRM_API3_Exception
1336 public function testLineItemAmountOnSalesTax() {
1337 $this->enableTaxAndInvoicing();
1338 $this->addTaxAccountToFinancialType(2);
1339 $form = $this->getForm();
1340 $form->preProcess();
1341 $this->mut
= new CiviMailUtils($this, TRUE);
1342 $this->createLoggedInUser();
1343 $priceSet = $this->callAPISuccess('PriceSet', 'Get', ['extends' => 'CiviMember']);
1344 $form->set('priceSetId', $priceSet['id']);
1345 // we are simulating the creation of a Price Set in Administer -> CiviContribute -> Manage Price Sets so set is_quick_config = 0
1346 $this->callAPISuccess('PriceSet', 'Create', ['id' => $priceSet['id'], 'is_quick_config' => 0]);
1347 // clean the price options static variable to repopulate the options, in order to fetch tax information
1348 \Civi
::$statics['CRM_Price_BAO_PriceField']['priceOptions'] = NULL;
1349 CRM_Price_BAO_PriceSet
::buildPriceSet($form);
1350 // rebuild the price set form variable to include the tax information against each price options
1351 $form->_priceSet
= current(CRM_Price_BAO_PriceSet
::getSetDetail($priceSet['id']));
1353 'cid' => $this->_individualId
,
1354 'join_date' => date('Y-m-d'),
1357 // This format reflects the first number being the organisation & the second being the type.
1358 'membership_type_id' => [$this->ids
['contact']['organization'], $this->ids
['membership_type']['AnnualFixed']],
1359 'record_contribution' => 1,
1360 'total_amount' => 55,
1361 'receive_date' => date('Y-m-d') . ' 20:36:00',
1362 'payment_instrument_id' => array_search('Check', $this->paymentInstruments
, TRUE),
1363 'contribution_status_id' => CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'),
1364 //Member dues, see data.xml
1365 'financial_type_id' => 2,
1366 'payment_processor_id' => $this->_paymentProcessorID
,
1368 $form->_contactID
= $this->_individualId
;
1369 $form->testSubmit($params);
1371 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
1372 $lineItem = $this->callAPISuccessGetSingle('LineItem', ['entity_id' => $membership['id'], 'entity_table' => 'civicrm_membership']);
1373 $this->assertEquals(1, $lineItem['qty']);
1374 $this->assertEquals(50.00, $lineItem['unit_price']);
1375 $this->assertEquals(50.00, $lineItem['line_total']);
1376 $this->assertEquals(5.00, $lineItem['tax_amount']);
1378 // Simply save the 'Edit Contribution' form
1379 $form = new CRM_Contribute_Form_Contribution();
1380 $form->_context
= 'membership';
1381 $form->_values
= $this->callAPISuccessGetSingle('Contribution', ['id' => $lineItem['contribution_id'], 'return' => ['total_amount', 'net_amount', 'fee_amount', 'tax_amount']]);
1383 'contact_id' => $this->_individualId
,
1384 'id' => $lineItem['contribution_id'],
1385 'financial_type_id' => 2,
1386 'contribution_status_id' => CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'),
1388 CRM_Core_Action
::UPDATE
);
1390 // ensure that the LineItem data remain the same
1391 $lineItem = $this->callAPISuccessGetSingle('LineItem', ['entity_id' => $membership['id'], 'entity_table' => 'civicrm_membership']);
1392 $this->assertEquals(1, $lineItem['qty']);
1393 $this->assertEquals(50.00, $lineItem['unit_price']);
1394 $this->assertEquals(50.00, $lineItem['line_total']);
1395 $this->assertEquals(5.00, $lineItem['tax_amount']);
1397 // ensure that the LineItem data add up to the data at the Contribution level
1398 $contribution = $this->callAPISuccessGetSingle('Contribution',
1400 'contribution_id' => 1,
1401 'return' => ['tax_amount', 'total_amount'],
1404 $this->assertEquals($contribution['total_amount'], $lineItem['line_total'] +
$lineItem['tax_amount']);
1405 $this->assertEquals($contribution['tax_amount'], $lineItem['tax_amount']);
1407 $financialItems = $this->callAPISuccess('FinancialItem', 'get', []);
1408 $financialItems_sum = 0;
1409 foreach ($financialItems['values'] as $financialItem) {
1410 $financialItems_sum +
= $financialItem['amount'];
1412 $this->assertEquals($contribution['total_amount'], $financialItems_sum);
1416 * Test that membership end_date is correct for multiple terms for pending contribution
1418 * @throws CiviCRM_API3_Exception
1419 * @throws \CRM_Core_Exception
1420 * @throws \Exception
1422 public function testCreatePendingWithMultipleTerms() {
1423 CRM_Core_Session
::singleton()->getStatus(TRUE);
1424 $this->mut
= new CiviMailUtils($this, TRUE);
1425 $this->createLoggedInUser();
1426 $membershipTypeAnnualRolling = $this->callAPISuccess('membership_type', 'create', [
1428 'name' => 'AnnualRollingNew',
1429 'member_of_contact_id' => $this->ids
['contact']['organization'],
1430 'duration_unit' => 'year',
1431 'minimum_fee' => 50,
1432 'duration_interval' => 1,
1433 'period_type' => 'rolling',
1434 'relationship_type_id' => 20,
1435 'relationship_direction' => 'b_a',
1436 'financial_type_id' => 2,
1439 'cid' => $this->_individualId
,
1440 'join_date' => date('Y-m-d'),
1443 'contribution_status_id' => CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending'),
1444 'membership_type_id' => [$this->ids
['contact']['organization'], $membershipTypeAnnualRolling['id']],
1445 'max_related' => '',
1447 'record_contribution' => 1,
1449 'total_amount' => $this->formatMoneyInput(150.00),
1450 'financial_type_id' => '2',
1451 'soft_credit_type_id' => '11',
1452 'soft_credit_contact_id' => '',
1453 'from_email_address' => '"Demonstrators Anonymous" <info@example.org>',
1454 'receipt_text' => '',
1456 $form = $this->getForm();
1457 $form->preProcess();
1458 $form->_contactID
= $this->_individualId
;
1459 $form->testSubmit($params);
1460 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
1461 $contribution = $this->callAPISuccess('Contribution', 'get', [
1462 'contact_id' => $this->_individualId
,
1464 $endDate = (new DateTime(date('Y-m-d')))->modify('+3 years')->modify('-1 day');
1465 $endDate = $endDate->format("Y-m-d");
1467 $this->assertEquals($endDate, $membership['end_date'], 'Membership end date should be ' . $endDate);
1468 $this->assertEquals(1, count($contribution['values']), 'Pending contribution should be created.');
1469 $contribution = $contribution['values'][$contribution['id']];
1470 $additionalPaymentForm = new CRM_Contribute_Form_AdditionalPayment();
1471 $additionalPaymentForm->testSubmit([
1472 'total_amount' => 150.00,
1473 'trxn_date' => date("Y-m-d H:i:s"),
1474 'payment_instrument_id' => array_search('Check', $this->paymentInstruments
),
1475 'check_number' => 'check-12345',
1477 'currency' => 'USD',
1479 'financial_type_id' => 1,
1481 'payment_processor_id' => 0,
1482 'contact_id' => $this->_individualId
,
1483 'contribution_id' => $contribution['id'],
1485 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId
]);
1486 $contribution = $this->callAPISuccess('Contribution', 'get', [
1487 'contact_id' => $this->_individualId
,
1488 'contribution_status_id' => CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'),
1490 $this->assertEquals($endDate, $membership['end_date'], 'Membership end date should be same (' . $endDate . ') after payment');
1491 $this->assertCount(1, $contribution['values'], 'Completed contribution should be fetched.');
1495 * Test Membership Payment owned by other contact, membership view should show all contribution records in listing.
1498 * @throws \CRM_Core_Exception
1499 * @throws \CiviCRM_API3_Exception
1500 * @throws \Exception
1502 public function testMembershipViewContributionOwnerDifferent() {
1504 $contactId1 = $this->individualCreate();
1506 // Contribution Onwer
1507 $contactId2 = $this->individualCreate();
1509 // create new membership type
1510 $membershipTypeAnnualFixed = $this->callAPISuccess('MembershipType', 'create', [
1512 'name' => 'AnnualFixed 2',
1513 'member_of_contact_id' => $this->organizationCreate(),
1514 'duration_unit' => 'year',
1515 'minimum_fee' => 50,
1516 'duration_interval' => 1,
1517 'period_type' => 'fixed',
1518 'fixed_period_start_day' => '101',
1519 'fixed_period_rollover_day' => '1231',
1520 'financial_type_id' => 2,
1523 // create Membership
1524 $membershipId = $this->contactMembershipCreate([
1525 'contact_id' => $contactId1,
1526 'membership_type_id' => $membershipTypeAnnualFixed['id'],
1527 'status_id' => 'New',
1532 'membership_id' => $membershipId,
1533 'total_amount' => 25,
1534 'financial_type_id' => 2,
1535 'contact_id' => $contactId2,
1536 'receive_date' => '2020-08-08',
1538 $contribution1 = CRM_Member_BAO_Membership
::recordMembershipContribution($contriParams);
1542 'membership_id' => $membershipId,
1543 'total_amount' => 25,
1544 'financial_type_id' => 2,
1545 'contact_id' => $contactId2,
1546 'receive_date' => '2020-07-08',
1548 $contribution2 = CRM_Member_BAO_Membership
::recordMembershipContribution($contriParams);
1550 // View Membership record
1551 $membershipViewForm = new CRM_Member_Form_MembershipView();
1552 $membershipViewForm->controller
= new CRM_Core_Controller_Simple('CRM_Member_Form_MembershipView', 'View Membership');
1553 $membershipViewForm->set('id', $membershipId);
1554 $membershipViewForm->set('context', 'membership');
1555 $membershipViewForm->controller
->setEmbedded(TRUE);
1556 $membershipViewForm->preProcess();
1558 // get contribution rows related to membership payments
1559 $templateVar = $membershipViewForm::getTemplate()->get_template_vars('rows');
1561 $this->assertEquals($templateVar[0]['contribution_id'], $contribution1->id
);
1562 $this->assertEquals($templateVar[0]['contact_id'], $contactId2);
1564 $this->assertEquals($templateVar[1]['contribution_id'], $contribution2->id
);
1565 $this->assertEquals($templateVar[1]['contact_id'], $contactId2);
1566 $this->assertEquals(count($templateVar), 2);