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 * Test APIv3 civicrm_contribute_recur* functions
15 * @package CiviCRM_APIv3
16 * @subpackage API_Contribution
19 class api_v3_ContributionPageTest
extends CiviUnitTestCase
{
20 protected $testAmount = 34567;
23 protected $contactIds = [];
24 protected $_entity = 'contribution_page';
25 protected $contribution_result = NULL;
26 protected $_priceSetParams = [];
27 protected $_membershipBlockAmount = 2;
29 * Payment processor details.
32 protected $_paymentProcessor = [];
43 public $DBResetRequired = TRUE;
48 * @throws \CRM_Core_Exception
50 public function setUp() {
52 $this->contactIds
[] = $this->individualCreate();
54 'title' => 'Test Contribution Page',
55 'financial_type_id' => 1,
57 'goal_amount' => $this->testAmount
,
59 'pay_later_text' => 'Send check',
60 'is_monetary' => TRUE,
61 'is_email_receipt' => TRUE,
62 'receipt_from_email' => 'yourconscience@donate.com',
63 'receipt_from_name' => 'Ego Freud',
66 $this->_priceSetParams
= [
67 'is_quick_config' => 1,
68 'extends' => 'CiviContribute',
69 'financial_type_id' => 'Donation',
75 * Tear down after test.
77 * @throws \CRM_Core_Exception
78 * @throws \CiviCRM_API3_Exception
80 public function tearDown() {
81 foreach ($this->contactIds
as $id) {
82 $this->callAPISuccess('contact', 'delete', ['id' => $id]);
84 $this->quickCleanUpFinancialEntities();
89 * Test creating a contribution page.
93 * @dataProvider versionThreeAndFour
94 * @throws \CRM_Core_Exception
96 public function testCreateContributionPage($version) {
97 $this->_apiversion
= $version;
98 $result = $this->callAPIAndDocument($this->_entity
, 'create', $this->params
, __FUNCTION__
, __FILE__
);
99 $this->assertEquals(1, $result['count']);
100 $this->assertNotNull($result['values'][$result['id']]['id']);
101 $this->getAndCheck($this->params
, $result['id'], $this->_entity
);
105 * Test getting a contribution page.
107 * @param int $version
109 * @dataProvider versionThreeAndFour
110 * @throws \CRM_Core_Exception
112 public function testGetBasicContributionPage($version) {
113 $this->_apiversion
= $version;
114 $createResult = $this->callAPISuccess($this->_entity
, 'create', $this->params
);
115 $this->id
= $createResult['id'];
118 'financial_type_id' => 1,
120 $getResult = $this->callAPIAndDocument($this->_entity
, 'get', $getParams, __FUNCTION__
, __FILE__
);
121 $this->assertEquals(1, $getResult['count']);
125 * Test get with amount as a parameter.
127 * @throws \CRM_Core_Exception
129 public function testGetContributionPageByAmount() {
130 $createResult = $this->callAPISuccess($this->_entity
, 'create', $this->params
);
131 $this->id
= $createResult['id'];
134 'amount' => '' . $this->testAmount
,
136 'financial_type_id' => 1,
138 $getResult = $this->callAPISuccess($this->_entity
, 'get', $getParams);
139 $this->assertEquals(1, $getResult['count']);
143 * Test page deletion.
145 * @param int $version
147 * @dataProvider versionThreeAndFour
148 * @throws \CRM_Core_Exception
150 public function testDeleteContributionPage($version) {
151 $this->_apiversion
= $version;
152 $createResult = $this->callAPISuccess($this->_entity
, 'create', $this->params
);
153 $deleteParams = ['id' => $createResult['id']];
154 $this->callAPIAndDocument($this->_entity
, 'delete', $deleteParams, __FUNCTION__
, __FILE__
);
155 $checkDeleted = $this->callAPISuccess($this->_entity
, 'get', []);
156 $this->assertEquals(0, $checkDeleted['count']);
160 * Test getfields function.
162 * @throws \CRM_Core_Exception
164 public function testGetFieldsContributionPage() {
165 $result = $this->callAPISuccess($this->_entity
, 'getfields', ['action' => 'create']);
166 $this->assertEquals(12, $result['values']['start_date']['type']);
170 * Test form submission with basic price set.
172 * @throws \CRM_Core_Exception
174 public function testSubmit() {
175 $this->setUpContributionPage();
176 $submitParams = $this->getBasicSubmitParams();
178 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
179 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['contribution_page_id' => $this->_ids
['contribution_page']]);
180 //assert non-deductible amount
181 $this->assertEquals(5.00, $contribution['non_deductible_amount']);
185 * Test form submission with basic price set.
187 public function testSubmitZeroDollar() {
188 $this->setUpContributionPage();
189 $priceFieldID = reset($this->_ids
['price_field']);
191 'price_' . $priceFieldID => $this->_ids
['price_field_value']['cheapskate'],
192 'id' => (int) $this->_ids
['contribution_page'],
194 'priceSetId' => $this->_ids
['price_set'][0],
195 'payment_processor_id' => '',
198 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
199 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['contribution_page_id' => $this->_ids
['contribution_page']]);
201 $this->assertEquals($this->formatMoneyInput(0), $contribution['non_deductible_amount']);
202 $this->assertEquals($this->formatMoneyInput(0), $contribution['total_amount']);
206 * Test form submission with billing first & last name where the contact does NOT
207 * otherwise have one.
209 * @throws \CRM_Core_Exception
211 public function testSubmitNewBillingNameData() {
212 $this->setUpContributionPage();
213 $contact = $this->callAPISuccess('Contact', 'create', ['contact_type' => 'Individual', 'email' => 'wonderwoman@amazon.com']);
214 $priceFieldID = reset($this->_ids
['price_field']);
215 $priceFieldValueID = reset($this->_ids
['price_field_value']);
217 'price_' . $priceFieldID => $priceFieldValueID,
218 'id' => (int) $this->_ids
['contribution_page'],
220 'billing_first_name' => 'Wonder',
221 'billing_last_name' => 'Woman',
222 'contactID' => $contact['id'],
223 'email' => 'wonderwoman@amazon.com',
226 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
227 $contact = $this->callAPISuccess('Contact', 'get', [
228 'id' => $contact['id'],
236 $this->assertEquals([
237 'first_name' => 'Wonder',
238 'last_name' => 'Woman',
239 'display_name' => 'Wonder Woman',
240 'sort_name' => 'Woman, Wonder',
241 'id' => $contact['id'],
242 'contact_id' => $contact['id'],
243 ], $contact['values'][$contact['id']]);
248 * Test form submission with billing first & last name where the contact does
249 * otherwise have one and should not be overwritten.
251 public function testSubmitNewBillingNameDoNotOverwrite() {
252 $this->setUpContributionPage();
253 $contact = $this->callAPISuccess('Contact', 'create', [
254 'contact_type' => 'Individual',
255 'email' => 'wonderwoman@amazon.com',
256 'first_name' => 'Super',
257 'last_name' => 'Boy',
259 $priceFieldID = reset($this->_ids
['price_field']);
260 $priceFieldValueID = reset($this->_ids
['price_field_value']);
262 'price_' . $priceFieldID => $priceFieldValueID,
263 'id' => (int) $this->_ids
['contribution_page'],
265 'billing_first_name' => 'Wonder',
266 'billing_last_name' => 'Woman',
267 'contactID' => $contact['id'],
268 'email' => 'wonderwoman@amazon.com',
271 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
272 $contact = $this->callAPISuccess('Contact', 'get', [
273 'id' => $contact['id'],
281 $this->assertEquals([
282 'first_name' => 'Super',
283 'last_name' => 'Boy',
284 'display_name' => 'Super Boy',
285 'sort_name' => 'Boy, Super',
286 'id' => $contact['id'],
287 'contact_id' => $contact['id'],
288 ], $contact['values'][$contact['id']]);
293 * Test process with instant payment when more than one configured for the page.
297 * @throws \CiviCRM_API3_Exception
298 * @throws \CRM_Core_Exception
300 public function testSubmitRecurMultiProcessorInstantPayment() {
301 $this->setUpContributionPage();
302 $this->setupPaymentProcessor();
303 $paymentProcessor2ID = $this->paymentProcessorCreate([
304 'payment_processor_type_id' => 'Dummy',
305 'name' => 'processor 2',
306 'class_name' => 'Payment_Dummy',
309 $dummyPP = Civi\Payment\System
::singleton()->getById($paymentProcessor2ID);
310 $dummyPP->setDoDirectPaymentResult([
311 'payment_status_id' => 1,
312 'trxn_id' => 'create_first_success',
315 $processor = $dummyPP->getPaymentProcessor();
316 $this->callAPISuccess('ContributionPage', 'create', [
317 'id' => $this->_ids
['contribution_page'],
318 'payment_processor' => [$paymentProcessor2ID, $this->_ids
['payment_processor']],
321 $priceFieldID = reset($this->_ids
['price_field']);
322 $priceFieldValueID = reset($this->_ids
['price_field_value']);
324 'price_' . $priceFieldID => $priceFieldValueID,
325 'id' => (int) $this->_ids
['contribution_page'],
328 'frequency_interval' => 1,
329 'frequency_unit' => 'month',
330 'payment_processor_id' => $paymentProcessor2ID,
333 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
334 $contribution = $this->callAPISuccess('contribution', 'getsingle', [
335 'contribution_page_id' => $this->_ids
['contribution_page'],
336 'contribution_status_id' => 1,
338 $this->assertEquals('create_first_success', $contribution['trxn_id']);
339 $this->assertEquals(10, $contribution['total_amount']);
340 $this->assertEquals(.85, $contribution['fee_amount']);
341 $this->assertEquals(9.15, $contribution['net_amount']);
342 $this->_checkFinancialRecords([
343 'id' => $contribution['id'],
344 'total_amount' => $contribution['total_amount'],
345 'payment_instrument_id' => $processor['payment_instrument_id'],
350 * Test submit with a membership block in place.
352 * @throws \CRM_Core_Exception
354 public function testSubmitMembershipBlockNotSeparatePayment() {
355 $this->setUpMembershipContributionPage();
357 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
358 'id' => (int) $this->_ids
['contribution_page'],
360 'billing_first_name' => 'Billy',
361 'billing_middle_name' => 'Goat',
362 'billing_last_name' => 'Gruff',
363 'selectMembership' => $this->_ids
['membership_type'],
366 $this->callAPIAndDocument('ContributionPage', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
367 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['contribution_page_id' => $this->_ids
['contribution_page']]);
368 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', ['contribution_id' => $contribution['id']]);
369 $this->callAPISuccessGetSingle('LineItem', ['contribution_id' => $contribution['id'], 'entity_id' => $membershipPayment['id']]);
373 * Test submit with a membership block in place works with renewal.
375 * @throws \CRM_Core_Exception
377 public function testSubmitMembershipBlockNotSeparatePaymentProcessorInstantRenew() {
378 $this->setUpMembershipContributionPage();
379 $dummyPP = Civi\Payment\System
::singleton()->getByProcessor($this->_paymentProcessor
);
380 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1]);
382 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
383 'id' => (int) $this->_ids
['contribution_page'],
385 'billing_first_name' => 'Billy',
386 'billing_middle_name' => 'Goat',
387 'billing_last_name' => 'Gruff',
388 'selectMembership' => $this->_ids
['membership_type'],
389 'payment_processor_id' => 1,
390 'credit_card_number' => '4111111111111111',
391 'credit_card_type' => 'Visa',
392 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
396 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
397 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['contribution_page_id' => $this->_ids
['contribution_page']]);
398 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', ['contribution_id' => $contribution['id']]);
399 $this->callAPISuccessGetCount('LineItem', [
400 'entity_table' => 'civicrm_membership',
401 'entity_id' => $membershipPayment['id'],
404 $submitParams['contact_id'] = $contribution['contact_id'];
406 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
407 $this->callAPISuccessGetCount('LineItem', [
408 'entity_table' => 'civicrm_membership',
409 'entity_id' => $membershipPayment['id'],
411 $membership = $this->callAPISuccessGetSingle('Membership', [
412 'id' => $membershipPayment['membership_id'],
413 'return' => ['end_date', 'join_date', 'start_date'],
415 $this->assertEquals(date('Y-m-d'), $membership['start_date']);
416 $this->assertEquals(date('Y-m-d'), $membership['join_date']);
417 $this->assertEquals(date('Y-m-d', strtotime('+ 2 year - 1 day')), $membership['end_date']);
421 * Test submit with a membership block in place.
423 * @throws \CRM_Core_Exception
425 public function testSubmitMembershipBlockNotSeparatePaymentWithEmail() {
426 $mut = new CiviMailUtils($this, TRUE);
427 $this->setUpMembershipContributionPage();
428 $this->addProfile('supporter_profile', $this->_ids
['contribution_page']);
431 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
432 'id' => (int) $this->_ids
['contribution_page'],
434 'billing_first_name' => 'Billy',
435 'billing_middle_name' => 'Goat',
436 'billing_last_name' => 'Gruff',
437 'selectMembership' => $this->_ids
['membership_type'],
438 'email-Primary' => 'billy-goat@the-bridge.net',
439 'payment_processor_id' => $this->_paymentProcessor
['id'],
440 'credit_card_number' => '4111111111111111',
441 'credit_card_type' => 'Visa',
442 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
446 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
447 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['contribution_page_id' => $this->_ids
['contribution_page']]);
448 $this->callAPISuccess('membership_payment', 'getsingle', ['contribution_id' => $contribution['id']]);
450 'Membership Type: General',
451 'Test Frontend title',
454 $mut->clearMessages();
458 * Test submit with a membership block in place.
462 public function testSubmitMembershipBlockNotSeparatePaymentZeroDollarsWithEmail() {
463 $mut = new CiviMailUtils($this, TRUE);
464 $this->_ids
['membership_type'] = [$this->membershipTypeCreate(['minimum_fee' => 0])];
465 $this->setUpMembershipContributionPage();
466 $this->addProfile('supporter_profile', $this->_ids
['contribution_page']);
469 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
470 'id' => (int) $this->_ids
['contribution_page'],
472 'billing_first_name' => 'Billy',
473 'billing_middle_name' => 'Goat',
474 'billing_last_name' => 'Gruffier',
475 'selectMembership' => $this->_ids
['membership_type'],
476 'email-Primary' => 'billy-goat@the-new-bridge.net',
477 'payment_processor_id' => $this->params
['payment_processor_id'],
480 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
481 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['contribution_page_id' => $this->_ids
['contribution_page']]);
482 $this->callAPISuccess('membership_payment', 'getsingle', ['contribution_id' => $contribution['id']]);
483 //Assert only one mail is being sent.
484 $msgs = $mut->getAllMessages();
485 $this->assertCount(1, $msgs);
488 'Membership Type: General',
494 $mut->clearMessages();
498 * Test submit with a pay later abnd check line item in mails.
500 * @throws \CRM_Core_Exception
502 public function testSubmitMembershipBlockIsSeparatePaymentPayLaterWithEmail() {
503 $mut = new CiviMailUtils($this, TRUE);
504 $this->setUpMembershipContributionPage();
506 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
507 'id' => (int) $this->_ids
['contribution_page'],
509 'billing_first_name' => 'Billy',
510 'billing_middle_name' => 'Goat',
511 'billing_last_name' => 'Gruff',
513 'selectMembership' => $this->_ids
['membership_type'],
514 'email-Primary' => 'billy-goat@the-bridge.net',
517 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
518 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['contribution_page_id' => $this->_ids
['contribution_page']]);
519 $this->callAPISuccess('membership_payment', 'getsingle', ['contribution_id' => $contribution['id']]);
521 'Membership Amount -... $ 2.00',
524 $mut->clearMessages();
528 * Test submit with a membership block in place.
530 * @throws \CRM_Core_Exception
532 public function testSubmitMembershipBlockIsSeparatePayment() {
533 $this->setUpMembershipContributionPage(TRUE);
534 $this->_ids
['membership_type'] = [$this->membershipTypeCreate(['minimum_fee' => 2])];
536 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
537 'id' => (int) $this->_ids
['contribution_page'],
539 'billing_first_name' => 'Billy',
540 'billing_middle_name' => 'Goat',
541 'billing_last_name' => 'Gruff',
542 'selectMembership' => $this->_ids
['membership_type'],
545 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
546 $contributions = $this->callAPISuccess('contribution', 'get', ['contribution_page_id' => $this->_ids
['contribution_page']]);
547 $this->assertCount(2, $contributions['values']);
548 $lines = $this->callAPISuccess('LineItem', 'get', ['sequential' => 1]);
549 $this->assertEquals(10, $lines['values'][0]['line_total']);
550 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
551 $this->assertTrue(in_array($membershipPayment['contribution_id'], array_keys($contributions['values'])));
552 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
553 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
557 * Test submit with a membership block in place.
559 * @throws \CRM_Core_Exception
561 public function testSubmitMembershipBlockIsSeparatePaymentWithPayLater() {
562 $this->setUpMembershipContributionPage(TRUE);
563 $this->_ids
['membership_type'] = [$this->membershipTypeCreate(['minimum_fee' => 2])];
566 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
567 'id' => (int) $this->_ids
['contribution_page'],
569 'billing_first_name' => 'Billy',
570 'billing_middle_name' => 'Goat',
571 'billing_last_name' => 'Gruff',
573 'selectMembership' => $this->_ids
['membership_type'],
576 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
577 $contributions = $this->callAPISuccess('contribution', 'get', ['contribution_page_id' => $this->_ids
['contribution_page']]);
578 $this->assertCount(2, $contributions['values']);
579 foreach ($contributions['values'] as $val) {
580 $this->assertEquals(CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending'), $val['contribution_status_id']);
583 //Membership should be in Pending state.
584 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
585 $this->assertTrue(in_array($membershipPayment['contribution_id'], array_keys($contributions['values'])));
586 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
587 $pendingStatus = $this->callAPISuccessGetSingle('MembershipStatus', [
591 $this->assertEquals($membership['status_id'], $pendingStatus['id']);
592 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
596 * Test submit with a membership block in place.
598 * @throws \CRM_Core_Exception
600 public function testSubmitMembershipBlockIsSeparatePaymentWithEmail() {
601 $mut = new CiviMailUtils($this, TRUE);
602 $this->setUpMembershipContributionPage(TRUE);
603 $this->addProfile('supporter_profile', $this->_ids
['contribution_page']);
606 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
607 'id' => (int) $this->_ids
['contribution_page'],
609 'billing_first_name' => 'Billy',
610 'billing_middle_name' => 'Goat',
611 'billing_last_name' => 'Gruff',
612 'selectMembership' => $this->_ids
['membership_type'],
613 'email-Primary' => 'billy-goat@the-bridge.net',
614 'payment_processor_id' => $this->_paymentProcessor
['id'],
615 'credit_card_number' => '4111111111111111',
616 'credit_card_type' => 'Visa',
617 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
621 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
622 $contributions = $this->callAPISuccess('contribution', 'get', ['contribution_page_id' => $this->_ids
['contribution_page']]);
623 $this->assertCount(2, $contributions['values']);
624 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
625 $this->assertTrue(in_array($membershipPayment['contribution_id'], array_keys($contributions['values'])));
626 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
627 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
628 // We should have two separate email messages, each with their own amount
629 // line and no total line.
630 $mut->checkAllMailLog(
641 $mut->clearMessages();
645 * Test submit with a membership block in place.
647 * @throws \CRM_Core_Exception
649 public function testSubmitMembershipBlockIsSeparatePaymentZeroDollarsPayLaterWithEmail() {
650 $mut = new CiviMailUtils($this, TRUE);
651 $this->_ids
['membership_type'] = [$this->membershipTypeCreate(['minimum_fee' => 0])];
652 $this->setUpMembershipContributionPage(TRUE);
653 $this->addProfile('supporter_profile', $this->_ids
['contribution_page']);
656 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
657 'id' => (int) $this->_ids
['contribution_page'],
659 'billing_first_name' => 'Billy',
660 'billing_middle_name' => 'Goat',
661 'billing_last_name' => 'Gruffalo',
662 'selectMembership' => $this->_ids
['membership_type'],
663 'payment_processor_id' => 0,
664 'email-Primary' => 'gruffalo@the-bridge.net',
667 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
668 $contributions = $this->callAPISuccess('contribution', 'get', ['contribution_page_id' => $this->_ids
['contribution_page']]);
669 $this->assertCount(2, $contributions['values']);
670 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
671 $this->assertTrue(in_array($membershipPayment['contribution_id'], array_keys($contributions['values'])));
672 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
673 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
676 'General Membership: $ 0.00',
680 $mut->clearMessages();
684 * Test submit with a membership block in place.
686 * @throws \CRM_Core_Exception
688 public function testSubmitMembershipBlockTwoTypesIsSeparatePayment() {
689 $this->_ids
['membership_type'] = [$this->membershipTypeCreate(['minimum_fee' => 6])];
690 $this->_ids
['membership_type'][] = $this->membershipTypeCreate(['name' => 'Student', 'minimum_fee' => 50]);
691 $this->setUpMembershipContributionPage(TRUE);
693 'price_' . $this->_ids
['price_field'][0] => $this->_ids
['price_field_value'][1],
694 'id' => (int) $this->_ids
['contribution_page'],
696 'billing_first_name' => 'Billy',
697 'billing_middle_name' => 'Goat',
698 'billing_last_name' => 'Gruff',
699 'selectMembership' => $this->_ids
['membership_type'][1],
702 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
703 $contributions = $this->callAPISuccess('contribution', 'get', ['contribution_page_id' => $this->_ids
['contribution_page']]);
704 $this->assertCount(2, $contributions['values']);
705 $ids = array_keys($contributions['values']);
706 $this->assertEquals('10.00', $contributions['values'][$ids[0]]['total_amount']);
707 $this->assertEquals('50.00', $contributions['values'][$ids[1]]['total_amount']);
708 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
709 $this->assertArrayHasKey($membershipPayment['contribution_id'], $contributions['values']);
710 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
711 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
715 * Test submit with a membership block in place.
717 * We are expecting a separate payment for the membership vs the contribution.
719 * @throws \CRM_Core_Exception
720 * @throws \CiviCRM_API3_Exception
722 public function testSubmitMembershipBlockIsSeparatePaymentPaymentProcessorNow() {
723 $mut = new CiviMailUtils($this, TRUE);
724 $this->setUpMembershipContributionPage(TRUE);
725 $processor = Civi\Payment\System
::singleton()->getById($this->_paymentProcessor
['id']);
726 $processor->setDoDirectPaymentResult(['fee_amount' => .72]);
728 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
729 'id' => (int) $this->_ids
['contribution_page'],
731 'billing_first_name' => 'Billy',
732 'billing_middle_name' => 'Goat',
733 'billing_last_name' => 'Gruff',
734 'email-Primary' => 'henry@8th.king',
735 'selectMembership' => $this->_ids
['membership_type'],
736 'payment_processor_id' => $this->_paymentProcessor
['id'],
737 'credit_card_number' => '4111111111111111',
738 'credit_card_type' => 'Visa',
739 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
743 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
744 $contributions = $this->callAPISuccess('contribution', 'get', [
745 'contribution_page_id' => $this->_ids
['contribution_page'],
746 'contribution_status_id' => 1,
748 $this->assertCount(2, $contributions['values']);
749 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
750 $this->assertTrue(in_array($membershipPayment['contribution_id'], array_keys($contributions['values'])));
751 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
752 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
753 $lineItem = $this->callAPISuccessGetSingle('LineItem', ['entity_table' => 'civicrm_membership']);
754 $this->assertEquals($lineItem['entity_id'], $membership['id']);
755 $this->assertEquals($lineItem['contribution_id'], $membershipPayment['contribution_id']);
756 $this->assertEquals($lineItem['qty'], 1);
757 $this->assertEquals($lineItem['unit_price'], 2);
758 $this->assertEquals($lineItem['line_total'], 2);
759 foreach ($contributions['values'] as $contribution) {
760 $this->assertEquals(.72, $contribution['fee_amount']);
761 $this->assertEquals($contribution['total_amount'] - .72, $contribution['net_amount']);
763 // The total string is currently absent & it seems worse with - although at some point
764 // it may have been intended
765 $mut->checkAllMailLog(['$ 2.00', 'Contribution Amount', '$ 10.00'], ['Total:']);
767 $mut->clearMessages();
771 * Test submit with a membership block in place.
773 * Ensure a separate payment for the membership vs the contribution, with
776 * @param string $thousandSeparator
777 * punctuation used to refer to thousands.
779 * @throws \CRM_Core_Exception
780 * @throws \CiviCRM_API3_Exception
782 * @dataProvider getThousandSeparators
784 public function testSubmitMembershipBlockIsSeparatePaymentPaymentProcessorNowChargesCorrectAmounts($thousandSeparator) {
785 $this->setCurrencySeparators($thousandSeparator);
786 $this->setUpMembershipContributionPage(TRUE);
787 $processor = Civi\Payment\System
::singleton()->getById($this->_paymentProcessor
['id']);
788 $processor->setDoDirectPaymentResult(['fee_amount' => .72]);
789 $test_uniq = uniqid();
790 $contributionPageAmount = 10;
792 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
793 'id' => (int) $this->_ids
['contribution_page'],
794 'amount' => $contributionPageAmount,
795 'billing_first_name' => 'Billy',
796 'billing_middle_name' => 'Goat',
797 'billing_last_name' => 'Gruff',
798 'email-Primary' => 'henry@8th.king',
799 'selectMembership' => $this->_ids
['membership_type'],
800 'payment_processor_id' => $this->_paymentProcessor
['id'],
801 'credit_card_number' => '4111111111111111',
802 'credit_card_type' => 'Visa',
803 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
805 'TEST_UNIQ' => $test_uniq,
809 $this->hookClass
->setHook('civicrm_alterPaymentProcessorParams', [$this, 'hook_civicrm_alterPaymentProcessorParams']);
811 $this->callAPISuccess('ContributionPage', 'submit', $submitParams);
812 $this->callAPISuccess('contribution', 'get', [
813 'contribution_page_id' => $this->_ids
['contribution_page'],
814 'contribution_status_id' => 1,
817 $result = civicrm_api3('SystemLog', 'get', [
819 'message' => ['LIKE' => "%{$test_uniq}%"],
821 $this->assertCount(2, $result['values'], "Expected exactly 2 log entries matching {$test_uniq}.");
823 // Examine logged entries to ensure correct values.
824 $contribution_ids = [];
825 $found_membership_amount = $found_contribution_amount = FALSE;
826 foreach ($result['values'] as $value) {
827 list($junk, $json) = explode("$test_uniq:", $value['message']);
828 $logged_contribution = json_decode($json, TRUE);
829 $contribution_ids[] = $logged_contribution['contributionID'];
830 if (!empty($logged_contribution['total_amount'])) {
831 $amount = $logged_contribution['total_amount'];
834 $amount = $logged_contribution['amount'];
837 if ($amount == $this->_membershipBlockAmount
) {
838 $found_membership_amount = TRUE;
840 if ($amount == $contributionPageAmount) {
841 $found_contribution_amount = TRUE;
845 $distinct_contribution_ids = array_unique($contribution_ids);
846 $this->assertCount(2, $distinct_contribution_ids, "Expected exactly 2 log contributions with distinct contributionIDs.");
847 $this->assertTrue($found_contribution_amount, "Expected one log contribution with amount '$contributionPageAmount' (the contribution page amount)");
848 $this->assertTrue($found_membership_amount, "Expected one log contribution with amount '$this->_membershipBlockAmount' (the membership amount)");
852 * Test that when a transaction fails the pending contribution remains.
854 * An activity should also be created. CRM-16417.
856 * @throws \CRM_Core_Exception
858 public function testSubmitPaymentProcessorFailure() {
859 $this->setUpContributionPage();
860 $this->setupPaymentProcessor();
861 $this->createLoggedInUser();
862 $priceFieldID = reset($this->_ids
['price_field']);
863 $priceFieldValueID = reset($this->_ids
['price_field_value']);
865 'price_' . $priceFieldID => $priceFieldValueID,
866 'id' => (int) $this->_ids
['contribution_page'],
868 'payment_processor_id' => 1,
869 'credit_card_number' => '4111111111111111',
870 'credit_card_type' => 'Visa',
871 'credit_card_exp_date' => ['M' => 9, 'Y' => 2008],
875 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
876 $contribution = $this->callAPISuccessGetSingle('contribution', [
877 'contribution_page_id' => $this->_ids
['contribution_page'],
878 'contribution_status_id' => 'Failed',
881 $this->callAPISuccessGetSingle('activity', [
882 'source_record_id' => $contribution['id'],
883 'activity_type_id' => 'Failed Payment',
889 * Test submit recurring (yearly) membership with immediate confirmation (IATS style).
891 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
892 * processor (IATS style - denoted by returning trxn_id)
893 * - the first creates a new membership, completed contribution, in progress recurring. Check these
894 * - create another - end date should be extended
896 * @throws \CRM_Core_Exception
898 public function testSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPaymentYear() {
899 $this->doSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPayment(['duration_unit' => 'year', 'recur_frequency_unit' => 'year']);
903 * Test submit recurring (monthly) membership with immediate confirmation (IATS style).
905 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
906 * processor (IATS style - denoted by returning trxn_id)
907 * - the first creates a new membership, completed contribution, in progress recurring. Check these
908 * - create another - end date should be extended
910 public function testSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPaymentMonth() {
911 $this->doSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPayment(['duration_unit' => 'month', 'recur_frequency_unit' => 'month']);
915 * Test submit recurring (mismatched frequency unit) membership with immediate confirmation (IATS style).
917 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
918 * processor (IATS style - denoted by returning trxn_id)
919 * - the first creates a new membership, completed contribution, in progress recurring. Check these
920 * - create another - end date should be extended
922 //public function testSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPaymentDifferentFrequency() {
923 // $this->doSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPayment(array('duration_unit' => 'year', 'recur_frequency_unit' => 'month'));
927 * Helper function for testSubmitMembershipPriceSetPaymentProcessorRecurInstantPayment*
928 * @param array $params
930 * @throws \CRM_Core_Exception
932 public function doSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPayment($params = []) {
933 $this->params
['is_recur'] = 1;
934 $this->params
['recur_frequency_unit'] = $params['recur_frequency_unit'];
935 $membershipTypeParams['duration_unit'] = $params['duration_unit'];
936 if ($params['recur_frequency_unit'] === $params['duration_unit']) {
937 $durationUnit = $params['duration_unit'];
940 $durationUnit = NULL;
942 $this->setUpMembershipContributionPage(FALSE, FALSE, $membershipTypeParams);
943 $dummyPP = Civi\Payment\System
::singleton()->getByProcessor($this->_paymentProcessor
);
944 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_first_success']);
945 $processor = $dummyPP->getPaymentProcessor();
947 if ($params['recur_frequency_unit'] === $params['duration_unit']) {
948 // Membership will be in "New" state because it will get confirmed as payment matches
949 $expectedMembershipStatus = 1;
952 // Membership will still be in "Pending" state as it won't get confirmed as payment doesn't match
953 $expectedMembershipStatus = 5;
957 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
958 'id' => (int) $this->_ids
['contribution_page'],
960 'billing_first_name' => 'Billy',
961 'billing_middle_name' => 'Goat',
962 'billing_last_name' => 'Gruff',
963 'email' => 'billy@goat.gruff',
964 'selectMembership' => $this->_ids
['membership_type'],
965 'payment_processor_id' => 1,
966 'credit_card_number' => '4111111111111111',
967 'credit_card_type' => 'Visa',
968 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
971 'frequency_interval' => 1,
972 'frequency_unit' => $this->params
['recur_frequency_unit'],
975 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
976 $contribution = $this->callAPISuccess('contribution', 'getsingle', [
977 'contribution_page_id' => $this->_ids
['contribution_page'],
978 'contribution_status_id' => 1,
980 $this->assertEquals($processor['payment_instrument_id'], $contribution['payment_instrument_id']);
982 $this->assertEquals('create_first_success', $contribution['trxn_id']);
983 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
984 $this->assertEquals($membershipPayment['contribution_id'], $contribution['id']);
985 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
986 $this->assertEquals($membership['contact_id'], $contribution['contact_id']);
987 $this->assertEquals($expectedMembershipStatus, $membership['status_id']);
988 $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $contribution['contribution_recur_id']]);
989 $this->assertEquals($contribution['contribution_recur_id'], $membership['contribution_recur_id']);
991 $this->callAPISuccess('line_item', 'getsingle', ['contribution_id' => $contribution['id'], 'entity_id' => $membership['id']]);
992 //renew it with processor setting completed - should extend membership
993 $submitParams['contact_id'] = $contribution['contact_id'];
994 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_second_success']);
995 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
996 $this->callAPISuccess('contribution', 'getsingle', [
997 'id' => ['NOT IN' => [$contribution['id']]],
998 'contribution_page_id' => $this->_ids
['contribution_page'],
999 'contribution_status_id' => 1,
1001 $renewedMembership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
1002 if ($durationUnit) {
1003 // We only have an end_date if frequency units match, otherwise membership won't be autorenewed and dates won't be calculated.
1004 $renewedMembershipEndDate = $this->membershipRenewalDate($durationUnit, $membership['end_date']);
1005 $this->assertEquals($renewedMembershipEndDate, $renewedMembership['end_date']);
1007 $recurringContribution = $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $contribution['contribution_recur_id']]);
1008 $this->assertEquals($processor['payment_instrument_id'], $recurringContribution['payment_instrument_id']);
1009 $this->assertEquals(5, $recurringContribution['contribution_status_id']);
1013 * Test submit recurring membership with immediate confirmation (IATS style).
1015 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
1016 * processor (IATS style - denoted by returning trxn_id)
1017 * - the first creates a new membership, completed contribution, in progress recurring. Check these
1018 * - create another - end date should be extended
1020 * @throws \CRM_Core_Exception
1022 public function testSubmitMembershipComplexNonPriceSetPaymentPaymentProcessorRecurInstantPayment() {
1023 $this->params
['is_recur'] = 1;
1024 $this->params
['recur_frequency_unit'] = $membershipTypeParams['duration_unit'] = 'year';
1025 // Add a membership so membership & contribution are not both 1.
1026 $preExistingMembershipID = $this->contactMembershipCreate(['contact_id' => $this->contactIds
[0]]);
1027 $this->setUpMembershipContributionPage(FALSE, FALSE, $membershipTypeParams);
1028 $dummyPP = Civi\Payment\System
::singleton()->getByProcessor($this->_paymentProcessor
);
1029 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_first_success']);
1030 $processor = $dummyPP->getPaymentProcessor();
1033 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
1034 'price_' . $this->_ids
['price_field']['cont'] => 88,
1035 'id' => (int) $this->_ids
['contribution_page'],
1037 'billing_first_name' => 'Billy',
1038 'billing_middle_name' => 'Goat',
1039 'billing_last_name' => 'Gruff',
1040 'email' => 'billy@goat.gruff',
1041 'selectMembership' => $this->_ids
['membership_type'],
1042 'payment_processor_id' => 1,
1043 'credit_card_number' => '4111111111111111',
1044 'credit_card_type' => 'Visa',
1045 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
1048 'frequency_interval' => 1,
1049 'frequency_unit' => $this->params
['recur_frequency_unit'],
1052 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
1053 $contribution = $this->callAPISuccess('contribution', 'getsingle', [
1054 'contribution_page_id' => $this->_ids
['contribution_page'],
1055 'contribution_status_id' => 1,
1057 $this->assertEquals($processor['payment_instrument_id'], $contribution['payment_instrument_id']);
1059 $this->assertEquals('create_first_success', $contribution['trxn_id']);
1060 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
1061 $this->assertEquals($membershipPayment['contribution_id'], $contribution['id']);
1062 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
1063 $this->assertEquals($membership['contact_id'], $contribution['contact_id']);
1064 $this->assertEquals(1, $membership['status_id']);
1065 $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $contribution['contribution_recur_id']]);
1067 $lines = $this->callAPISuccess('line_item', 'get', ['sequential' => 1, 'contribution_id' => $contribution['id']]);
1068 $this->assertEquals(2, $lines['count']);
1069 $this->assertEquals('civicrm_membership', $lines['values'][0]['entity_table']);
1070 $this->assertEquals($preExistingMembershipID +
1, $lines['values'][0]['entity_id']);
1071 $this->assertEquals('civicrm_contribution', $lines['values'][1]['entity_table']);
1072 $this->assertEquals($contribution['id'], $lines['values'][1]['entity_id']);
1073 $this->callAPISuccessGetSingle('MembershipPayment', ['contribution_id' => $contribution['id'], 'membership_id' => $preExistingMembershipID +
1]);
1075 //renew it with processor setting completed - should extend membership
1076 $submitParams['contact_id'] = $contribution['contact_id'];
1077 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_second_success']);
1078 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1079 $renewContribution = $this->callAPISuccess('contribution', 'getsingle', [
1080 'id' => ['NOT IN' => [$contribution['id']]],
1081 'contribution_page_id' => $this->_ids
['contribution_page'],
1082 'contribution_status_id' => 1,
1084 $lines = $this->callAPISuccess('line_item', 'get', ['sequential' => 1, 'contribution_id' => $renewContribution['id']]);
1085 $this->assertEquals(2, $lines['count']);
1086 $this->assertEquals('civicrm_membership', $lines['values'][0]['entity_table']);
1087 $this->assertEquals($preExistingMembershipID +
1, $lines['values'][0]['entity_id']);
1088 $this->assertEquals('civicrm_contribution', $lines['values'][1]['entity_table']);
1089 $this->assertEquals($renewContribution['id'], $lines['values'][1]['entity_id']);
1091 $renewedMembership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
1092 $this->assertEquals(date('Y-m-d', strtotime('+ 1 ' . $this->params
['recur_frequency_unit'], strtotime($membership['end_date']))), $renewedMembership['end_date']);
1093 $recurringContribution = $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $contribution['contribution_recur_id']]);
1094 $this->assertEquals($processor['payment_instrument_id'], $recurringContribution['payment_instrument_id']);
1095 $this->assertEquals(5, $recurringContribution['contribution_status_id']);
1099 * Test submit recurring membership with immediate confirmation (IATS style).
1101 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
1102 * processor (IATS style - denoted by returning trxn_id)
1103 * - the first creates a new membership, completed contribution, in progress recurring. Check these
1104 * - create another - end date should be extended
1106 * @throws \CRM_Core_Exception
1108 public function testSubmitMembershipComplexPriceSetPaymentPaymentProcessorRecurInstantPayment() {
1109 $this->params
['is_recur'] = 1;
1110 $this->params
['recur_frequency_unit'] = $membershipTypeParams['duration_unit'] = 'year';
1111 // Add a membership so membership & contribution are not both 1.
1112 $preExistingMembershipID = $this->contactMembershipCreate(['contact_id' => $this->contactIds
[0]]);
1113 $this->createPriceSetWithPage();
1114 $this->addSecondOrganizationMembershipToPriceSet();
1115 $this->setupPaymentProcessor();
1117 $dummyPP = Civi\Payment\System
::singleton()->getByProcessor($this->_paymentProcessor
);
1118 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_first_success']);
1119 $processor = $dummyPP->getPaymentProcessor();
1122 'price_' . $this->_ids
['price_field'][0] => $this->_ids
['price_field_value']['cont'],
1123 'price_' . $this->_ids
['price_field']['org1'] => $this->_ids
['price_field_value']['org1'],
1124 'price_' . $this->_ids
['price_field']['org2'] => $this->_ids
['price_field_value']['org2'],
1125 'id' => (int) $this->_ids
['contribution_page'],
1127 'billing_first_name' => 'Billy',
1128 'billing_middle_name' => 'Goat',
1129 'billing_last_name' => 'Gruff',
1130 'email' => 'billy@goat.gruff',
1131 'selectMembership' => NULL,
1132 'payment_processor_id' => 1,
1133 'credit_card_number' => '4111111111111111',
1134 'credit_card_type' => 'Visa',
1135 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
1137 'frequency_interval' => 1,
1138 'frequency_unit' => $this->params
['recur_frequency_unit'],
1141 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
1142 $contribution = $this->callAPISuccess('contribution', 'getsingle', [
1143 'contribution_page_id' => $this->_ids
['contribution_page'],
1144 'contribution_status_id' => 1,
1146 $this->assertEquals($processor['payment_instrument_id'], $contribution['payment_instrument_id']);
1148 $this->assertEquals('create_first_success', $contribution['trxn_id']);
1149 $membershipPayments = $this->callAPISuccess('membership_payment', 'get', [
1151 'contribution_id' => $contribution['id'],
1153 $this->assertEquals(2, $membershipPayments['count']);
1154 $lines = $this->callAPISuccess('line_item', 'get', ['sequential' => 1, 'contribution_id' => $contribution['id']]);
1155 $this->assertEquals(3, $lines['count']);
1156 $this->assertEquals('civicrm_membership', $lines['values'][0]['entity_table']);
1157 $this->assertEquals($preExistingMembershipID +
1, $lines['values'][0]['entity_id']);
1158 $this->assertEquals('civicrm_contribution', $lines['values'][1]['entity_table']);
1159 $this->assertEquals($contribution['id'], $lines['values'][1]['entity_id']);
1160 $this->assertEquals('civicrm_membership', $lines['values'][2]['entity_table']);
1161 $this->assertEquals($preExistingMembershipID +
2, $lines['values'][2]['entity_id']);
1163 $this->callAPISuccessGetSingle('MembershipPayment', ['contribution_id' => $contribution['id'], 'membership_id' => $preExistingMembershipID +
1]);
1164 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $preExistingMembershipID +
1]);
1166 //renew it with processor setting completed - should extend membership
1167 $submitParams['contact_id'] = $contribution['contact_id'];
1168 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_second_success']);
1169 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1170 $renewContribution = $this->callAPISuccess('contribution', 'getsingle', [
1171 'id' => ['NOT IN' => [$contribution['id']]],
1172 'contribution_page_id' => $this->_ids
['contribution_page'],
1173 'contribution_status_id' => 1,
1175 $lines = $this->callAPISuccess('line_item', 'get', ['sequential' => 1, 'contribution_id' => $renewContribution['id']]);
1176 $this->assertEquals(3, $lines['count']);
1177 $this->assertEquals('civicrm_membership', $lines['values'][0]['entity_table']);
1178 $this->assertEquals($preExistingMembershipID +
1, $lines['values'][0]['entity_id']);
1179 $this->assertEquals('civicrm_contribution', $lines['values'][1]['entity_table']);
1180 $this->assertEquals($renewContribution['id'], $lines['values'][1]['entity_id']);
1182 $renewedMembership = $this->callAPISuccessGetSingle('membership', ['id' => $preExistingMembershipID +
1]);
1183 $this->assertEquals(date('Y-m-d', strtotime('+ 1 ' . $this->params
['recur_frequency_unit'], strtotime($membership['end_date']))), $renewedMembership['end_date']);
1187 * Extend the price set with a second organisation's membership.
1189 * @throws \CRM_Core_Exception
1191 public function addSecondOrganizationMembershipToPriceSet() {
1192 $organization2ID = $this->organizationCreate();
1193 $membershipTypes = $this->callAPISuccess('MembershipType', 'get', []);
1194 $this->_ids
['membership_type'] = array_keys($membershipTypes['values']);
1195 $this->_ids
['membership_type']['org2'] = $this->membershipTypeCreate(['contact_id' => $organization2ID, 'name' => 'Org 2']);
1196 $priceField = $this->callAPISuccess('PriceField', 'create', [
1197 'price_set_id' => $this->_ids
['price_set'],
1198 'html_type' => 'Radio',
1199 'name' => 'Org1 Price',
1200 'label' => 'Org1Price',
1202 $this->_ids
['price_field']['org1'] = $priceField['id'];
1204 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
1205 'name' => 'org1 amount',
1206 'label' => 'org 1 Amount',
1208 'financial_type_id' => 'Member Dues',
1209 'format.only_id' => TRUE,
1210 'membership_type_id' => reset($this->_ids
['membership_type']),
1211 'price_field_id' => $priceField['id'],
1213 $this->_ids
['price_field_value']['org1'] = $priceFieldValue;
1215 $priceField = $this->callAPISuccess('PriceField', 'create', [
1216 'price_set_id' => $this->_ids
['price_set'],
1217 'html_type' => 'Radio',
1218 'name' => 'Org2 Price',
1219 'label' => 'Org2Price',
1221 $this->_ids
['price_field']['org2'] = $priceField['id'];
1223 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
1224 'name' => 'org2 amount',
1225 'label' => 'org 2 Amount',
1227 'financial_type_id' => 'Member Dues',
1228 'format.only_id' => TRUE,
1229 'membership_type_id' => $this->_ids
['membership_type']['org2'],
1230 'price_field_id' => $priceField['id'],
1232 $this->_ids
['price_field_value']['org2'] = $priceFieldValue;
1237 * Test submit recurring membership with immediate confirmation (IATS style).
1239 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
1240 * processor (IATS style - denoted by returning trxn_id)
1241 * - the first creates a new membership, completed contribution, in progress recurring. Check these
1242 * - create another - end date should be extended
1244 * @throws \CRM_Core_Exception
1246 public function testSubmitMembershipPriceSetPaymentPaymentProcessorSeparatePaymentRecurInstantPayment() {
1248 $this->setUpMembershipContributionPage(TRUE);
1249 $dummyPP = Civi\Payment\System
::singleton()->getByProcessor($this->_paymentProcessor
);
1250 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_first_success']);
1253 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
1254 'id' => (int) $this->_ids
['contribution_page'],
1256 'billing_first_name' => 'Billy',
1257 'billing_middle_name' => 'Goat',
1258 'billing_last_name' => 'Gruff',
1259 'email' => 'billy@goat.gruff',
1260 'selectMembership' => $this->_ids
['membership_type'],
1261 'payment_processor_id' => 1,
1262 'credit_card_number' => '4111111111111111',
1263 'credit_card_type' => 'Visa',
1264 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
1267 'auto_renew' => TRUE,
1268 'frequency_interval' => 1,
1269 'frequency_unit' => 'month',
1272 $this->callAPIAndDocument('ContributionPage', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
1273 $contribution = $this->callAPISuccess('contribution', 'get', [
1274 'contribution_page_id' => $this->_ids
['contribution_page'],
1275 'contribution_status_id' => 1,
1278 $this->assertEquals(2, $contribution['count']);
1279 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
1280 $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
1281 $this->assertNotEmpty($contribution['values'][$membershipPayment['contribution_id']]['contribution_recur_id']);
1282 $this->callAPISuccess('contribution_recur', 'getsingle', []);
1286 * Test submit recurring membership with delayed confirmation (Authorize.net style)
1287 * - we process 2 membership transactions against with a recurring contribution against a contribution page with a delayed
1288 * processor (Authorize.net style - denoted by NOT returning trxn_id)
1289 * - the first creates a pending membership, pending contribution, penging recurring. Check these
1290 * - complete the transaction
1291 * - create another - end date should NOT be extended
1293 * @throws \CRM_Core_Exception
1295 public function testSubmitMembershipPriceSetPaymentPaymentProcessorRecurDelayed() {
1296 $this->params
['is_recur'] = 1;
1297 $this->params
['recur_frequency_unit'] = $membershipTypeParams['duration_unit'] = 'year';
1298 $this->setUpMembershipContributionPage();
1299 $dummyPP = Civi\Payment\System
::singleton()->getByProcessor($this->_paymentProcessor
);
1300 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 2]);
1301 $this->membershipTypeCreate(['name' => 'Student']);
1303 // Add a contribution & a couple of memberships so the id will not be 1 & will differ from membership id.
1304 // This saves us from 'accidental success'.
1305 $this->contributionCreate(['contact_id' => $this->contactIds
[0]]);
1306 $this->contactMembershipCreate(['contact_id' => $this->contactIds
[0]]);
1307 $this->contactMembershipCreate(['contact_id' => $this->contactIds
[0], 'membership_type_id' => 'Student']);
1310 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
1311 'id' => (int) $this->_ids
['contribution_page'],
1313 'billing_first_name' => 'Billy',
1314 'billing_middle_name' => 'Goat',
1315 'billing_last_name' => 'Gruff',
1316 'email' => 'billy@goat.gruff',
1317 'selectMembership' => $this->_ids
['membership_type'],
1318 'payment_processor_id' => 1,
1319 'credit_card_number' => '4111111111111111',
1320 'credit_card_type' => 'Visa',
1321 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
1324 'frequency_interval' => 1,
1325 'frequency_unit' => $this->params
['recur_frequency_unit'],
1328 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
1329 $contribution = $this->callAPISuccess('contribution', 'getsingle', [
1330 'contribution_page_id' => $this->_ids
['contribution_page'],
1331 'contribution_status_id' => 2,
1334 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
1335 $this->assertEquals($membershipPayment['contribution_id'], $contribution['id']);
1336 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
1337 $this->assertEquals($membership['contact_id'], $contribution['contact_id']);
1338 $this->assertEquals(5, $membership['status_id']);
1340 $line = $this->callAPISuccess('line_item', 'getsingle', ['contribution_id' => $contribution['id']]);
1341 $this->assertEquals('civicrm_membership', $line['entity_table']);
1342 $this->assertEquals($membership['id'], $line['entity_id']);
1344 $this->callAPISuccess('contribution', 'completetransaction', [
1345 'id' => $contribution['id'],
1346 'trxn_id' => 'ipn_called',
1347 'payment_processor_id' => $this->_paymentProcessor
['id'],
1349 $line = $this->callAPISuccess('line_item', 'getsingle', ['contribution_id' => $contribution['id']]);
1350 $this->assertEquals('civicrm_membership', $line['entity_table']);
1351 $this->assertEquals($membership['id'], $line['entity_id']);
1353 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
1354 //renew it with processor setting completed - should extend membership
1355 $submitParams = array_merge($submitParams, [
1356 'contact_id' => $contribution['contact_id'],
1358 'frequency_interval' => 1,
1359 'frequency_unit' => $this->params
['recur_frequency_unit'],
1362 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 2]);
1363 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1364 $newContribution = $this->callAPISuccess('contribution', 'getsingle', [
1366 'NOT IN' => [$contribution['id']],
1368 'contribution_page_id' => $this->_ids
['contribution_page'],
1369 'contribution_status_id' => 2,
1371 $line = $this->callAPISuccess('line_item', 'getsingle', ['contribution_id' => $newContribution['id']]);
1372 $this->assertEquals('civicrm_membership', $line['entity_table']);
1373 $this->assertEquals($membership['id'], $line['entity_id']);
1375 $renewedMembership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
1376 //no renewal as the date hasn't changed
1377 $this->assertEquals($membership['end_date'], $renewedMembership['end_date']);
1378 $recurringContribution = $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $newContribution['contribution_recur_id']]);
1379 $this->assertEquals(2, $recurringContribution['contribution_status_id']);
1383 * Test non-recur contribution with membership payment
1385 * @throws \CRM_Core_Exception
1387 public function testSubmitMembershipIsSeparatePaymentNotRecur() {
1388 //Create recur contribution page.
1389 $this->setUpMembershipContributionPage(TRUE, TRUE);
1390 $dummyPP = Civi\Payment\System
::singleton()->getByProcessor($this->_paymentProcessor
);
1391 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_first_success']);
1393 //Sumbit payment with recur disabled.
1395 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
1396 'id' => (int) $this->_ids
['contribution_page'],
1398 'frequency_interval' => 1,
1399 'frequency_unit' => 'month',
1400 'billing_first_name' => 'Billy',
1401 'billing_middle_name' => 'Goat',
1402 'billing_last_name' => 'Gruff',
1403 'email' => 'billy@goat.gruff',
1404 'selectMembership' => $this->_ids
['membership_type'],
1405 'payment_processor_id' => 1,
1406 'credit_card_number' => '4111111111111111',
1407 'credit_card_type' => 'Visa',
1408 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
1412 //Assert if recur contribution is created.
1413 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1414 $recur = $this->callAPISuccess('contribution_recur', 'get', []);
1415 $this->assertEmpty($recur['count']);
1419 * Set up membership contribution page.
1421 * @param bool $isSeparatePayment
1422 * @param bool $isRecur
1423 * @param array $membershipTypeParams Parameters to pass to membershiptype.create API
1425 * @throws \CRM_Core_Exception
1427 public function setUpMembershipContributionPage($isSeparatePayment = FALSE, $isRecur = FALSE, $membershipTypeParams = []) {
1428 $this->setUpMembershipBlockPriceSet($membershipTypeParams);
1429 $this->setupPaymentProcessor();
1430 $this->setUpContributionPage($isRecur);
1432 $this->callAPISuccess('membership_block', 'create', [
1433 'entity_id' => $this->_ids
['contribution_page'],
1434 'entity_table' => 'civicrm_contribution_page',
1435 'is_required' => TRUE,
1436 'is_active' => TRUE,
1437 'is_separate_payment' => $isSeparatePayment,
1438 'membership_type_default' => $this->_ids
['membership_type'],
1443 * Set up pledge block.
1445 public function setUpPledgeBlock() {
1447 'entity_table' => 'civicrm_contribution_page',
1448 'entity_id' => $this->_ids
['contribution_page'],
1449 'pledge_frequency_unit' => 'week',
1450 'is_pledge_interval' => 0,
1451 'pledge_start_date' => json_encode(['calendar_date' => date('Ymd', strtotime("+1 month"))]),
1453 $pledgeBlock = CRM_Pledge_BAO_PledgeBlock
::create($params);
1454 $this->_ids
['pledge_block_id'] = $pledgeBlock->id
;
1458 * The default data set does not include a complete default membership price set - not quite sure why.
1460 * This function ensures it exists & populates $this->_ids with it's data
1462 * @throws \CRM_Core_Exception
1464 public function setUpMembershipBlockPriceSet($membershipTypeParams = []) {
1465 $this->_ids
['price_set'][] = $this->callAPISuccess('price_set', 'getvalue', [
1466 'name' => 'default_membership_type_amount',
1469 if (empty($this->_ids
['membership_type'])) {
1470 $membershipTypeParams = array_merge([
1472 ], $membershipTypeParams);
1473 $this->_ids
['membership_type'] = [$this->membershipTypeCreate($membershipTypeParams)];
1475 $priceField = $this->callAPISuccess('price_field', 'create', [
1476 'price_set_id' => reset($this->_ids
['price_set']),
1477 'name' => 'membership_amount',
1478 'label' => 'Membership Amount',
1479 'html_type' => 'Radio',
1482 $this->_ids
['price_field'][] = $priceField['id'];
1484 foreach ($this->_ids
['membership_type'] as $membershipTypeID) {
1485 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
1486 'name' => 'membership_amount',
1487 'label' => 'Membership Amount',
1488 'amount' => $this->_membershipBlockAmount
,
1489 'financial_type_id' => 'Donation',
1490 'format.only_id' => TRUE,
1491 'membership_type_id' => $membershipTypeID,
1492 'price_field_id' => $priceField['id'],
1494 $this->_ids
['price_field_value'][] = $priceFieldValue;
1496 if (!empty($this->_ids
['membership_type']['org2'])) {
1497 $priceField = $this->callAPISuccess('price_field', 'create', [
1498 'price_set_id' => reset($this->_ids
['price_set']),
1499 'name' => 'membership_org2',
1500 'label' => 'Membership Org2',
1501 'html_type' => 'Checkbox',
1504 $this->_ids
['price_field']['org2'] = $priceField['id'];
1506 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
1507 'name' => 'membership_org2',
1508 'label' => 'Membership org 2',
1510 'financial_type_id' => 'Member Dues',
1511 'format.only_id' => TRUE,
1512 'membership_type_id' => $this->_ids
['membership_type']['org2'],
1513 'price_field_id' => $priceField['id'],
1515 $this->_ids
['price_field_value']['org2'] = $priceFieldValue;
1517 $priceField = $this->callAPISuccess('price_field', 'create', [
1518 'price_set_id' => reset($this->_ids
['price_set']),
1519 'name' => 'Contribution',
1520 'label' => 'Contribution',
1521 'html_type' => 'Text',
1523 'is_enter_qty' => 1,
1525 $this->_ids
['price_field']['cont'] = $priceField['id'];
1526 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
1527 'name' => 'contribution',
1528 'label' => 'Give me money',
1530 'financial_type_id' => 'Donation',
1531 'format.only_id' => TRUE,
1532 'price_field_id' => $priceField['id'],
1534 $this->_ids
['price_field_value'][] = $priceFieldValue;
1538 * Add text field other amount to the price set.
1540 * @throws \CRM_Core_Exception
1542 public function addOtherAmountFieldToMembershipPriceSet() {
1543 $this->_ids
['price_field']['other_amount'] = $this->callAPISuccess('price_field', 'create', [
1544 'price_set_id' => reset($this->_ids
['price_set']),
1545 'name' => 'other_amount',
1546 'label' => 'Other Amount',
1547 'html_type' => 'Text',
1548 'format.only_id' => TRUE,
1551 $this->_ids
['price_field_value']['other_amount'] = $this->callAPISuccess('price_field_value', 'create', [
1552 'financial_type_id' => 'Donation',
1553 'format.only_id' => TRUE,
1554 'label' => 'Other Amount',
1556 'price_field_id' => $this->_ids
['price_field']['other_amount'],
1561 * Help function to set up contribution page with some defaults.
1563 * @param bool $isRecur
1565 * @throws \CRM_Core_Exception
1567 public function setUpContributionPage($isRecur = FALSE) {
1569 $this->params
['is_recur'] = 1;
1570 $this->params
['recur_frequency_unit'] = 'month';
1572 $this->params
['frontend_title'] = 'Test Frontend title';
1573 $contributionPageResult = $this->callAPISuccess($this->_entity
, 'create', $this->params
);
1574 if (empty($this->_ids
['price_set'])) {
1575 $priceSet = $this->callAPISuccess('price_set', 'create', $this->_priceSetParams
);
1576 $this->_ids
['price_set'][] = $priceSet['id'];
1578 $priceSetID = reset($this->_ids
['price_set']);
1579 CRM_Price_BAO_PriceSet
::addTo('civicrm_contribution_page', $contributionPageResult['id'], $priceSetID);
1581 if (empty($this->_ids
['price_field'])) {
1582 $priceField = $this->callAPISuccess('price_field', 'create', [
1583 'price_set_id' => $priceSetID,
1584 'label' => 'Goat Breed',
1585 'html_type' => 'Radio',
1587 $this->_ids
['price_field'] = [$priceField['id']];
1589 if (empty($this->_ids
['price_field_value'])) {
1590 $this->callAPISuccess('price_field_value', 'create', [
1591 'price_set_id' => $priceSetID,
1592 'price_field_id' => $priceField['id'],
1593 'label' => 'Long Haired Goat',
1594 'financial_type_id' => 'Donation',
1596 'non_deductible_amount' => 15,
1598 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
1599 'price_set_id' => $priceSetID,
1600 'price_field_id' => $priceField['id'],
1601 'label' => 'Shoe-eating Goat',
1602 'financial_type_id' => 'Donation',
1604 'non_deductible_amount' => 5,
1606 $this->_ids
['price_field_value'] = [$priceFieldValue['id']];
1608 $this->_ids
['price_field_value']['cheapskate'] = $this->callAPISuccess('price_field_value', 'create', [
1609 'price_set_id' => $priceSetID,
1610 'price_field_id' => $priceField['id'],
1611 'label' => 'Stingy Goat',
1612 'financial_type_id' => 'Donation',
1614 'non_deductible_amount' => 0,
1617 $this->_ids
['contribution_page'] = $contributionPageResult['id'];
1621 * Helper function to set up contribution page which can be used to purchase a
1622 * membership type for different intervals.
1624 * @throws \CRM_Core_Exception
1626 public function setUpMultiIntervalMembershipContributionPage() {
1627 $this->setupPaymentProcessor();
1628 $contributionPage = $this->callAPISuccess($this->_entity
, 'create', $this->params
);
1629 $this->_ids
['contribution_page'] = $contributionPage['id'];
1631 $this->_ids
['membership_type'] = $this->membershipTypeCreate([
1634 'duration_unit' => 'month',
1637 $priceSet = $this->callAPISuccess('PriceSet', 'create', [
1638 'is_quick_config' => 0,
1639 'extends' => 'CiviMember',
1640 'financial_type_id' => 'Member Dues',
1641 'title' => 'CRM-21177',
1643 $this->_ids
['price_set'] = $priceSet['id'];
1645 $priceField = $this->callAPISuccess('price_field', 'create', [
1646 'price_set_id' => $this->_ids
['price_set'],
1647 'name' => 'membership_type',
1648 'label' => 'Membership Type',
1649 'html_type' => 'Radio',
1651 $this->_ids
['price_field'] = $priceField['id'];
1653 $priceFieldValueMonthly = $this->callAPISuccess('price_field_value', 'create', [
1654 'name' => 'CRM-21177_Monthly',
1655 'label' => 'CRM-21177 - Monthly',
1657 'membership_num_terms' => 1,
1658 'membership_type_id' => $this->_ids
['membership_type'],
1659 'price_field_id' => $this->_ids
['price_field'],
1660 'financial_type_id' => 'Member Dues',
1662 $this->_ids
['price_field_value_monthly'] = $priceFieldValueMonthly['id'];
1664 $priceFieldValueYearly = $this->callAPISuccess('price_field_value', 'create', [
1665 'name' => 'CRM-21177_Yearly',
1666 'label' => 'CRM-21177 - Yearly',
1668 'membership_num_terms' => 12,
1669 'membership_type_id' => $this->_ids
['membership_type'],
1670 'price_field_id' => $this->_ids
['price_field'],
1671 'financial_type_id' => 'Member Dues',
1673 $this->_ids
['price_field_value_yearly'] = $priceFieldValueYearly['id'];
1675 CRM_Price_BAO_PriceSet
::addTo('civicrm_contribution_page', $this->_ids
['contribution_page'], $this->_ids
['price_set']);
1677 $this->callAPISuccess('membership_block', 'create', [
1678 'entity_id' => $this->_ids
['contribution_page'],
1679 'entity_table' => 'civicrm_contribution_page',
1680 'is_required' => TRUE,
1681 'is_separate_payment' => FALSE,
1682 'is_active' => TRUE,
1683 'membership_type_default' => $this->_ids
['membership_type'],
1688 * Test submit with a membership block in place.
1690 * @throws \CRM_Core_Exception
1692 public function testSubmitMultiIntervalMembershipContributionPage() {
1693 $this->setUpMultiIntervalMembershipContributionPage();
1695 'price_' . $this->_ids
['price_field'] => $this->_ids
['price_field_value_monthly'],
1696 'id' => (int) $this->_ids
['contribution_page'],
1698 'first_name' => 'Billy',
1699 'last_name' => 'Gruff',
1700 'email' => 'billy@goat.gruff',
1701 'payment_processor_id' => $this->_ids
['payment_processor'],
1702 'credit_card_number' => '4111111111111111',
1703 'credit_card_type' => 'Visa',
1704 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
1708 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1710 $submitParams['price_' . $this->_ids
['price_field']] = $this->_ids
['price_field_value_yearly'];
1711 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1713 $contribution = $this->callAPISuccess('Contribution', 'get', [
1714 'contribution_page_id' => $this->_ids
['contribution_page'],
1716 'api.ContributionRecur.getsingle' => [],
1718 $this->assertEquals(1, $contribution['values'][0]['api.ContributionRecur.getsingle']['frequency_interval']);
1719 //$this->assertEquals(12, $contribution['values'][1]['api.ContributionRecur.getsingle']['frequency_interval']);
1722 public static function setUpBeforeClass() {
1723 // put stuff here that should happen before all tests in this unit
1727 * Create a payment processor instance.
1729 * @throws \CRM_Core_Exception
1731 protected function setupPaymentProcessor() {
1732 $this->params
['payment_processor_id'] = $this->_ids
['payment_processor'] = $this->paymentProcessorCreate([
1733 'payment_processor_type_id' => 'Dummy',
1734 'class_name' => 'Payment_Dummy',
1735 'billing_mode' => 1,
1737 $this->_paymentProcessor
= $this->callAPISuccess('payment_processor', 'getsingle', ['id' => $this->params
['payment_processor_id']]);
1741 * Test submit recurring pledge.
1743 * - we process 1 pledge with a future start date. A recur contribution and the pledge should be created with first payment date in the future.
1745 * @throws \CRM_Core_Exception
1747 public function testSubmitPledgePaymentPaymentProcessorRecurFuturePayment() {
1748 $this->params
['adjust_recur_start_date'] = TRUE;
1749 $this->params
['is_pay_later'] = FALSE;
1750 $this->setUpContributionPage();
1751 $this->setUpPledgeBlock();
1752 $this->setupPaymentProcessor();
1753 $dummyPP = Civi\Payment\System
::singleton()->getByProcessor($this->_paymentProcessor
);
1754 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_first_success']);
1757 'id' => (int) $this->_ids
['contribution_page'],
1759 'billing_first_name' => 'Billy',
1760 'billing_middle_name' => 'Goat',
1761 'billing_last_name' => 'Gruff',
1762 'email' => 'billy@goat.gruff',
1763 'payment_processor_id' => 1,
1764 'credit_card_number' => '4111111111111111',
1765 'credit_card_type' => 'Visa',
1766 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
1768 'pledge_frequency_interval' => 1,
1769 'pledge_frequency_unit' => 'week',
1770 'pledge_installments' => 3,
1771 'is_pledge' => TRUE,
1772 'pledge_block_id' => (int) $this->_ids
['pledge_block_id'],
1775 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
1777 // Check if contribution created.
1778 $contribution = $this->callAPISuccess('contribution', 'getsingle', [
1779 'contribution_page_id' => $this->_ids
['contribution_page'],
1780 // Will be pending when actual payment processor is used (dummy processor does not support future payments).
1781 'contribution_status_id' => 'Completed',
1784 $this->assertEquals('create_first_success', $contribution['trxn_id']);
1786 // Check if pledge created.
1787 $pledge = $this->callAPISuccess('pledge', 'getsingle', []);
1788 $this->assertEquals(date('Ymd', strtotime($pledge['pledge_start_date'])), date('Ymd', strtotime("+1 month")));
1789 $this->assertEquals($pledge['pledge_amount'], 300.00);
1791 // Check if pledge payments created.
1793 'pledge_id' => $pledge['id'],
1795 $pledgePayment = $this->callAPISuccess('pledge_payment', 'get', $params);
1796 $this->assertEquals($pledgePayment['count'], 3);
1797 $this->assertEquals(date('Ymd', strtotime($pledgePayment['values'][1]['scheduled_date'])), date('Ymd', strtotime("+1 month")));
1798 $this->assertEquals($pledgePayment['values'][1]['scheduled_amount'], 100.00);
1799 // Will be pending when actual payment processor is used (dummy processor does not support future payments).
1800 $this->assertEquals($pledgePayment['values'][1]['status_id'], 1);
1802 // Check contribution recur record.
1803 $recur = $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $contribution['contribution_recur_id']]);
1804 $this->assertEquals(date('Ymd', strtotime($recur['start_date'])), date('Ymd', strtotime("+1 month")));
1805 $this->assertEquals($recur['amount'], 100.00);
1806 // In progress status.
1807 $this->assertEquals($recur['contribution_status_id'], 5);
1811 * Test submit pledge payment.
1813 * - test submitting a pledge payment using contribution form.
1815 * @throws \CRM_Core_Exception
1817 public function testSubmitPledgePayment() {
1818 $this->testSubmitPledgePaymentPaymentProcessorRecurFuturePayment();
1819 $pledge = $this->callAPISuccess('pledge', 'getsingle', []);
1821 'pledge_id' => $pledge['id'],
1824 'id' => (int) $pledge['pledge_contribution_page_id'],
1825 'pledge_amount' => [2 => 1],
1826 'billing_first_name' => 'Billy',
1827 'billing_middle_name' => 'Goat',
1828 'billing_last_name' => 'Gruff',
1829 'email' => 'billy@goat.gruff',
1830 'payment_processor_id' => 1,
1831 'credit_card_number' => '4111111111111111',
1832 'credit_card_type' => 'Visa',
1833 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
1835 'pledge_id' => $pledge['id'],
1836 'cid' => $pledge['contact_id'],
1837 'contact_id' => $pledge['contact_id'],
1839 'is_pledge' => TRUE,
1840 'pledge_block_id' => $this->_ids
['pledge_block_id'],
1842 $pledgePayment = $this->callAPISuccess('pledge_payment', 'get', $params);
1843 $this->assertEquals($pledgePayment['values'][2]['status_id'], 2);
1845 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
1847 // Check if contribution created.
1848 $contribution = $this->callAPISuccess('contribution', 'getsingle', [
1849 'contribution_page_id' => $pledge['pledge_contribution_page_id'],
1850 'contribution_status_id' => 'Completed',
1851 'contact_id' => $pledge['contact_id'],
1852 'contribution_recur_id' => ['IS NULL' => 1],
1855 $this->assertEquals(100.00, $contribution['total_amount']);
1856 $pledgePayment = $this->callAPISuccess('pledge_payment', 'get', $params);
1857 $this->assertEquals($pledgePayment['values'][2]['status_id'], 1, "This pledge payment should have been completed");
1858 $this->assertEquals($pledgePayment['values'][2]['contribution_id'], $contribution['id']);
1862 * Test form submission with multiple option price set.
1864 * @param string $thousandSeparator
1865 * punctuation used to refer to thousands.
1867 * @dataProvider getThousandSeparators
1868 * @throws \CRM_Core_Exception
1870 public function testSubmitContributionPageWithPriceSet($thousandSeparator) {
1871 $this->setCurrencySeparators($thousandSeparator);
1872 $this->_priceSetParams
['is_quick_config'] = 0;
1873 $this->setUpContributionPage();
1875 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
1876 'id' => (int) $this->_ids
['contribution_page'],
1878 'first_name' => 'Billy',
1879 'last_name' => 'Gruff',
1880 'email' => 'billy@goat.gruff',
1881 'is_pay_later' => TRUE,
1883 $this->addPriceFields($submitParams);
1885 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1886 $contribution = $this->callAPISuccessGetSingle('contribution', [
1887 'contribution_page_id' => $this->_ids
['contribution_page'],
1888 'contribution_status_id' => 'Pending',
1890 $this->assertEquals(80, $contribution['total_amount']);
1891 $lineItems = $this->callAPISuccess('LineItem', 'get', [
1892 'contribution_id' => $contribution['id'],
1894 $this->assertEquals(3, $lineItems['count']);
1895 $totalLineAmount = 0;
1896 foreach ($lineItems['values'] as $lineItem) {
1897 $totalLineAmount = $totalLineAmount +
$lineItem['line_total'];
1899 $this->assertEquals(80, $totalLineAmount);
1903 * Function to add additional price fields to priceset.
1905 * @param array $params
1907 * @throws \CRM_Core_Exception
1909 public function addPriceFields(&$params) {
1910 $priceSetID = reset($this->_ids
['price_set']);
1911 $priceField = $this->callAPISuccess('price_field', 'create', [
1912 'price_set_id' => $priceSetID,
1913 'label' => 'Chicken Breed',
1914 'html_type' => 'CheckBox',
1916 $priceFieldValue1 = $this->callAPISuccess('price_field_value', 'create', [
1917 'price_set_id' => $priceSetID,
1918 'price_field_id' => $priceField['id'],
1919 'label' => 'Shoe-eating chicken -1',
1920 'financial_type_id' => 'Donation',
1923 $priceFieldValue2 = $this->callAPISuccess('price_field_value', 'create', [
1924 'price_set_id' => $priceSetID,
1925 'price_field_id' => $priceField['id'],
1926 'label' => 'Shoe-eating chicken -2',
1927 'financial_type_id' => 'Donation',
1930 $params['price_' . $priceField['id']] = [
1931 $priceFieldValue1['id'] => 1,
1932 $priceFieldValue2['id'] => 1,
1937 * Test Tax Amount is calculated properly when using PriceSet with Field Type = Text/Numeric Quantity
1939 * @param string $thousandSeparator
1940 * punctuation used to refer to thousands.
1942 * @dataProvider getThousandSeparators
1943 * @throws \CRM_Core_Exception
1945 public function testSubmitContributionPageWithPriceSetQuantity($thousandSeparator) {
1946 $this->setCurrencySeparators($thousandSeparator);
1947 $this->_priceSetParams
['is_quick_config'] = 0;
1948 $this->enableTaxAndInvoicing();
1949 $financialType = $this->createFinancialType();
1950 $financialTypeId = $financialType['id'];
1951 // This function sets the Tax Rate at 10% - it currently has no way to pass Tax Rate into it - so let's work with 10%
1952 $this->relationForFinancialTypeWithFinancialAccount($financialType['id'], 5);
1954 $this->setUpContributionPage();
1956 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
1957 'id' => (int) $this->_ids
['contribution_page'],
1958 'first_name' => 'J',
1960 'email' => 'JT@ohcanada.ca',
1961 'is_pay_later' => TRUE,
1962 'receive_date' => date('Y-m-d H:i:s'),
1965 // Create PriceSet/PriceField
1966 $priceSetID = reset($this->_ids
['price_set']);
1967 $priceField = $this->callAPISuccess('price_field', 'create', [
1968 'price_set_id' => $priceSetID,
1969 'label' => 'Printing Rights',
1970 'html_type' => 'Text',
1972 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
1973 'price_set_id' => $priceSetID,
1974 'price_field_id' => $priceField['id'],
1975 'label' => 'Printing Rights',
1976 'financial_type_id' => $financialTypeId,
1977 'amount' => '16.95',
1979 $priceFieldId = $priceField['id'];
1981 // Set quantity for our test
1982 $submitParams['price_' . $priceFieldId] = 180;
1984 // contribution_page submit requires amount and tax_amount - and that's ok we're not testing that - we're testing at the LineItem level
1985 $submitParams['amount'] = $this->formatMoneyInput(180 * 16.95);
1986 // This is the correct Tax Amount - use it later to compare to what the CiviCRM Core came up with at the LineItem level
1987 $submitParams['tax_amount'] = $this->formatMoneyInput(180 * 16.95 * 0.10);
1989 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1990 $contribution = $this->callAPISuccessGetSingle('contribution', [
1991 'contribution_page_id' => $this->_ids
['contribution_page'],
1994 // Retrieve the lineItem that belongs to the Printing Rights and check the tax_amount CiviCRM Core calculated for it
1995 $lineItem = $this->callAPISuccessGetSingle('LineItem', [
1996 'contribution_id' => $contribution['id'],
1997 'label' => 'Printing Rights',
2000 $lineItem_TaxAmount = round($lineItem['tax_amount'], 2);
2002 $this->assertEquals($lineItem['line_total'], $contribution['total_amount'], 'Contribution total should match line total');
2003 $this->assertEquals($lineItem_TaxAmount, round(180 * 16.95 * 0.10, 2), 'Wrong Sales Tax Amount is calculated and stored.');
2007 * Test validating a contribution page submit.
2009 * @throws \CRM_Core_Exception
2011 public function testValidate() {
2012 $this->setUpContributionPage();
2013 $errors = $this->callAPISuccess('ContributionPage', 'validate', array_merge($this->getBasicSubmitParams(), ['action' => 'submit']))['values'];
2014 $this->assertEmpty($errors);
2018 * Test validating a contribution page submit in POST context.
2020 * A likely use case for the validation is when the is being submitted and some handling is
2021 * to be done before processing but the validity of input needs to be checked first.
2023 * For example Paypal Checkout will replace the confirm button with it's own but we are able to validate
2024 * before paypal launches it's modal. In this case the $_REQUEST is post but we need validation to succeed.
2026 public function testValidatePost() {
2027 $_SERVER['REQUEST_METHOD'] = 'POST';
2028 $this->setUpContributionPage();
2029 $errors = $this->callAPISuccess('ContributionPage', 'validate', array_merge($this->getBasicSubmitParams(), ['action' => 'submit']))['values'];
2030 $this->assertEmpty($errors);
2031 unset($_SERVER['REQUEST_METHOD']);
2035 * Test that an error is generated if required fields are not submitted.
2037 public function testValidateOutputOnMissingRecurFields() {
2038 $this->params
['is_recur_interval'] = 1;
2039 $this->setUpContributionPage(TRUE);
2040 $submitParams = array_merge($this->getBasicSubmitParams(), ['action' => 'submit']);
2041 $submitParams['is_recur'] = 1;
2042 $submitParams['frequency_interval'] = '';
2043 $submitParams['frequency_unit'] = '';
2044 $errors = $this->callAPISuccess('ContributionPage', 'validate', $submitParams)['values'];
2045 $this->assertEquals('Please enter a number for how often you want to make this recurring contribution (EXAMPLE: Every 3 months).', $errors['frequency_interval']);
2049 * Implements hook_civicrm_alterPaymentProcessorParams().
2051 * @throws \Exception
2053 public function hook_civicrm_alterPaymentProcessorParams($paymentObj, &$rawParams, &$cookedParams) {
2054 // Ensure total_amount are the same if they're both given.
2055 $total_amount = CRM_Utils_Array
::value('total_amount', $rawParams);
2056 $amount = CRM_Utils_Array
::value('amount', $rawParams);
2057 if (!empty($total_amount) && !empty($amount) && $total_amount != $amount) {
2058 throw new Exception("total_amount '$total_amount' and amount '$amount' differ.");
2061 // Log parameters for later debugging and testing.
2062 $message = __FUNCTION__
. ": {$rawParams['TEST_UNIQ']}:";
2063 $log_params = array_intersect_key($rawParams, [
2065 'total_amount' => 1,
2066 'contributionID' => 1,
2068 $message .= json_encode($log_params);
2069 $log = new CRM_Utils_SystemLogger();
2070 $log->debug($message, $_REQUEST);
2074 * Get the params for a basic simple submit.
2078 protected function getBasicSubmitParams() {
2079 $priceFieldID = reset($this->_ids
['price_field']);
2080 $priceFieldValueID = reset($this->_ids
['price_field_value']);
2082 'price_' . $priceFieldID => $priceFieldValueID,
2083 'id' => (int) $this->_ids
['contribution_page'],
2085 'priceSetId' => $this->_ids
['price_set'][0],
2086 'payment_processor_id' => 0,
2088 return $submitParams;