Cleanup on exceptions
[civicrm-core.git] / tests / phpunit / CRM / Member / Form / MembershipTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 * File for the MembershipTest class
30 *
31 * (PHP 5)
32 *
33 * @author Walt Haas <walt@dharmatech.org> (801) 534-1262
34 */
35
36 /**
37 * Test CRM_Member_Form_Membership functions.
38 *
39 * @package CiviCRM
40 * @group headless
41 */
42 class CRM_Member_Form_MembershipTest extends CiviUnitTestCase {
43
44 protected $_individualId;
45 protected $_contribution;
46 protected $_financialTypeId = 1;
47 protected $_entity = 'Membership';
48 protected $_params;
49 protected $_ids = [];
50 protected $_paymentProcessorID;
51
52 /**
53 * Membership type ID for annual fixed membership.
54 *
55 * @var int
56 */
57 protected $membershipTypeAnnualFixedID;
58
59 /**
60 * Parameters to create payment processor.
61 *
62 * @var array
63 */
64 protected $_processorParams = [];
65
66 /**
67 * ID of created membership.
68 *
69 * @var int
70 */
71 protected $_membershipID;
72
73 /**
74 * Payment instrument mapping.
75 *
76 * @var array
77 */
78 protected $paymentInstruments = [];
79
80 /**
81 * @var CiviMailUtils
82 */
83 protected $mut;
84
85 /**
86 * Test setup for every test.
87 *
88 * Connect to the database, truncate the tables that will be used
89 * and redirect stdin to a temporary file.
90 *
91 * @throws \CRM_Core_Exception
92 */
93 public function setUp() {
94 $this->_apiversion = 3;
95 parent::setUp();
96
97 $this->_individualId = $this->individualCreate();
98 $this->_paymentProcessorID = $this->processorCreate();
99
100 $this->loadXMLDataSet(dirname(__FILE__) . '/dataset/data.xml');
101
102 $membershipTypeAnnualFixed = $this->callAPISuccess('membership_type', 'create', [
103 'domain_id' => 1,
104 'name' => "AnnualFixed",
105 'member_of_contact_id' => 23,
106 'duration_unit' => "year",
107 'minimum_fee' => 50,
108 'duration_interval' => 1,
109 'period_type' => "fixed",
110 'fixed_period_start_day' => "101",
111 'fixed_period_rollover_day' => "1231",
112 'relationship_type_id' => 20,
113 'financial_type_id' => 2,
114 ]);
115 $this->membershipTypeAnnualFixedID = $membershipTypeAnnualFixed['id'];
116
117 $instruments = $this->callAPISuccess('contribution', 'getoptions', ['field' => 'payment_instrument_id']);
118 $this->paymentInstruments = $instruments['values'];
119 }
120
121 /**
122 * Clean up after each test.
123 */
124 public function tearDown() {
125 $this->quickCleanUpFinancialEntities();
126 $this->quickCleanup(
127 [
128 'civicrm_relationship',
129 'civicrm_membership_type',
130 'civicrm_membership',
131 'civicrm_uf_match',
132 'civicrm_email',
133 ]
134 );
135 foreach ([17, 18, 23, 32] as $contactID) {
136 $this->callAPISuccess('contact', 'delete', ['id' => $contactID, 'skip_undelete' => TRUE]);
137 }
138 $this->callAPISuccess('relationship_type', 'delete', ['id' => 20]);
139 }
140
141 /**
142 * Test CRM_Member_Form_Membership::formRule() with a parameter
143 * that has an empty contact_select_id value
144 *
145 * @throws \CiviCRM_API3_Exception
146 */
147 public function testFormRuleEmptyContact() {
148 $params = [
149 'contact_select_id' => 0,
150 'membership_type_id' => [1 => NULL],
151 ];
152 $files = [];
153 $obj = new CRM_Member_Form_Membership();
154 $rc = $obj->formRule($params, $files, $obj);
155 $this->assertType('array', $rc);
156 $this->assertTrue(array_key_exists('membership_type_id', $rc));
157
158 $params['membership_type_id'] = [1 => 3];
159 $rc = $obj->formRule($params, $files, $obj);
160 $this->assertType('array', $rc);
161 $this->assertTrue(array_key_exists('join_date', $rc));
162 }
163
164 /**
165 * Test that form rule fails if start date is before join date.
166 *
167 * Test CRM_Member_Form_Membership::formRule() with a parameter
168 * that has an start date before the join date and a rolling
169 * membership type.
170 */
171 public function testFormRuleRollingEarlyStart() {
172 $unixNow = time();
173 $unixYesterday = $unixNow - (24 * 60 * 60);
174 $ymdYesterday = date('Y-m-d', $unixYesterday);
175 $params = [
176 'join_date' => date('Y-m-d'),
177 'start_date' => $ymdYesterday,
178 'end_date' => '',
179 'membership_type_id' => ['23', '15'],
180 ];
181 $files = [];
182 $obj = new CRM_Member_Form_Membership();
183 $rc = call_user_func(['CRM_Member_Form_Membership', 'formRule'],
184 $params, $files, $obj
185 );
186 $this->assertType('array', $rc);
187 $this->assertTrue(array_key_exists('start_date', $rc));
188 }
189
190 /**
191 * Test CRM_Member_Form_Membership::formRule() with a parameter
192 * that has an end date before the start date and a rolling
193 * membership type
194 */
195 public function testFormRuleRollingEarlyEnd() {
196 $unixNow = time();
197 $unixYesterday = $unixNow - (24 * 60 * 60);
198 $ymdYesterday = date('Y-m-d', $unixYesterday);
199 $params = [
200 'join_date' => date('Y-m-d'),
201 'start_date' => date('Y-m-d'),
202 'end_date' => $ymdYesterday,
203 'membership_type_id' => ['23', '15'],
204 ];
205 $files = [];
206 $obj = new CRM_Member_Form_Membership();
207 $rc = $obj->formRule($params, $files, $obj);
208 $this->assertType('array', $rc);
209 $this->assertTrue(array_key_exists('end_date', $rc));
210 }
211
212 /**
213 * Test CRM_Member_Form_Membership::formRule() with end date but no start date and a rolling membership type.
214 */
215 public function testFormRuleRollingEndNoStart() {
216 $unixNow = time();
217 $unixYearFromNow = $unixNow + (365 * 24 * 60 * 60);
218 $ymdYearFromNow = date('Y-m-d', $unixYearFromNow);
219 $params = [
220 'join_date' => date('Y-m-d'),
221 'start_date' => '',
222 'end_date' => $ymdYearFromNow,
223 'membership_type_id' => ['23', '15'],
224 ];
225 $files = [];
226 $obj = new CRM_Member_Form_Membership();
227 $rc = $obj->formRule($params, $files, $obj);
228 $this->assertType('array', $rc);
229 $this->assertTrue(array_key_exists('start_date', $rc));
230 }
231
232 /**
233 * Test CRM_Member_Form_Membership::formRule() with a parameter
234 * that has an end date and a lifetime membership type
235 */
236 public function testFormRuleRollingLifetimeEnd() {
237 $unixNow = time();
238 $unixYearFromNow = $unixNow + (365 * 24 * 60 * 60);
239 $params = [
240 'join_date' => date('Y-m-d'),
241 'start_date' => date('Y-m-d'),
242 'end_date' => date('Y-m-d',
243 $unixYearFromNow
244 ),
245 'membership_type_id' => ['23', '25'],
246 ];
247 $files = [];
248 $obj = new CRM_Member_Form_Membership();
249 $rc = $obj->formRule($params, $files, $obj);
250 $this->assertType('array', $rc);
251 $this->assertTrue(array_key_exists('status_id', $rc));
252 }
253
254 /**
255 * Test CRM_Member_Form_Membership::formRule() with a parameter
256 * that has permanent override and no status
257 */
258 public function testFormRulePermanentOverrideWithNoStatus() {
259 $unixNow = time();
260 $params = [
261 'join_date' => date('Y-m-d'),
262 'membership_type_id' => ['23', '25'],
263 'is_override' => TRUE,
264 ];
265 $files = [];
266 $obj = new CRM_Member_Form_Membership();
267 $rc = $obj->formRule($params, $files, $obj);
268 $this->assertType('array', $rc);
269 $this->assertTrue(array_key_exists('status_id', $rc));
270 }
271
272 public function testFormRuleUntilDateOverrideWithValidOverrideEndDate() {
273 $params = [
274 'join_date' => date('Y-m-d'),
275 'membership_type_id' => ['23', '25'],
276 'is_override' => TRUE,
277 'status_id' => 1,
278 'status_override_end_date' => date('Y-m-d'),
279 ];
280 $files = [];
281 $membershipForm = new CRM_Member_Form_Membership();
282 $validationResponse = $membershipForm->formRule($params, $files, $membershipForm);
283 $this->assertTrue($validationResponse);
284 }
285
286 public function testFormRuleUntilDateOverrideWithNoOverrideEndDate() {
287 $params = [
288 'join_date' => date('Y-m-d'),
289 'membership_type_id' => ['23', '25'],
290 'is_override' => CRM_Member_StatusOverrideTypes::UNTIL_DATE,
291 'status_id' => 1,
292 ];
293 $files = [];
294 $membershipForm = new CRM_Member_Form_Membership();
295 $validationResponse = $membershipForm->formRule($params, $files, $membershipForm);
296 $this->assertType('array', $validationResponse);
297 $this->assertEquals('Please enter the Membership override end date.', $validationResponse['status_override_end_date']);
298 }
299
300 /**
301 * Test CRM_Member_Form_Membership::formRule() with a join date
302 * of one month from now and a rolling membership type
303 */
304 public function testFormRuleRollingJoin1MonthFromNow() {
305 $unixNow = time();
306 $unix1MFmNow = $unixNow + (31 * 24 * 60 * 60);
307 $params = [
308 'join_date' => date('Y-m-d', $unix1MFmNow),
309 'start_date' => '',
310 'end_date' => '',
311 'membership_type_id' => ['23', '15'],
312 ];
313 $files = [];
314 $obj = new CRM_Member_Form_Membership();
315 $rc = $obj->formRule($params, $files, $obj);
316
317 // Should have found no valid membership status.
318 $this->assertType('array', $rc);
319 $this->assertTrue(array_key_exists('_qf_default', $rc));
320 }
321
322 /**
323 * Test CRM_Member_Form_Membership::formRule() with a join date of today and a rolling membership type.
324 */
325 public function testFormRuleRollingJoinToday() {
326 $params = [
327 'join_date' => date('Y-m-d'),
328 'start_date' => '',
329 'end_date' => '',
330 'membership_type_id' => ['23', '15'],
331 ];
332 $files = [];
333 $obj = new CRM_Member_Form_Membership();
334 $rc = $obj->formRule($params, $files, $obj);
335
336 // Should have found New membership status
337 $this->assertTrue($rc);
338 }
339
340 /**
341 * Test CRM_Member_Form_Membership::formRule() with a join date
342 * of one month ago and a rolling membership type
343 */
344 public function testFormRuleRollingJoin1MonthAgo() {
345 $unixNow = time();
346 $unix1MAgo = $unixNow - (31 * 24 * 60 * 60);
347 $params = [
348 'join_date' => date('Y-m-d', $unix1MAgo),
349 'start_date' => '',
350 'end_date' => '',
351 'membership_type_id' => ['23', '15'],
352 ];
353 $files = [];
354 $obj = new CRM_Member_Form_Membership();
355 $rc = $obj->formRule($params, $files, $obj);
356
357 // Should have found New membership status.
358 $this->assertTrue($rc);
359 }
360
361 /**
362 * Test CRM_Member_Form_Membership::formRule() with a join date of six months ago and a rolling membership type.
363 */
364 public function testFormRuleRollingJoin6MonthsAgo() {
365 $unixNow = time();
366 $unix6MAgo = $unixNow - (180 * 24 * 60 * 60);
367 $params = [
368 'join_date' => date('Y-m-d', $unix6MAgo),
369 'start_date' => '',
370 'end_date' => '',
371 'membership_type_id' => ['23', '15'],
372 ];
373 $files = [];
374 $obj = new CRM_Member_Form_Membership();
375 $rc = $obj->formRule($params, $files, $obj);
376
377 // Should have found Current membership status.
378 $this->assertTrue($rc);
379 }
380
381 /**
382 * Test CRM_Member_Form_Membership::formRule() with a join date
383 * of one year+ ago and a rolling membership type
384 */
385 public function testFormRuleRollingJoin1YearAgo() {
386 $unixNow = time();
387 $unix1YAgo = $unixNow - (370 * 24 * 60 * 60);
388 $params = [
389 'join_date' => date('Y-m-d', $unix1YAgo),
390 'start_date' => '',
391 'end_date' => '',
392 'membership_type_id' => ['23', '15'],
393 ];
394 $files = [];
395 $obj = new CRM_Member_Form_Membership();
396 $rc = $obj->formRule($params, $files, $obj);
397
398 // Should have found Grace membership status
399 $this->assertTrue($rc);
400 }
401
402 /**
403 * Test CRM_Member_Form_Membership::formRule() with a join date
404 * of two years ago and a rolling membership type
405 */
406 public function testFormRuleRollingJoin2YearsAgo() {
407 $unixNow = time();
408 $unix2YAgo = $unixNow - (2 * 365 * 24 * 60 * 60);
409 $params = [
410 'join_date' => date('Y-m-d', $unix2YAgo),
411 'start_date' => '',
412 'end_date' => '',
413 'membership_type_id' => ['23', '15'],
414 ];
415 $files = [];
416 $obj = new CRM_Member_Form_Membership();
417 $rc = $obj->formRule($params, $files, $obj);
418
419 // Should have found Expired membership status
420 $this->assertTrue($rc);
421 }
422
423 /**
424 * Test CRM_Member_Form_Membership::formRule() with a join date
425 * of six months ago and a fixed membership type
426 */
427 public function testFormRuleFixedJoin6MonthsAgo() {
428 $unixNow = time();
429 $unix6MAgo = $unixNow - (180 * 24 * 60 * 60);
430 $params = [
431 'join_date' => date('Y-m-d', $unix6MAgo),
432 'start_date' => '',
433 'end_date' => '',
434 'membership_type_id' => ['23', '7'],
435 ];
436 $files = [];
437 $obj = new CRM_Member_Form_Membership();
438 $rc = $obj->formRule($params, $files, $obj);
439
440 // Should have found Current membership status
441 $this->assertTrue($rc);
442 }
443
444 /**
445 * Test the submit function of the membership form.
446 *
447 * @param string $thousandSeparator
448 *
449 * @dataProvider getThousandSeparators
450 */
451 public function testSubmit($thousandSeparator) {
452 CRM_Core_Session::singleton()->getStatus(TRUE);
453 $this->setCurrencySeparators($thousandSeparator);
454 $form = $this->getForm();
455 $form->preProcess();
456 $this->mut = new CiviMailUtils($this, TRUE);
457 $form->_mode = 'test';
458 $this->createLoggedInUser();
459 $params = [
460 'cid' => $this->_individualId,
461 'join_date' => date('2/d/Y', time()),
462 'start_date' => '',
463 'end_date' => '',
464 // This format reflects the 23 being the organisation & the 25 being the type.
465 'membership_type_id' => [23, $this->membershipTypeAnnualFixedID],
466 'auto_renew' => '0',
467 'max_related' => '',
468 'num_terms' => '1',
469 'source' => '',
470 'total_amount' => $this->formatMoneyInput(1234.56),
471 //Member dues, see data.xml
472 'financial_type_id' => '2',
473 'soft_credit_type_id' => '',
474 'soft_credit_contact_id' => '',
475 'from_email_address' => '"Demonstrators Anonymous" <info@example.org>',
476 'payment_processor_id' => $this->_paymentProcessorID,
477 'credit_card_number' => '4111111111111111',
478 'cvv2' => '123',
479 'credit_card_exp_date' => [
480 'M' => '9',
481 // TODO: Future proof
482 'Y' => '2024',
483 ],
484 'credit_card_type' => 'Visa',
485 'billing_first_name' => 'Test',
486 'billing_middlename' => 'Last',
487 'billing_street_address-5' => '10 Test St',
488 'billing_city-5' => 'Test',
489 'billing_state_province_id-5' => '1003',
490 'billing_postal_code-5' => '90210',
491 'billing_country_id-5' => '1228',
492 'send_receipt' => TRUE,
493 'receipt_text' => 'Receipt text',
494 ];
495 $form->_contactID = $this->_individualId;
496 $form->testSubmit($params);
497 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId]);
498 $this->callAPISuccessGetCount('ContributionRecur', ['contact_id' => $this->_individualId], 0);
499 $contribution = $this->callAPISuccess('Contribution', 'get', [
500 'contact_id' => $this->_individualId,
501 'is_test' => TRUE,
502 ]);
503
504 //CRM-20264 : Check that CC type and number (last 4 digit) is stored during backoffice membership payment
505 $lastFinancialTrxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($contribution['id'], 'DESC');
506 $financialTrxn = $this->callAPISuccessGetSingle(
507 'FinancialTrxn',
508 [
509 'id' => $lastFinancialTrxnId['financialTrxnId'],
510 'return' => ['card_type_id', 'pan_truncation'],
511 ]
512 );
513 $this->assertEquals(1, $financialTrxn['card_type_id']);
514 $this->assertEquals(1111, $financialTrxn['pan_truncation']);
515
516 $this->callAPISuccessGetCount('LineItem', [
517 'entity_id' => $membership['id'],
518 'entity_table' => 'civicrm_membership',
519 'contribution_id' => $contribution['id'],
520 ], 1);
521
522 $this->_checkFinancialRecords([
523 'id' => $contribution['id'],
524 'total_amount' => 1234.56,
525 'financial_account_id' => 2,
526 'payment_instrument_id' => $this->callAPISuccessGetValue('PaymentProcessor', [
527 'id' => $this->_paymentProcessorID,
528 'return' => 'payment_instrument_id',
529 ]),
530 ], 'online');
531 $this->mut->checkMailLog([
532 CRM_Utils_Money::format('1234.56'),
533 'Receipt text',
534 ]);
535 $this->mut->stop();
536 $this->assertEquals([
537 [
538 '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.',
539 'title' => 'Complete',
540 'type' => 'success',
541 'options' => NULL,
542 ],
543 ], CRM_Core_Session::singleton()->getStatus());
544 }
545
546 /**
547 * Test the submit function of the membership form on membership type change.
548 * Check if the related contribuion is also updated if the minimum_fee didn't match
549 */
550 public function testContributionUpdateOnMembershipTypeChange() {
551 // Step 1: Create a Membership via backoffice whose with 50.00 payment
552 $form = $this->getForm();
553 $form->preProcess();
554 $this->mut = new CiviMailUtils($this, TRUE);
555 $this->createLoggedInUser();
556 $priceSet = $this->callAPISuccess('PriceSet', 'Get', ["extends" => "CiviMember"]);
557 $form->set('priceSetId', $priceSet['id']);
558 CRM_Price_BAO_PriceSet::buildPriceSet($form);
559 $params = [
560 'cid' => $this->_individualId,
561 'join_date' => date('Y-m-d'),
562 'start_date' => '',
563 'end_date' => '',
564 // This format reflects the 23 being the organisation & the 25 being the type.
565 'membership_type_id' => [23, $this->membershipTypeAnnualFixedID],
566 'record_contribution' => 1,
567 'total_amount' => 50,
568 'receive_date' => date('Y-m-d', time()) . ' 20:36:00',
569 'payment_instrument_id' => array_search('Check', $this->paymentInstruments),
570 'contribution_status_id' => CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'),
571 //Member dues, see data.xml
572 'financial_type_id' => '2',
573 'payment_processor_id' => $this->_paymentProcessorID,
574 ];
575 $form->_contactID = $this->_individualId;
576 $form->testSubmit($params);
577 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId]);
578 // check the membership status after partial payment, if its Pending
579 $this->assertEquals(array_search('New', CRM_Member_PseudoConstant::membershipStatus()), $membership['status_id']);
580 $contribution = $this->callAPISuccessGetSingle('Contribution', [
581 'contact_id' => $this->_individualId,
582 ]);
583 $this->assertEquals('Completed', $contribution['contribution_status']);
584 $this->assertEquals(50.00, $contribution['total_amount']);
585 $this->assertEquals(50.00, $contribution['net_amount']);
586
587 // Step 2: Change the membership type whose minimum free is less than earlier membership
588 $secondMembershipType = $this->callAPISuccess('membership_type', 'create', [
589 'domain_id' => 1,
590 'name' => "Second Test Membership",
591 'member_of_contact_id' => 23,
592 'duration_unit' => "month",
593 'minimum_fee' => 25,
594 'duration_interval' => 1,
595 'period_type' => "fixed",
596 'fixed_period_start_day' => "101",
597 'fixed_period_rollover_day' => "1231",
598 'relationship_type_id' => 20,
599 'financial_type_id' => 2,
600 ]);
601 Civi::settings()->set('update_contribution_on_membership_type_change', TRUE);
602 $form = $this->getForm();
603 $form->preProcess();
604 $form->_id = $membership['id'];
605 $form->set('priceSetId', $priceSet['id']);
606 CRM_Price_BAO_PriceSet::buildPriceSet($form);
607 $form->_action = CRM_Core_Action::UPDATE;
608 $params = [
609 'cid' => $this->_individualId,
610 'join_date' => date('Y-m-d'),
611 'start_date' => '',
612 'end_date' => '',
613 // This format reflects the 23 being the organisation & the 25 being the type.
614 'membership_type_id' => [23, $secondMembershipType['id']],
615 'record_contribution' => 1,
616 'status_id' => 1,
617 'total_amount' => 25,
618 'receive_date' => date('Y-m-d', time()) . ' 20:36:00',
619 'payment_instrument_id' => array_search('Check', $this->paymentInstruments),
620 //Member dues, see data.xml
621 'financial_type_id' => '2',
622 'payment_processor_id' => $this->_paymentProcessorID,
623 ];
624 $form->_contactID = $this->_individualId;
625 $form->testSubmit($params);
626 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId]);
627 // check the membership status after partial payment, if its Pending
628 $contribution = $this->callAPISuccessGetSingle('Contribution', [
629 'contact_id' => $this->_individualId,
630 ]);
631 $payment = CRM_Contribute_BAO_Contribution::getPaymentInfo($membership['id'], 'membership', FALSE, TRUE);
632 // Check the contribution status on membership type change whose minimum fee was less than earlier memebership
633 $this->assertEquals('Pending refund', $contribution['contribution_status']);
634 // Earlier paid amount
635 $this->assertEquals(50, $payment['paid']);
636 // balance remaning
637 $this->assertEquals(-25, $payment['balance']);
638 }
639
640 /**
641 * Test the submit function of the membership form for partial payment.
642 *
643 * @param string $thousandSeparator
644 * punctuation used to refer to thousands.
645 *
646 * @dataProvider getThousandSeparators
647 *
648 * @throws \CRM_Core_Exception
649 * @throws \CiviCRM_API3_Exception
650 */
651 public function testSubmitPartialPayment($thousandSeparator) {
652 $this->setCurrencySeparators($thousandSeparator);
653 // Step 1: submit a partial payment for a membership via backoffice
654 $form = $this->getForm();
655 $form->preProcess();
656 $this->mut = new CiviMailUtils($this, TRUE);
657 $this->createLoggedInUser();
658 $priceSet = $this->callAPISuccess('PriceSet', 'Get', ["extends" => "CiviMember"]);
659 $form->set('priceSetId', $priceSet['id']);
660 $partiallyPaidAmount = 25;
661 CRM_Price_BAO_PriceSet::buildPriceSet($form);
662 $params = [
663 'cid' => $this->_individualId,
664 'join_date' => date('Y-m-d'),
665 'start_date' => '',
666 'end_date' => '',
667 // This format reflects the 23 being the organisation & the 25 being the type.
668 'membership_type_id' => [23, $this->membershipTypeAnnualFixedID],
669 'receive_date' => date('Y-m-d', time()) . ' 20:36:00',
670 'record_contribution' => 1,
671 'total_amount' => $this->formatMoneyInput($partiallyPaidAmount),
672 'payment_instrument_id' => array_search('Check', $this->paymentInstruments),
673 'contribution_status_id' => CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Partially paid'),
674 //Member dues, see data.xml
675 'financial_type_id' => '2',
676 'payment_processor_id' => $this->_paymentProcessorID,
677 ];
678 $form->_contactID = $this->_individualId;
679 $form->testSubmit($params);
680 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId]);
681 // check the membership status after partial payment, if its Pending
682 $this->assertEquals(array_search('Pending', CRM_Member_PseudoConstant::membershipStatus()), $membership['status_id']);
683 $contribution = $this->callAPISuccessGetSingle('Contribution', [
684 'contact_id' => $this->_individualId,
685 ]);
686 $this->assertEquals('Partially paid', $contribution['contribution_status']);
687 // $this->assertEquals(50.00, $contribution['total_amount']);
688 // $this->assertEquals(25.00, $contribution['net_amount']);
689
690 // Step 2: submit the other half of the partial payment
691 // via AdditionalPayment form to complete the related contribution
692 $form = new CRM_Contribute_Form_AdditionalPayment();
693 $submitParams = [
694 'contribution_id' => $contribution['contribution_id'],
695 'contact_id' => $this->_individualId,
696 'total_amount' => $this->formatMoneyInput($partiallyPaidAmount),
697 'currency' => 'USD',
698 'financial_type_id' => 2,
699 'receive_date' => '2015-04-21 23:27:00',
700 'trxn_date' => '2017-04-11 13:05:11',
701 'payment_processor_id' => 0,
702 'payment_instrument_id' => array_search('Check', $this->paymentInstruments),
703 'check_number' => 'check-12345',
704 ];
705 $form->cid = $this->_individualId;
706 $form->testSubmit($submitParams);
707 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId]);
708 // check the membership status after additional payment, if its changed to 'New'
709 $this->assertEquals(array_search('New', CRM_Member_PseudoConstant::membershipStatus()), $membership['status_id']);
710
711 // check the contribution status and net amount after additional payment
712 $contribution = $this->callAPISuccessGetSingle('Contribution', [
713 'contact_id' => $this->_individualId,
714 ]);
715 $this->assertEquals('Completed', $contribution['contribution_status']);
716 // $this->assertEquals(50.00, $contribution['net_amount']);
717 }
718
719 /**
720 * Test the submit function of the membership form.
721 *
722 * @throws \CRM_Core_Exception
723 */
724 public function testSubmitRecur() {
725 CRM_Core_Session::singleton()->getStatus(TRUE);
726 $pendingVal = $this->callAPISuccessGetValue('OptionValue', [
727 'return' => "id",
728 'option_group_id' => "contribution_status",
729 'label' => "Pending Label**",
730 ]);
731 //Update label for Pending contribution status.
732 $this->callAPISuccess('OptionValue', 'create', [
733 'id' => $pendingVal,
734 'label' => "PendingEdited",
735 ]);
736
737 $form = $this->getForm();
738
739 $this->callAPISuccess('MembershipType', 'create', [
740 'id' => $this->membershipTypeAnnualFixedID,
741 'duration_unit' => 'month',
742 'duration_interval' => 1,
743 'auto_renew' => TRUE,
744 ]);
745 $form->preProcess();
746 $this->createLoggedInUser();
747 $params = $this->getBaseSubmitParams();
748 $form->_mode = 'test';
749 $form->_contactID = $this->_individualId;
750 $form->testSubmit($params);
751 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId]);
752 $this->callAPISuccessGetCount('ContributionRecur', ['contact_id' => $this->_individualId], 1);
753
754 $contribution = $this->callAPISuccess('Contribution', 'get', [
755 'contact_id' => $this->_individualId,
756 'is_test' => TRUE,
757 ]);
758
759 //Check if Membership Payment is recorded.
760 $this->callAPISuccessGetCount('MembershipPayment', [
761 'membership_id' => $membership['id'],
762 'contribution_id' => $contribution['id'],
763 ], 1);
764
765 // CRM-16992.
766 $this->callAPISuccessGetCount('LineItem', [
767 'entity_id' => $membership['id'],
768 'entity_table' => 'civicrm_membership',
769 'contribution_id' => $contribution['id'],
770 ], 1);
771 $this->assertEquals([
772 [
773 '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')) . ' 12:00 AM.',
774 'title' => 'Complete',
775 'type' => 'success',
776 'options' => NULL,
777 ],
778 ], CRM_Core_Session::singleton()->getStatus());
779 }
780
781 /**
782 * CRM-20946: Test the financial entires especially the reversed amount,
783 * after related Contribution is cancelled
784 */
785 public function testFinancialEntiriesOnCancelledContribution() {
786 // Create two memberships for individual $this->_individualId, via a price set in the back end.
787 $this->createTwoMembershipsViaPriceSetInBackEnd($this->_individualId);
788
789 // cancel the related contribution via API
790 $contribution = $this->callAPISuccessGetSingle('Contribution', [
791 'contact_id' => $this->_individualId,
792 'contribution_status_id' => 2,
793 ]);
794 $this->callAPISuccess('Contribution', 'create', [
795 'id' => $contribution['id'],
796 'contribution_status_id' => CRM_Core_PseudoConstant::getKey('CRM_Contribute_DAO_Contribution', 'contribution_status_id', 'Cancelled'),
797 ]);
798
799 // fetch financial_trxn ID of the related contribution
800 $sql = "SELECT financial_trxn_id
801 FROM civicrm_entity_financial_trxn
802 WHERE entity_id = %1 AND entity_table = 'civicrm_contribution'
803 ORDER BY id DESC
804 LIMIT 1
805 ";
806 $financialTrxnID = CRM_Core_DAO::singleValueQuery($sql, [1 => [$contribution['id'], 'Int']]);
807
808 // fetch entity_financial_trxn records and compare their cancelled records
809 $result = $this->callAPISuccess('EntityFinancialTrxn', 'Get', [
810 'financial_trxn_id' => $financialTrxnID,
811 'entity_table' => 'civicrm_financial_item',
812 ]);
813 // compare the reversed amounts of respective memberships after cancelling contribution
814 $cancelledMembershipAmounts = [
815 -20.00,
816 -10.00,
817 ];
818 $count = 0;
819 foreach ($result['values'] as $record) {
820 $this->assertEquals($cancelledMembershipAmounts[$count], $record['amount']);
821 $count++;
822 }
823 }
824
825 /**
826 * Test the submit function of the membership form.
827 */
828 public function testSubmitPayLaterWithBilling() {
829 $form = $this->getForm(NULL);
830 $form->preProcess();
831 $this->createLoggedInUser();
832 $params = [
833 'cid' => $this->_individualId,
834 'join_date' => date('Y-m-d'),
835 'start_date' => '',
836 'end_date' => '',
837 // This format reflects the 23 being the organisation & the 25 being the type.
838 'membership_type_id' => [23, $this->membershipTypeAnnualFixedID],
839 'auto_renew' => '0',
840 'max_related' => '',
841 'num_terms' => '2',
842 'source' => '',
843 'total_amount' => '50.00',
844 //Member dues, see data.xml
845 'financial_type_id' => '2',
846 'soft_credit_type_id' => '',
847 'soft_credit_contact_id' => '',
848 'payment_instrument_id' => 4,
849 'from_email_address' => '"Demonstrators Anonymous" <info@example.org>',
850 'receipt_text_signup' => 'Thank you text',
851 'payment_processor_id' => $this->_paymentProcessorID,
852 'record_contribution' => TRUE,
853 'trxn_id' => 777,
854 'contribution_status_id' => 2,
855 'billing_first_name' => 'Test',
856 'billing_middlename' => 'Last',
857 'billing_street_address-5' => '10 Test St',
858 'billing_city-5' => 'Test',
859 'billing_state_province_id-5' => '1003',
860 'billing_postal_code-5' => '90210',
861 'billing_country_id-5' => '1228',
862 ];
863 $form->_contactID = $this->_individualId;
864
865 $form->testSubmit($params);
866 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId]);
867 $contribution = $this->callAPISuccessGetSingle('Contribution', [
868 'contact_id' => $this->_individualId,
869 'contribution_status_id' => 2,
870 ]);
871 $this->assertEquals($contribution['trxn_id'], 777);
872
873 $this->callAPISuccessGetCount('LineItem', [
874 'entity_id' => $membership['id'],
875 'entity_table' => 'civicrm_membership',
876 'contribution_id' => $contribution['id'],
877 ], 1);
878 $this->callAPISuccessGetSingle('address', [
879 'contact_id' => $this->_individualId,
880 'street_address' => '10 Test St',
881 'postal_code' => 90210,
882 ]);
883 }
884
885 /**
886 * Test if membership is updated to New after contribution
887 * is updated from Partially paid to Completed.
888 */
889 public function testSubmitUpdateMembershipFromPartiallyPaid() {
890 $memStatus = CRM_Member_BAO_Membership::buildOptions('status_id', 'validate');
891
892 //Perform a pay later membership contribution.
893 $this->testSubmitPayLaterWithBilling();
894 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId]);
895 $this->assertEquals($membership['status_id'], array_search('Pending', $memStatus));
896 $contribution = $this->callAPISuccessGetSingle('MembershipPayment', [
897 'membership_id' => $membership['id'],
898 ]);
899
900 //Update contribution to Partially paid.
901 $prevContribution = $this->callAPISuccess('Contribution', 'create', [
902 'id' => $contribution['contribution_id'],
903 'contribution_status_id' => 'Partially paid',
904 ]);
905 $prevContribution = $prevContribution['values'][1];
906
907 //Complete the contribution from offline form.
908 $form = new CRM_Contribute_Form_Contribution();
909 $submitParams = [
910 'id' => $contribution['contribution_id'],
911 'contribution_status_id' => CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'),
912 'price_set_id' => 0,
913 ];
914 $fields = ['total_amount', 'net_amount', 'financial_type_id', 'receive_date', 'contact_id', 'payment_instrument_id'];
915 foreach ($fields as $val) {
916 $submitParams[$val] = $prevContribution[$val];
917 }
918 $form->testSubmit($submitParams, CRM_Core_Action::UPDATE);
919
920 //Check if Membership is updated to New.
921 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId]);
922 $this->assertEquals($membership['status_id'], array_search('New', $memStatus));
923 }
924
925 /**
926 * Test the submit function of the membership form.
927 */
928 public function testSubmitRecurCompleteInstant() {
929 $form = $this->getForm();
930 $mut = new CiviMailUtils($this, TRUE);
931 $processor = Civi\Payment\System::singleton()->getById($this->_paymentProcessorID);
932 $processor->setDoDirectPaymentResult([
933 'payment_status_id' => 1,
934 'trxn_id' => 'kettles boil water',
935 'fee_amount' => .14,
936 ]);
937 $processorDetail = $processor->getPaymentProcessor();
938 $this->callAPISuccess('MembershipType', 'create', [
939 'id' => $this->membershipTypeAnnualFixedID,
940 'duration_unit' => 'month',
941 'duration_interval' => 1,
942 'auto_renew' => TRUE,
943 ]);
944 $form->preProcess();
945 $this->createLoggedInUser();
946 $params = $this->getBaseSubmitParams();
947 $form->_mode = 'test';
948 $form->_contactID = $this->_individualId;
949 $form->testSubmit($params);
950 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId]);
951 $this->callAPISuccessGetCount('ContributionRecur', ['contact_id' => $this->_individualId], 1);
952
953 $contribution = $this->callAPISuccess('Contribution', 'getsingle', [
954 'contact_id' => $this->_individualId,
955 'is_test' => TRUE,
956 ]);
957
958 $this->assertEquals(.14, $contribution['fee_amount']);
959 $this->assertEquals('kettles boil water', $contribution['trxn_id']);
960 $this->assertEquals($processorDetail['payment_instrument_id'], $contribution['payment_instrument_id']);
961
962 $this->callAPISuccessGetCount('LineItem', [
963 'entity_id' => $membership['id'],
964 'entity_table' => 'civicrm_membership',
965 'contribution_id' => $contribution['id'],
966 ], 1);
967 $mut->checkMailLog([
968 '===========================================================
969 Billing Name and Address
970 ===========================================================
971 Test
972 10 Test St
973 Test, AR 90210
974 US',
975 '===========================================================
976 Membership Information
977 ===========================================================
978 Membership Type: AnnualFixed
979 Membership Start Date: ',
980 '===========================================================
981 Credit Card Information
982 ===========================================================
983 Visa
984 ************1111
985 Expires: ',
986 ]);
987 $mut->stop();
988
989 }
990
991 /**
992 * Test membership form with Failed Contribution.
993 */
994 public function testFormWithFailedContribution() {
995 $form = $this->getForm();
996 $form->preProcess();
997 $this->createLoggedInUser();
998 $params = $this->getBaseSubmitParams();
999 unset($params['price_set_id']);
1000 unset($params['credit_card_number']);
1001 unset($params['cvv2']);
1002 unset($params['credit_card_exp_date']);
1003 unset($params['credit_card_type']);
1004 unset($params['send_receipt']);
1005 unset($params['is_recur']);
1006
1007 $params['record_contribution'] = TRUE;
1008 $params['contribution_status_id'] = array_search('Failed', CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name'));
1009 $form->_mode = NULL;
1010 $form->_contactID = $this->_individualId;
1011
1012 $form->testSubmit($params);
1013 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId]);
1014 $form->testSubmit($params);
1015 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId]);
1016 $this->assertEquals($membership['status_id'], array_search('Pending', CRM_Member_PseudoConstant::membershipStatus()));
1017 }
1018
1019 /**
1020 * CRM-20955, CRM-20966:
1021 * Test creating two memberships with inheritance via price set in the back end,
1022 * checking that the correct primary & secondary memberships, contributions, line items
1023 * & membership_payment records are created.
1024 * Uses some data from tests/phpunit/CRM/Member/Form/dataset/data.xml .
1025 */
1026 public function testTwoInheritedMembershipsViaPriceSetInBackend() {
1027 // Create an organization and give it a "Member of" relationship to $this->_individualId.
1028 $orgID = $this->organizationCreate();
1029 $relationship = $this->callAPISuccess('relationship', 'create', [
1030 'contact_id_a' => $this->_individualId,
1031 'contact_id_b' => $orgID,
1032 'relationship_type_id' => 20,
1033 'is_active' => 1,
1034 ]);
1035
1036 // Create two memberships for the organization, via a price set in the back end.
1037 $this->createTwoMembershipsViaPriceSetInBackEnd($orgID);
1038
1039 // Check the primary memberships on the organization.
1040 $orgMembershipResult = $this->callAPISuccess('membership', 'get', [
1041 'contact_id' => $orgID,
1042 ]);
1043 $this->assertEquals(2, $orgMembershipResult['count'], "2 primary memberships should have been created on the organization.");
1044 $primaryMembershipIds = [];
1045 foreach ($orgMembershipResult['values'] as $membership) {
1046 $primaryMembershipIds[] = $membership['id'];
1047 $this->assertTrue(empty($membership['owner_membership_id']), "Membership on the organization has owner_membership_id so is inherited.");
1048 }
1049
1050 // CRM-20955: check that correct inherited memberships were created for the individual,
1051 // for both of the primary memberships.
1052 $individualMembershipResult = $this->callAPISuccess('membership', 'get', [
1053 'contact_id' => $this->_individualId,
1054 ]);
1055 $this->assertEquals(2, $individualMembershipResult['count'], "2 inherited memberships should have been created on the individual.");
1056 foreach ($individualMembershipResult['values'] as $membership) {
1057 $this->assertNotEmpty($membership['owner_membership_id'], "Membership on the individual lacks owner_membership_id so is not inherited.");
1058 $this->assertNotContains($membership['id'], $primaryMembershipIds, "Inherited membership id should not be the id of a primary membership.");
1059 $this->assertContains($membership['owner_membership_id'], $primaryMembershipIds, "Inherited membership owner_membership_id should be the id of a primary membership.");
1060 }
1061
1062 // CRM-20966: check that the correct membership contribution, line items
1063 // & membership_payment records were created for the organization.
1064 $contributionResult = $this->callAPISuccess('contribution', 'get', [
1065 'contact_id' => $orgID,
1066 'sequential' => 1,
1067 'api.line_item.get' => [],
1068 'api.membership_payment.get' => [],
1069 ]);
1070 $this->assertEquals(1, $contributionResult['count'], "One contribution should have been created for the organization's memberships.");
1071
1072 $this->assertEquals(2, $contributionResult['values'][0]['api.line_item.get']['count'], "2 line items should have been created for the organization's memberships.");
1073 foreach ($contributionResult['values'][0]['api.line_item.get']['values'] as $lineItem) {
1074 $this->assertEquals('civicrm_membership', $lineItem['entity_table'], "Membership line item's entity_table should be 'civicrm_membership'.");
1075 $this->assertContains($lineItem['entity_id'], $primaryMembershipIds, "Membership line item's entity_id should be the id of a primary membership.");
1076 }
1077
1078 $this->assertEquals(2, $contributionResult['values'][0]['api.membership_payment.get']['count'], "2 membership payment records should have been created for the organization's memberships.");
1079 foreach ($contributionResult['values'][0]['api.membership_payment.get']['values'] as $membershipPayment) {
1080 $this->assertEquals($contributionResult['values'][0]['id'], $membershipPayment['contribution_id'], "membership payment's contribution ID should be the ID of the organization's membership contribution.");
1081 $this->assertContains($membershipPayment['membership_id'], $primaryMembershipIds, "membership payment's membership ID should be the ID of a primary membership.");
1082 }
1083
1084 // CRM-20966: check that deleting relationship used for inheritance does not delete contribution.
1085 $this->callAPISuccess('relationship', 'delete', [
1086 'id' => $relationship['id'],
1087 ]);
1088
1089 $contributionResultAfterRelationshipDelete = $this->callAPISuccess('contribution', 'get', [
1090 'id' => $contributionResult['values'][0]['id'],
1091 'contact_id' => $orgID,
1092 ]);
1093 $this->assertEquals(1, $contributionResultAfterRelationshipDelete['count'], "Contribution has been wrongly deleted.");
1094 }
1095
1096 /**
1097 * dev/core/issues/860:
1098 * Test creating two memberships via price set in the back end with a discount,
1099 * checking that the line items have correct amounts.
1100 */
1101 public function testTwoMembershipsViaPriceSetInBackendWithDiscount() {
1102 // Register buildAmount hook to apply discount.
1103 $this->hookClass->setHook('civicrm_buildAmount', [$this, 'buildAmountMembershipDiscount']);
1104
1105 // Create two memberships for individual $this->_individualId, via a price set in the back end.
1106 $this->createTwoMembershipsViaPriceSetInBackEnd($this->_individualId);
1107 $contribution = $this->callAPISuccessGetSingle('Contribution', [
1108 'contact_id' => $this->_individualId,
1109 ]);
1110 // Note: we can't check for the contribution total being discounted, because the total is set
1111 // when the contribution is created via $form->testSubmit(), but buildAmount isn't called
1112 // until testSubmit() runs. Fixing that might involve making testSubmit() more sophisticated,
1113 // or just hacking total_amount for this case.
1114
1115 $lineItemResult = $this->callAPISuccess('LineItem', 'get', [
1116 'contribution_id' => $contribution['id'],
1117 ]);
1118 $this->assertEquals(2, $lineItemResult['count']);
1119 $discountedItems = 0;
1120 foreach ($lineItemResult['values'] as $lineItem) {
1121 if (CRM_Utils_String::startsWith($lineItem['label'], 'Long Haired Goat')) {
1122 $this->assertEquals(15.0, $lineItem['line_total']);
1123 $this->assertEquals('Long Haired Goat - one leg free!', $lineItem['label']);
1124 $discountedItems++;
1125 }
1126 }
1127 $this->assertEquals(1, $discountedItems);
1128 }
1129
1130 /**
1131 * Implements hook_civicrm_buildAmount() for testTwoMembershipsViaPriceSetInBackendWithDiscount().
1132 */
1133 public function buildAmountMembershipDiscount($pageType, &$form, &$amount) {
1134 foreach ($amount as $id => $priceField) {
1135 if (is_array($priceField['options'])) {
1136 foreach ($priceField['options'] as $optionId => $option) {
1137 if ($option['membership_type_id'] == 15) {
1138 // Long Haired Goat membership discount.
1139 $amount[$id]['options'][$optionId]['amount'] = $option['amount'] * 0.75;
1140 $amount[$id]['options'][$optionId]['label'] = $option['label'] . ' - one leg free!';
1141 }
1142 }
1143 }
1144 }
1145 }
1146
1147 /**
1148 * Get a membership form object.
1149 *
1150 * We need to instantiate the form to run preprocess, which means we have to trick it about the request method.
1151 *
1152 * @return \CRM_Member_Form_Membership
1153 */
1154 protected function getForm() {
1155 if (isset($_REQUEST['cid'])) {
1156 unset($_REQUEST['cid']);
1157 }
1158 $form = new CRM_Member_Form_Membership();
1159 $_SERVER['REQUEST_METHOD'] = 'GET';
1160 $form->controller = new CRM_Core_Controller();
1161 return $form;
1162 }
1163
1164 /**
1165 * @return array
1166 */
1167 protected function getBaseSubmitParams() {
1168 $params = [
1169 'cid' => $this->_individualId,
1170 'price_set_id' => 0,
1171 'join_date' => date('Y-m-d'),
1172 'start_date' => '',
1173 'end_date' => '',
1174 'campaign_id' => '',
1175 // This format reflects the 23 being the organisation & the 25 being the type.
1176 'membership_type_id' => [23, $this->membershipTypeAnnualFixedID],
1177 'auto_renew' => '1',
1178 'is_recur' => 1,
1179 'max_related' => 0,
1180 'num_terms' => '1',
1181 'source' => '',
1182 'total_amount' => '77.00',
1183 //Member dues, see data.xml
1184 'financial_type_id' => '2',
1185 'soft_credit_type_id' => 11,
1186 'soft_credit_contact_id' => '',
1187 'from_email_address' => '"Demonstrators Anonymous" <info@example.org>',
1188 'receipt_text' => 'Thank you text',
1189 'payment_processor_id' => $this->_paymentProcessorID,
1190 'credit_card_number' => '4111111111111111',
1191 'cvv2' => '123',
1192 'credit_card_exp_date' => [
1193 'M' => '9',
1194 // TODO: Future proof
1195 'Y' => '2019',
1196 ],
1197 'credit_card_type' => 'Visa',
1198 'billing_first_name' => 'Test',
1199 'billing_middlename' => 'Last',
1200 'billing_street_address-5' => '10 Test St',
1201 'billing_city-5' => 'Test',
1202 'billing_state_province_id-5' => '1003',
1203 'billing_postal_code-5' => '90210',
1204 'billing_country_id-5' => '1228',
1205 'send_receipt' => 1,
1206 ];
1207 return $params;
1208 }
1209
1210 /**
1211 * Scenario builder:
1212 * create two memberships for the same individual, via a price set in the back end.
1213 *
1214 * @param int $contactId Id of contact on which the memberships will be created.
1215 */
1216 protected function createTwoMembershipsViaPriceSetInBackEnd($contactId) {
1217 $form = $this->getForm(NULL);
1218 $form->preProcess();
1219 $this->createLoggedInUser();
1220
1221 // create a price-set of price-field of type checkbox and each price-option corresponds to a membership type
1222 $priceSet = $this->callAPISuccess('price_set', 'create', [
1223 'is_quick_config' => 0,
1224 'extends' => 'CiviMember',
1225 'financial_type_id' => 1,
1226 'title' => 'my Page',
1227 ]);
1228 $priceSetID = $priceSet['id'];
1229 // create respective checkbox price-field
1230 $priceField = $this->callAPISuccess('price_field', 'create', [
1231 'price_set_id' => $priceSetID,
1232 'label' => 'Memberships',
1233 'html_type' => 'Checkbox',
1234 ]);
1235 $priceFieldID = $priceField['id'];
1236 // create two price options, each represent a membership type of amount 20 and 10 respectively
1237 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
1238 'price_set_id' => $priceSetID,
1239 'price_field_id' => $priceField['id'],
1240 'label' => 'Long Haired Goat',
1241 'amount' => 20,
1242 'financial_type_id' => 'Donation',
1243 'membership_type_id' => 15,
1244 'membership_num_terms' => 1,
1245 ]);
1246 $pfvIDs = [$priceFieldValue['id'] => 1];
1247 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
1248 'price_set_id' => $priceSetID,
1249 'price_field_id' => $priceField['id'],
1250 'label' => 'Shoe-eating Goat',
1251 'amount' => 10,
1252 'financial_type_id' => 'Donation',
1253 'membership_type_id' => 35,
1254 'membership_num_terms' => 2,
1255 ]);
1256 $pfvIDs[$priceFieldValue['id']] = 1;
1257
1258 // register for both of these memberships via backoffice membership form submission
1259 $params = [
1260 'cid' => $contactId,
1261 'join_date' => date('Y-m-d'),
1262 'start_date' => '',
1263 'end_date' => '',
1264 // This format reflects the 23 being the organisation & the 25 being the type.
1265 "price_$priceFieldID" => $pfvIDs,
1266 "price_set_id" => $priceSetID,
1267 'membership_type_id' => [1 => 0],
1268 'auto_renew' => '0',
1269 'max_related' => '',
1270 'num_terms' => '2',
1271 'source' => '',
1272 'total_amount' => '30.00',
1273 //Member dues, see data.xml
1274 'financial_type_id' => '2',
1275 'soft_credit_type_id' => '',
1276 'soft_credit_contact_id' => '',
1277 'payment_instrument_id' => 4,
1278 'from_email_address' => '"Demonstrators Anonymous" <info@example.org>',
1279 'receipt_text_signup' => 'Thank you text',
1280 'payment_processor_id' => $this->_paymentProcessorID,
1281 'record_contribution' => TRUE,
1282 'trxn_id' => 777,
1283 'contribution_status_id' => CRM_Core_PseudoConstant::getKey('CRM_Contribute_DAO_Contribution', 'contribution_status_id', 'Pending'),
1284 'billing_first_name' => 'Test',
1285 'billing_middlename' => 'Last',
1286 'billing_street_address-5' => '10 Test St',
1287 'billing_city-5' => 'Test',
1288 'billing_state_province_id-5' => '1003',
1289 'billing_postal_code-5' => '90210',
1290 'billing_country_id-5' => '1228',
1291 ];
1292 $form->testSubmit($params);
1293 }
1294
1295 /**
1296 * Test membership status overrides when contribution is cancelled.
1297 */
1298 public function testContributionFormStatusUpdate() {
1299 $form = new CRM_Contribute_Form_Contribution();
1300
1301 //Create a membership with status = 'New'.
1302 $this->_individualId = $this->createLoggedInUser();
1303 $memParams = [
1304 'contact_id' => $this->_individualId,
1305 'membership_type_id' => $this->membershipTypeAnnualFixedID,
1306 'status_id' => array_search('New', CRM_Member_PseudoConstant::membershipStatus()),
1307 ];
1308 $cancelledStatusId = $this->callAPISuccessGetValue('OptionValue', [
1309 'return' => "value",
1310 'option_group_id' => "contribution_status",
1311 'name' => "Cancelled",
1312 ]);
1313 $params = [
1314 'total_amount' => 50,
1315 'financial_type_id' => 2,
1316 'contact_id' => $this->_individualId,
1317 'payment_instrument_id' => array_search('Check', $this->paymentInstruments),
1318 'contribution_status_id' => $cancelledStatusId,
1319 ];
1320 $membershipId = $this->contactMembershipCreate($memParams);
1321
1322 $contriParams = [
1323 'membership_id' => $membershipId,
1324 'total_amount' => 50,
1325 'financial_type_id' => 2,
1326 'contact_id' => $this->_individualId,
1327 ];
1328 $contribution = CRM_Member_BAO_Membership::recordMembershipContribution($contriParams);
1329
1330 //Update Contribution to Cancelled.
1331 $form->_id = $params['id'] = $contribution->id;
1332 $form->_mode = NULL;
1333 $form->_contactID = $this->_individualId;
1334 $form->testSubmit($params, CRM_Core_Action::UPDATE);
1335 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId]);
1336
1337 //Assert membership status overrides when the contribution cancelled.
1338 $this->assertEquals($membership['is_override'], TRUE);
1339 $this->assertEquals($membership['status_id'], $this->callAPISuccessGetValue('MembershipStatus', [
1340 'return' => "id",
1341 'name' => "Cancelled",
1342 ]));
1343 }
1344
1345 /**
1346 * CRM-21656: Test the submit function of the membership form if Sales Tax is enabled.
1347 * This test simulates what happens when one hits Edit on a Contribution that has both LineItems and Sales Tax components
1348 * Without making any Edits -> check that the LineItem data remain the same
1349 * In addition (a data-integrity check) -> check that the LineItem data add up to the data at the Contribution level
1350 */
1351 public function testLineItemAmountOnSalesTax() {
1352 $this->enableTaxAndInvoicing();
1353 $this->relationForFinancialTypeWithFinancialAccount(2);
1354 $form = $this->getForm();
1355 $form->preProcess();
1356 $this->mut = new CiviMailUtils($this, TRUE);
1357 $this->createLoggedInUser();
1358 $priceSet = $this->callAPISuccess('PriceSet', 'Get', ["extends" => "CiviMember"]);
1359 $form->set('priceSetId', $priceSet['id']);
1360 // we are simulating the creation of a Price Set in Administer -> CiviContribute -> Manage Price Sets so set is_quick_config = 0
1361 $this->callAPISuccess('PriceSet', 'Create', ["id" => $priceSet['id'], 'is_quick_config' => 0]);
1362 // clean the price options static variable to repopulate the options, in order to fetch tax information
1363 \Civi::$statics['CRM_Price_BAO_PriceField']['priceOptions'] = NULL;
1364 CRM_Price_BAO_PriceSet::buildPriceSet($form);
1365 // rebuild the price set form variable to include the tax information against each price options
1366 $form->_priceSet = current(CRM_Price_BAO_PriceSet::getSetDetail($priceSet['id']));
1367 $params = [
1368 'cid' => $this->_individualId,
1369 'join_date' => date('Y-m-d'),
1370 'start_date' => '',
1371 'end_date' => '',
1372 // This format reflects the 23 being the organisation & the 25 being the type.
1373 'membership_type_id' => [23, $this->membershipTypeAnnualFixedID],
1374 'record_contribution' => 1,
1375 'total_amount' => 55,
1376 'receive_date' => date('Y-m-d', time()) . ' 20:36:00',
1377 'payment_instrument_id' => array_search('Check', $this->paymentInstruments),
1378 'contribution_status_id' => CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'),
1379 //Member dues, see data.xml
1380 'financial_type_id' => 2,
1381 'payment_processor_id' => $this->_paymentProcessorID,
1382 ];
1383 $form->_contactID = $this->_individualId;
1384 $form->testSubmit($params);
1385
1386 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId]);
1387 $lineItem = $this->callAPISuccessGetSingle('LineItem', ['entity_id' => $membership['id'], 'entity_table' => 'civicrm_membership']);
1388 $this->assertEquals(1, $lineItem['qty']);
1389 $this->assertEquals(50.00, $lineItem['unit_price']);
1390 $this->assertEquals(50.00, $lineItem['line_total']);
1391 $this->assertEquals(5.00, $lineItem['tax_amount']);
1392
1393 // Simply save the 'Edit Contribution' form
1394 $form = new CRM_Contribute_Form_Contribution();
1395 $form->_context = 'membership';
1396 $form->_values = $this->callAPISuccessGetSingle('Contribution', ['id' => $lineItem['contribution_id'], 'return' => ['total_amount', 'net_amount', 'fee_amount', 'tax_amount']]);
1397 $form->testSubmit([
1398 'contact_id' => $this->_individualId,
1399 'id' => $lineItem['contribution_id'],
1400 'financial_type_id' => 2,
1401 'contribution_status_id' => CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'),
1402 ],
1403 CRM_Core_Action::UPDATE);
1404
1405 // ensure that the LineItem data remain the same
1406 $lineItem = $this->callAPISuccessGetSingle('LineItem', ['entity_id' => $membership['id'], 'entity_table' => 'civicrm_membership']);
1407 $this->assertEquals(1, $lineItem['qty']);
1408 $this->assertEquals(50.00, $lineItem['unit_price']);
1409 $this->assertEquals(50.00, $lineItem['line_total']);
1410 $this->assertEquals(5.00, $lineItem['tax_amount']);
1411
1412 // ensure that the LineItem data add up to the data at the Contribution level
1413 $contribution = $this->callAPISuccessGetSingle('Contribution',
1414 [
1415 'contribution_id' => 1,
1416 'return' => ['tax_amount', 'total_amount'],
1417 ]
1418 );
1419 $this->assertEquals($contribution['total_amount'], $lineItem['line_total'] + $lineItem['tax_amount']);
1420 $this->assertEquals($contribution['tax_amount'], $lineItem['tax_amount']);
1421
1422 $financialItems = $this->callAPISuccess('FinancialItem', 'get', []);
1423 $financialItems_sum = 0;
1424 foreach ($financialItems['values'] as $financialItem) {
1425 $financialItems_sum += $financialItem['amount'];
1426 }
1427 $this->assertEquals($contribution['total_amount'], $financialItems_sum);
1428
1429 // reset the price options static variable so not leave any dummy data, that might hamper other unit tests
1430 \Civi::$statics['CRM_Price_BAO_PriceField']['priceOptions'] = NULL;
1431 $this->disableTaxAndInvoicing();
1432 }
1433
1434 /**
1435 * Test that membership end_date is correct for multiple terms for pending contribution
1436 *
1437 * @throws CiviCRM_API3_Exception
1438 * @throws \CRM_Core_Exception
1439 */
1440 public function testCreatePendingWithMultipleTerms() {
1441 CRM_Core_Session::singleton()->getStatus(TRUE);
1442 $form = $this->getForm();
1443 $form->preProcess();
1444 $this->mut = new CiviMailUtils($this, TRUE);
1445 $this->createLoggedInUser();
1446 $membershipTypeAnnualRolling = $this->callAPISuccess('membership_type', 'create', [
1447 'domain_id' => 1,
1448 'name' => "AnnualRollingNew",
1449 'member_of_contact_id' => 23,
1450 'duration_unit' => "year",
1451 'minimum_fee' => 50,
1452 'duration_interval' => 1,
1453 'period_type' => "rolling",
1454 'relationship_type_id' => 20,
1455 'relationship_direction' => 'b_a',
1456 'financial_type_id' => 2,
1457 ]);
1458 $params = [
1459 'cid' => $this->_individualId,
1460 'join_date' => date('Y-m-d'),
1461 'start_date' => '',
1462 'end_date' => '',
1463 'contribution_status_id' => CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending'),
1464 'membership_type_id' => [23, $membershipTypeAnnualRolling['id']],
1465 'max_related' => '',
1466 'num_terms' => '3',
1467 'record_contribution' => 1,
1468 'source' => '',
1469 'total_amount' => $this->formatMoneyInput(150.00),
1470 'financial_type_id' => '2',
1471 'soft_credit_type_id' => '11',
1472 'soft_credit_contact_id' => '',
1473 'from_email_address' => '"Demonstrators Anonymous" <info@example.org>',
1474 'receipt_text' => '',
1475 ];
1476 $form->_contactID = $this->_individualId;
1477 $form->testSubmit($params);
1478 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId]);
1479 $contribution = $this->callAPISuccess('Contribution', 'get', [
1480 'contact_id' => $this->_individualId,
1481 ]);
1482 $endDate = (new DateTime(date('Y-m-d')))->modify("+3 years")->modify("-1 day");
1483 $endDate = $endDate->format("Y-m-d");
1484
1485 $this->assertEquals($endDate, $membership['end_date'], 'Membership end date should be ' . $endDate);
1486 $this->assertEquals(1, count($contribution['values']), 'Pending contribution should be created.');
1487 $contribution = $contribution['values'][$contribution['id']];
1488 $additionalPaymentForm = new CRM_Contribute_Form_AdditionalPayment();
1489 $additionalPaymentForm->testSubmit([
1490 'total_amount' => 150.00,
1491 'trxn_date' => date("Y-m-d H:i:s"),
1492 'payment_instrument_id' => array_search('Check', $this->paymentInstruments),
1493 'check_number' => 'check-12345',
1494 'trxn_id' => '',
1495 'currency' => 'USD',
1496 'fee_amount' => '',
1497 'financial_type_id' => 1,
1498 'net_amount' => '',
1499 'payment_processor_id' => 0,
1500 'contact_id' => $this->_individualId,
1501 'contribution_id' => $contribution['id'],
1502 ]);
1503 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_individualId]);
1504 $contribution = $this->callAPISuccess('Contribution', 'get', [
1505 'contact_id' => $this->_individualId,
1506 'contribution_status_id' => CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'),
1507 ]);
1508 $this->assertEquals($endDate, $membership['end_date'], 'Membership end date should be same (' . $endDate . ') after payment');
1509 $this->assertEquals(1, count($contribution['values']), 'Completed contribution should be fetched.');
1510 }
1511
1512 }