3 +--------------------------------------------------------------------+
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
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. |
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. |
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 +--------------------------------------------------------------------+
29 * Test APIv3 civicrm_contribute_recur* functions
31 * @package CiviCRM_APIv3
32 * @subpackage API_Contribution
35 class api_v3_ContributionPageTest
extends CiviUnitTestCase
{
36 protected $testAmount = 34567;
39 protected $contactIds = [];
40 protected $_entity = 'contribution_page';
41 protected $contribution_result = NULL;
42 protected $_priceSetParams = [];
43 protected $_membershipBlockAmount = 2;
45 * Payment processor details.
48 protected $_paymentProcessor = [];
60 public $DBResetRequired = TRUE;
62 public function setUp() {
64 $this->contactIds
[] = $this->individualCreate();
66 'title' => "Test Contribution Page",
67 'financial_type_id' => 1,
69 'goal_amount' => $this->testAmount
,
71 'is_monetary' => TRUE,
72 'is_email_receipt' => TRUE,
73 'receipt_from_email' => 'yourconscience@donate.com',
74 'receipt_from_name' => 'Ego Freud',
77 $this->_priceSetParams
= [
78 'is_quick_config' => 1,
79 'extends' => 'CiviContribute',
80 'financial_type_id' => 'Donation',
85 public function tearDown() {
86 foreach ($this->contactIds
as $id) {
87 $this->callAPISuccess('contact', 'delete', ['id' => $id]);
89 $this->quickCleanUpFinancialEntities();
95 * @dataProvider versionThreeAndFour
97 public function testCreateContributionPage($version) {
98 $this->_apiversion
= $version;
99 $result = $this->callAPIAndDocument($this->_entity
, 'create', $this->params
, __FUNCTION__
, __FILE__
);
100 $this->assertEquals(1, $result['count']);
101 $this->assertNotNull($result['values'][$result['id']]['id']);
102 $this->getAndCheck($this->params
, $result['id'], $this->_entity
);
106 * @param int $version
107 * @dataProvider versionThreeAndFour
109 public function testGetBasicContributionPage($version) {
110 $this->_apiversion
= $version;
111 $createResult = $this->callAPISuccess($this->_entity
, 'create', $this->params
);
112 $this->id
= $createResult['id'];
115 'financial_type_id' => 1,
117 $getResult = $this->callAPIAndDocument($this->_entity
, 'get', $getParams, __FUNCTION__
, __FILE__
);
118 $this->assertEquals(1, $getResult['count']);
121 public function testGetContributionPageByAmount() {
122 $createResult = $this->callAPISuccess($this->_entity
, 'create', $this->params
);
123 $this->id
= $createResult['id'];
126 'amount' => '' . $this->testAmount
,
128 'financial_type_id' => 1,
130 $getResult = $this->callAPISuccess($this->_entity
, 'get', $getParams);
131 $this->assertEquals(1, $getResult['count']);
135 * @param int $version
136 * @dataProvider versionThreeAndFour
138 public function testDeleteContributionPage($version) {
139 $this->_apiversion
= $version;
140 $createResult = $this->callAPISuccess($this->_entity
, 'create', $this->params
);
141 $deleteParams = ['id' => $createResult['id']];
142 $this->callAPIAndDocument($this->_entity
, 'delete', $deleteParams, __FUNCTION__
, __FILE__
);
143 $checkDeleted = $this->callAPISuccess($this->_entity
, 'get', []);
144 $this->assertEquals(0, $checkDeleted['count']);
147 public function testGetFieldsContributionPage() {
148 $result = $this->callAPISuccess($this->_entity
, 'getfields', ['action' => 'create']);
149 $this->assertEquals(12, $result['values']['start_date']['type']);
153 * Test form submission with basic price set.
155 public function testSubmit() {
156 $this->setUpContributionPage();
157 $submitParams = $this->getBasicSubmitParams();
159 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
160 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['contribution_page_id' => $this->_ids
['contribution_page']]);
161 //assert non-deductible amount
162 $this->assertEquals(5.00, $contribution['non_deductible_amount']);
166 * Test form submission with basic price set.
168 public function testSubmitZeroDollar() {
169 $this->setUpContributionPage();
170 $priceFieldID = reset($this->_ids
['price_field']);
172 'price_' . $priceFieldID => $this->_ids
['price_field_value']['cheapskate'],
173 'id' => (int) $this->_ids
['contribution_page'],
175 'priceSetId' => $this->_ids
['price_set'][0],
176 'payment_processor_id' => '',
179 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
180 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['contribution_page_id' => $this->_ids
['contribution_page']]);
182 $this->assertEquals($this->formatMoneyInput(0), $contribution['non_deductible_amount']);
183 $this->assertEquals($this->formatMoneyInput(0), $contribution['total_amount']);
187 * Test form submission with billing first & last name where the contact does NOT
188 * otherwise have one.
190 public function testSubmitNewBillingNameData() {
191 $this->setUpContributionPage();
192 $contact = $this->callAPISuccess('Contact', 'create', ['contact_type' => 'Individual', 'email' => 'wonderwoman@amazon.com']);
193 $priceFieldID = reset($this->_ids
['price_field']);
194 $priceFieldValueID = reset($this->_ids
['price_field_value']);
196 'price_' . $priceFieldID => $priceFieldValueID,
197 'id' => (int) $this->_ids
['contribution_page'],
199 'billing_first_name' => 'Wonder',
200 'billing_last_name' => 'Woman',
201 'contactID' => $contact['id'],
202 'email' => 'wonderwoman@amazon.com',
205 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
206 $contact = $this->callAPISuccess('Contact', 'get', [
207 'id' => $contact['id'],
215 $this->assertEquals([
216 'first_name' => 'Wonder',
217 'last_name' => 'Woman',
218 'display_name' => 'Wonder Woman',
219 'sort_name' => 'Woman, Wonder',
220 'id' => $contact['id'],
221 'contact_id' => $contact['id'],
222 ], $contact['values'][$contact['id']]);
227 * Test form submission with billing first & last name where the contact does
228 * otherwise have one and should not be overwritten.
230 public function testSubmitNewBillingNameDoNotOverwrite() {
231 $this->setUpContributionPage();
232 $contact = $this->callAPISuccess('Contact', 'create', [
233 'contact_type' => 'Individual',
234 'email' => 'wonderwoman@amazon.com',
235 'first_name' => 'Super',
236 'last_name' => 'Boy',
238 $priceFieldID = reset($this->_ids
['price_field']);
239 $priceFieldValueID = reset($this->_ids
['price_field_value']);
241 'price_' . $priceFieldID => $priceFieldValueID,
242 'id' => (int) $this->_ids
['contribution_page'],
244 'billing_first_name' => 'Wonder',
245 'billing_last_name' => 'Woman',
246 'contactID' => $contact['id'],
247 'email' => 'wonderwoman@amazon.com',
250 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
251 $contact = $this->callAPISuccess('Contact', 'get', [
252 'id' => $contact['id'],
260 $this->assertEquals([
261 'first_name' => 'Super',
262 'last_name' => 'Boy',
263 'display_name' => 'Super Boy',
264 'sort_name' => 'Boy, Super',
265 'id' => $contact['id'],
266 'contact_id' => $contact['id'],
267 ], $contact['values'][$contact['id']]);
272 * Test process with instant payment when more than one configured for the page.
276 public function testSubmitRecurMultiProcessorInstantPayment() {
277 $this->setUpContributionPage();
278 $this->setupPaymentProcessor();
279 $paymentProcessor2ID = $this->paymentProcessorCreate([
280 'payment_processor_type_id' => 'Dummy',
281 'name' => 'processor 2',
282 'class_name' => 'Payment_Dummy',
285 $dummyPP = Civi\Payment\System
::singleton()->getById($paymentProcessor2ID);
286 $dummyPP->setDoDirectPaymentResult([
287 'payment_status_id' => 1,
288 'trxn_id' => 'create_first_success',
291 $processor = $dummyPP->getPaymentProcessor();
292 $this->callAPISuccess('ContributionPage', 'create', [
293 'id' => $this->_ids
['contribution_page'],
294 'payment_processor' => [$paymentProcessor2ID, $this->_ids
['payment_processor']],
297 $priceFieldID = reset($this->_ids
['price_field']);
298 $priceFieldValueID = reset($this->_ids
['price_field_value']);
300 'price_' . $priceFieldID => $priceFieldValueID,
301 'id' => (int) $this->_ids
['contribution_page'],
304 'frequency_interval' => 1,
305 'frequency_unit' => 'month',
306 'payment_processor_id' => $paymentProcessor2ID,
309 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
310 $contribution = $this->callAPISuccess('contribution', 'getsingle', [
311 'contribution_page_id' => $this->_ids
['contribution_page'],
312 'contribution_status_id' => 1,
314 $this->assertEquals('create_first_success', $contribution['trxn_id']);
315 $this->assertEquals(10, $contribution['total_amount']);
316 $this->assertEquals(.85, $contribution['fee_amount']);
317 $this->assertEquals(9.15, $contribution['net_amount']);
318 $this->_checkFinancialRecords([
319 'id' => $contribution['id'],
320 'total_amount' => $contribution['total_amount'],
321 'payment_instrument_id' => $processor['payment_instrument_id'],
326 * Test submit with a membership block in place.
328 public function testSubmitMembershipBlockNotSeparatePayment() {
329 $this->setUpMembershipContributionPage();
331 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
332 'id' => (int) $this->_ids
['contribution_page'],
334 'billing_first_name' => 'Billy',
335 'billing_middle_name' => 'Goat',
336 'billing_last_name' => 'Gruff',
337 'selectMembership' => $this->_ids
['membership_type'],
341 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
342 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['contribution_page_id' => $this->_ids
['contribution_page']]);
343 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', ['contribution_id' => $contribution['id']]);
344 $this->callAPISuccessGetSingle('LineItem', ['contribution_id' => $contribution['id'], 'entity_id' => $membershipPayment['id']]);
348 * Test submit with a membership block in place works with renewal.
350 * @throws \CRM_Core_Exception
352 public function testSubmitMembershipBlockNotSeparatePaymentProcessorInstantRenew() {
353 $this->setUpMembershipContributionPage();
354 $dummyPP = Civi\Payment\System
::singleton()->getByProcessor($this->_paymentProcessor
);
355 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1]);
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'],
364 'payment_processor_id' => 1,
365 'credit_card_number' => '4111111111111111',
366 'credit_card_type' => 'Visa',
367 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
371 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
372 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['contribution_page_id' => $this->_ids
['contribution_page']]);
373 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', ['contribution_id' => $contribution['id']]);
374 $this->callAPISuccessGetCount('LineItem', [
375 'entity_table' => 'civicrm_membership',
376 'entity_id' => $membershipPayment['id'],
379 $submitParams['contact_id'] = $contribution['contact_id'];
381 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
382 $this->callAPISuccessGetCount('LineItem', [
383 'entity_table' => 'civicrm_membership',
384 'entity_id' => $membershipPayment['id'],
386 $membership = $this->callAPISuccessGetSingle('Membership', [
387 'id' => $membershipPayment['membership_id'],
388 'return' => ['end_date', 'join_date', 'start_date'],
390 $this->assertEquals(date('Y-m-d'), $membership['start_date']);
391 $this->assertEquals(date('Y-m-d'), $membership['join_date']);
392 $this->assertEquals(date('Y-m-d', strtotime('+ 2 year - 1 day')), $membership['end_date']);
396 * Test submit with a membership block in place.
398 public function testSubmitMembershipBlockNotSeparatePaymentWithEmail() {
399 $mut = new CiviMailUtils($this, TRUE);
400 $this->setUpMembershipContributionPage();
401 $this->addProfile('supporter_profile', $this->_ids
['contribution_page']);
404 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
405 'id' => (int) $this->_ids
['contribution_page'],
407 'billing_first_name' => 'Billy',
408 'billing_middle_name' => 'Goat',
409 'billing_last_name' => 'Gruff',
410 'selectMembership' => $this->_ids
['membership_type'],
411 'email-Primary' => 'billy-goat@the-bridge.net',
412 'payment_processor_id' => $this->_paymentProcessor
['id'],
413 'credit_card_number' => '4111111111111111',
414 'credit_card_type' => 'Visa',
415 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
419 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
420 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['contribution_page_id' => $this->_ids
['contribution_page']]);
421 $this->callAPISuccess('membership_payment', 'getsingle', ['contribution_id' => $contribution['id']]);
423 'Membership Type: General',
426 $mut->clearMessages();
430 * Test submit with a membership block in place.
432 public function testSubmitMembershipBlockNotSeparatePaymentZeroDollarsWithEmail() {
433 $mut = new CiviMailUtils($this, TRUE);
434 $this->_ids
['membership_type'] = [$this->membershipTypeCreate(['minimum_fee' => 0])];
435 $this->setUpMembershipContributionPage();
436 $this->addProfile('supporter_profile', $this->_ids
['contribution_page']);
439 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
440 'id' => (int) $this->_ids
['contribution_page'],
442 'billing_first_name' => 'Billy',
443 'billing_middle_name' => 'Goat',
444 'billing_last_name' => 'Gruffier',
445 'selectMembership' => $this->_ids
['membership_type'],
446 'email-Primary' => 'billy-goat@the-new-bridge.net',
447 'payment_processor_id' => $this->params
['payment_processor_id'],
450 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
451 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['contribution_page_id' => $this->_ids
['contribution_page']]);
452 $this->callAPISuccess('membership_payment', 'getsingle', ['contribution_id' => $contribution['id']]);
453 //Assert only one mail is being sent.
454 $msgs = $mut->getAllMessages();
455 $this->assertCount(1, $msgs);
458 'Membership Type: General',
464 $mut->clearMessages();
468 * Test submit with a pay later abnd check line item in mails.
470 public function testSubmitMembershipBlockIsSeparatePaymentPayLaterWithEmail() {
471 $mut = new CiviMailUtils($this, TRUE);
472 $this->setUpMembershipContributionPage();
474 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
475 'id' => (int) $this->_ids
['contribution_page'],
477 'billing_first_name' => 'Billy',
478 'billing_middle_name' => 'Goat',
479 'billing_last_name' => 'Gruff',
481 'selectMembership' => $this->_ids
['membership_type'],
482 'email-Primary' => 'billy-goat@the-bridge.net',
485 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
486 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['contribution_page_id' => $this->_ids
['contribution_page']]);
487 $this->callAPISuccess('membership_payment', 'getsingle', ['contribution_id' => $contribution['id']]);
489 'Membership Amount -... $ 2.00',
492 $mut->clearMessages();
496 * Test submit with a membership block in place.
498 public function testSubmitMembershipBlockIsSeparatePayment() {
499 $this->setUpMembershipContributionPage(TRUE);
500 $this->_ids
['membership_type'] = [$this->membershipTypeCreate(['minimum_fee' => 2])];
502 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
503 'id' => (int) $this->_ids
['contribution_page'],
505 'billing_first_name' => 'Billy',
506 'billing_middle_name' => 'Goat',
507 'billing_last_name' => 'Gruff',
508 'selectMembership' => $this->_ids
['membership_type'],
511 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
512 $contributions = $this->callAPISuccess('contribution', 'get', ['contribution_page_id' => $this->_ids
['contribution_page']]);
513 $this->assertCount(2, $contributions['values']);
514 $lines = $this->callAPISuccess('LineItem', 'get', ['sequential' => 1]);
515 $this->assertEquals(10, $lines['values'][0]['line_total']);
516 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
517 $this->assertTrue(in_array($membershipPayment['contribution_id'], array_keys($contributions['values'])));
518 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
519 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
523 * Test submit with a membership block in place.
525 public function testSubmitMembershipBlockIsSeparatePaymentWithPayLater() {
526 $this->setUpMembershipContributionPage(TRUE);
527 $this->_ids
['membership_type'] = [$this->membershipTypeCreate(['minimum_fee' => 2])];
530 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
531 'id' => (int) $this->_ids
['contribution_page'],
533 'billing_first_name' => 'Billy',
534 'billing_middle_name' => 'Goat',
535 'billing_last_name' => 'Gruff',
537 'selectMembership' => $this->_ids
['membership_type'],
540 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
541 $contributions = $this->callAPISuccess('contribution', 'get', ['contribution_page_id' => $this->_ids
['contribution_page']]);
542 $this->assertCount(2, $contributions['values']);
543 foreach ($contributions['values'] as $val) {
544 $this->assertEquals('Pending', $val['contribution_status']);
547 //Membership should be in Pending state.
548 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
549 $this->assertTrue(in_array($membershipPayment['contribution_id'], array_keys($contributions['values'])));
550 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
551 $pendingStatus = $this->callAPISuccessGetSingle('MembershipStatus', [
555 $this->assertEquals($membership['status_id'], $pendingStatus['id']);
556 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
560 * Test submit with a membership block in place.
562 public function testSubmitMembershipBlockIsSeparatePaymentWithEmail() {
563 $mut = new CiviMailUtils($this, TRUE);
564 $this->setUpMembershipContributionPage(TRUE);
565 $this->addProfile('supporter_profile', $this->_ids
['contribution_page']);
568 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
569 'id' => (int) $this->_ids
['contribution_page'],
571 'billing_first_name' => 'Billy',
572 'billing_middle_name' => 'Goat',
573 'billing_last_name' => 'Gruff',
574 'selectMembership' => $this->_ids
['membership_type'],
575 'email-Primary' => 'billy-goat@the-bridge.net',
576 'payment_processor_id' => $this->_paymentProcessor
['id'],
577 'credit_card_number' => '4111111111111111',
578 'credit_card_type' => 'Visa',
579 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
583 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
584 $contributions = $this->callAPISuccess('contribution', 'get', ['contribution_page_id' => $this->_ids
['contribution_page']]);
585 $this->assertCount(2, $contributions['values']);
586 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
587 $this->assertTrue(in_array($membershipPayment['contribution_id'], array_keys($contributions['values'])));
588 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
589 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
590 // We should have two separate email messages, each with their own amount
591 // line and no total line.
592 $mut->checkAllMailLog(
603 $mut->clearMessages();
607 * Test submit with a membership block in place.
609 public function testSubmitMembershipBlockIsSeparatePaymentZeroDollarsPayLaterWithEmail() {
610 $mut = new CiviMailUtils($this, TRUE);
611 $this->_ids
['membership_type'] = [$this->membershipTypeCreate(['minimum_fee' => 0])];
612 $this->setUpMembershipContributionPage(TRUE);
613 $this->addProfile('supporter_profile', $this->_ids
['contribution_page']);
616 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
617 'id' => (int) $this->_ids
['contribution_page'],
619 'billing_first_name' => 'Billy',
620 'billing_middle_name' => 'Goat',
621 'billing_last_name' => 'Gruffalo',
622 'selectMembership' => $this->_ids
['membership_type'],
623 'payment_processor_id' => 0,
624 'email-Primary' => 'gruffalo@the-bridge.net',
627 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
628 $contributions = $this->callAPISuccess('contribution', 'get', ['contribution_page_id' => $this->_ids
['contribution_page']]);
629 $this->assertCount(2, $contributions['values']);
630 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
631 $this->assertTrue(in_array($membershipPayment['contribution_id'], array_keys($contributions['values'])));
632 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
633 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
636 'General Membership: $ 0.00',
640 $mut->clearMessages();
644 * Test submit with a membership block in place.
646 public function testSubmitMembershipBlockTwoTypesIsSeparatePayment() {
647 $this->_ids
['membership_type'] = [$this->membershipTypeCreate(['minimum_fee' => 6])];
648 $this->_ids
['membership_type'][] = $this->membershipTypeCreate(['name' => 'Student', 'minimum_fee' => 50]);
649 $this->setUpMembershipContributionPage(TRUE);
651 'price_' . $this->_ids
['price_field'][0] => $this->_ids
['price_field_value'][1],
652 'id' => (int) $this->_ids
['contribution_page'],
654 'billing_first_name' => 'Billy',
655 'billing_middle_name' => 'Goat',
656 'billing_last_name' => 'Gruff',
657 'selectMembership' => $this->_ids
['membership_type'][1],
660 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
661 $contributions = $this->callAPISuccess('contribution', 'get', ['contribution_page_id' => $this->_ids
['contribution_page']]);
662 $this->assertCount(2, $contributions['values']);
663 $ids = array_keys($contributions['values']);
664 $this->assertEquals('10.00', $contributions['values'][$ids[0]]['total_amount']);
665 $this->assertEquals('50.00', $contributions['values'][$ids[1]]['total_amount']);
666 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
667 $this->assertArrayHasKey($membershipPayment['contribution_id'], $contributions['values']);
668 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
669 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
673 * Test submit with a membership block in place.
675 * We are expecting a separate payment for the membership vs the contribution.
677 public function testSubmitMembershipBlockIsSeparatePaymentPaymentProcessorNow() {
678 $mut = new CiviMailUtils($this, TRUE);
679 $this->setUpMembershipContributionPage(TRUE);
680 $processor = Civi\Payment\System
::singleton()->getById($this->_paymentProcessor
['id']);
681 $processor->setDoDirectPaymentResult(['fee_amount' => .72]);
683 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
684 'id' => (int) $this->_ids
['contribution_page'],
686 'billing_first_name' => 'Billy',
687 'billing_middle_name' => 'Goat',
688 'billing_last_name' => 'Gruff',
689 'email-Primary' => 'henry@8th.king',
690 'selectMembership' => $this->_ids
['membership_type'],
691 'payment_processor_id' => $this->_paymentProcessor
['id'],
692 'credit_card_number' => '4111111111111111',
693 'credit_card_type' => 'Visa',
694 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
698 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
699 $contributions = $this->callAPISuccess('contribution', 'get', [
700 'contribution_page_id' => $this->_ids
['contribution_page'],
701 'contribution_status_id' => 1,
703 $this->assertCount(2, $contributions['values']);
704 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
705 $this->assertTrue(in_array($membershipPayment['contribution_id'], array_keys($contributions['values'])));
706 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
707 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
708 $lineItem = $this->callAPISuccessGetSingle('LineItem', ['entity_table' => 'civicrm_membership']);
709 $this->assertEquals($lineItem['entity_id'], $membership['id']);
710 $this->assertEquals($lineItem['contribution_id'], $membershipPayment['contribution_id']);
711 $this->assertEquals($lineItem['qty'], 1);
712 $this->assertEquals($lineItem['unit_price'], 2);
713 $this->assertEquals($lineItem['line_total'], 2);
714 foreach ($contributions['values'] as $contribution) {
715 $this->assertEquals(.72, $contribution['fee_amount']);
716 $this->assertEquals($contribution['total_amount'] - .72, $contribution['net_amount']);
718 // The total string is currently absent & it seems worse with - although at some point
719 // it may have been intended
720 $mut->checkAllMailLog(['$ 2.00', 'Contribution Amount', '$ 10.00'], ['Total:']);
722 $mut->clearMessages();
726 * Test submit with a membership block in place.
728 * Ensure a separate payment for the membership vs the contribution, with
731 * @param string $thousandSeparator
732 * punctuation used to refer to thousands.
734 * @dataProvider getThousandSeparators
736 public function testSubmitMembershipBlockIsSeparatePaymentPaymentProcessorNowChargesCorrectAmounts($thousandSeparator) {
737 $this->setCurrencySeparators($thousandSeparator);
738 $this->setUpMembershipContributionPage(TRUE);
739 $processor = Civi\Payment\System
::singleton()->getById($this->_paymentProcessor
['id']);
740 $processor->setDoDirectPaymentResult(['fee_amount' => .72]);
741 $test_uniq = uniqid();
742 $contributionPageAmount = 10;
744 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
745 'id' => (int) $this->_ids
['contribution_page'],
746 'amount' => $contributionPageAmount,
747 'billing_first_name' => 'Billy',
748 'billing_middle_name' => 'Goat',
749 'billing_last_name' => 'Gruff',
750 'email-Primary' => 'henry@8th.king',
751 'selectMembership' => $this->_ids
['membership_type'],
752 'payment_processor_id' => $this->_paymentProcessor
['id'],
753 'credit_card_number' => '4111111111111111',
754 'credit_card_type' => 'Visa',
755 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
757 'TEST_UNIQ' => $test_uniq,
761 $this->hookClass
->setHook('civicrm_alterPaymentProcessorParams', [$this, 'hook_civicrm_alterPaymentProcessorParams']);
763 $this->callAPISuccess('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
764 $contributions = $this->callAPISuccess('contribution', 'get', [
765 'contribution_page_id' => $this->_ids
['contribution_page'],
766 'contribution_status_id' => 1,
769 $result = civicrm_api3('SystemLog', 'get', [
771 'message' => ['LIKE' => "%{$test_uniq}%"],
773 $this->assertCount(2, $result['values'], "Expected exactly 2 log entries matching {$test_uniq}.");
775 // Examine logged entries to ensure correct values.
776 $contribution_ids = [];
777 $found_membership_amount = $found_contribution_amount = FALSE;
778 foreach ($result['values'] as $value) {
779 list($junk, $json) = explode("$test_uniq:", $value['message']);
780 $logged_contribution = json_decode($json, TRUE);
781 $contribution_ids[] = $logged_contribution['contributionID'];
782 if (!empty($logged_contribution['total_amount'])) {
783 $amount = $logged_contribution['total_amount'];
786 $amount = $logged_contribution['amount'];
789 if ($amount == $this->_membershipBlockAmount
) {
790 $found_membership_amount = TRUE;
792 if ($amount == $contributionPageAmount) {
793 $found_contribution_amount = TRUE;
797 $distinct_contribution_ids = array_unique($contribution_ids);
798 $this->assertCount(2, $distinct_contribution_ids, "Expected exactly 2 log contributions with distinct contributionIDs.");
799 $this->assertTrue($found_contribution_amount, "Expected one log contribution with amount '$contributionPageAmount' (the contribution page amount)");
800 $this->assertTrue($found_membership_amount, "Expected one log contribution with amount '$this->_membershipBlockAmount' (the membership amount)");
804 * Test that when a transaction fails the pending contribution remains.
806 * An activity should also be created. CRM-16417.
808 public function testSubmitPaymentProcessorFailure() {
809 $this->setUpContributionPage();
810 $this->setupPaymentProcessor();
811 $this->createLoggedInUser();
812 $priceFieldID = reset($this->_ids
['price_field']);
813 $priceFieldValueID = reset($this->_ids
['price_field_value']);
815 'price_' . $priceFieldID => $priceFieldValueID,
816 'id' => (int) $this->_ids
['contribution_page'],
818 'payment_processor_id' => 1,
819 'credit_card_number' => '4111111111111111',
820 'credit_card_type' => 'Visa',
821 'credit_card_exp_date' => ['M' => 9, 'Y' => 2008],
825 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
826 $contribution = $this->callAPISuccessGetSingle('contribution', [
827 'contribution_page_id' => $this->_ids
['contribution_page'],
828 'contribution_status_id' => 'Failed',
831 $this->callAPISuccessGetSingle('activity', [
832 'source_record_id' => $contribution['id'],
833 'activity_type_id' => 'Failed Payment',
839 * Test submit recurring (yearly) membership with immediate confirmation (IATS style).
841 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
842 * processor (IATS style - denoted by returning trxn_id)
843 * - the first creates a new membership, completed contribution, in progress recurring. Check these
844 * - create another - end date should be extended
846 public function testSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPaymentYear() {
847 $this->doSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPayment(['duration_unit' => 'year', 'recur_frequency_unit' => 'year']);
851 * Test submit recurring (monthly) membership with immediate confirmation (IATS style).
853 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
854 * processor (IATS style - denoted by returning trxn_id)
855 * - the first creates a new membership, completed contribution, in progress recurring. Check these
856 * - create another - end date should be extended
858 public function testSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPaymentMonth() {
859 $this->doSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPayment(['duration_unit' => 'month', 'recur_frequency_unit' => 'month']);
863 * Test submit recurring (mismatched frequency unit) membership with immediate confirmation (IATS style).
865 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
866 * processor (IATS style - denoted by returning trxn_id)
867 * - the first creates a new membership, completed contribution, in progress recurring. Check these
868 * - create another - end date should be extended
870 //public function testSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPaymentDifferentFrequency() {
871 // $this->doSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPayment(array('duration_unit' => 'year', 'recur_frequency_unit' => 'month'));
875 * Helper function for testSubmitMembershipPriceSetPaymentProcessorRecurInstantPayment*
876 * @param array $params
878 * @throws \CRM_Core_Exception
881 public function doSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPayment($params = []) {
882 $this->params
['is_recur'] = 1;
883 $this->params
['recur_frequency_unit'] = $params['recur_frequency_unit'];
884 $membershipTypeParams['duration_unit'] = $params['duration_unit'];
885 if ($params['recur_frequency_unit'] === $params['duration_unit']) {
886 $durationUnit = $params['duration_unit'];
889 $durationUnit = NULL;
891 $this->setUpMembershipContributionPage(FALSE, FALSE, $membershipTypeParams);
892 $dummyPP = Civi\Payment\System
::singleton()->getByProcessor($this->_paymentProcessor
);
893 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_first_success']);
894 $processor = $dummyPP->getPaymentProcessor();
896 if ($params['recur_frequency_unit'] === $params['duration_unit']) {
897 // Membership will be in "New" state because it will get confirmed as payment matches
898 $expectedMembershipStatus = 1;
901 // Membership will still be in "Pending" state as it won't get confirmed as payment doesn't match
902 $expectedMembershipStatus = 5;
906 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
907 'id' => (int) $this->_ids
['contribution_page'],
909 'billing_first_name' => 'Billy',
910 'billing_middle_name' => 'Goat',
911 'billing_last_name' => 'Gruff',
912 'email' => 'billy@goat.gruff',
913 'selectMembership' => $this->_ids
['membership_type'],
914 'payment_processor_id' => 1,
915 'credit_card_number' => '4111111111111111',
916 'credit_card_type' => 'Visa',
917 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
920 'frequency_interval' => 1,
921 'frequency_unit' => $this->params
['recur_frequency_unit'],
924 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
925 $contribution = $this->callAPISuccess('contribution', 'getsingle', [
926 'contribution_page_id' => $this->_ids
['contribution_page'],
927 'contribution_status_id' => 1,
929 $this->assertEquals($processor['payment_instrument_id'], $contribution['payment_instrument_id']);
931 $this->assertEquals('create_first_success', $contribution['trxn_id']);
932 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
933 $this->assertEquals($membershipPayment['contribution_id'], $contribution['id']);
934 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
935 $this->assertEquals($membership['contact_id'], $contribution['contact_id']);
936 $this->assertEquals($expectedMembershipStatus, $membership['status_id']);
937 $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $contribution['contribution_recur_id']]);
939 $this->callAPISuccess('line_item', 'getsingle', ['contribution_id' => $contribution['id'], 'entity_id' => $membership['id']]);
940 //renew it with processor setting completed - should extend membership
941 $submitParams['contact_id'] = $contribution['contact_id'];
942 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_second_success']);
943 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
944 $this->callAPISuccess('contribution', 'getsingle', [
945 'id' => ['NOT IN' => [$contribution['id']]],
946 'contribution_page_id' => $this->_ids
['contribution_page'],
947 'contribution_status_id' => 1,
949 $renewedMembership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
951 // We only have an end_date if frequency units match, otherwise membership won't be autorenewed and dates won't be calculated.
952 $renewedMembershipEndDate = $this->membershipRenewalDate($durationUnit, $membership['end_date']);
953 $this->assertEquals($renewedMembershipEndDate, $renewedMembership['end_date']);
955 $recurringContribution = $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $contribution['contribution_recur_id']]);
956 $this->assertEquals($processor['payment_instrument_id'], $recurringContribution['payment_instrument_id']);
957 $this->assertEquals(5, $recurringContribution['contribution_status_id']);
961 * Test submit recurring membership with immediate confirmation (IATS style).
963 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
964 * processor (IATS style - denoted by returning trxn_id)
965 * - the first creates a new membership, completed contribution, in progress recurring. Check these
966 * - create another - end date should be extended
968 public function testSubmitMembershipComplexNonPriceSetPaymentPaymentProcessorRecurInstantPayment() {
969 $this->params
['is_recur'] = 1;
970 $this->params
['recur_frequency_unit'] = $membershipTypeParams['duration_unit'] = 'year';
971 // Add a membership so membership & contribution are not both 1.
972 $preExistingMembershipID = $this->contactMembershipCreate(['contact_id' => $this->contactIds
[0]]);
973 $this->setUpMembershipContributionPage(FALSE, FALSE, $membershipTypeParams);
974 $dummyPP = Civi\Payment\System
::singleton()->getByProcessor($this->_paymentProcessor
);
975 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_first_success']);
976 $processor = $dummyPP->getPaymentProcessor();
979 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
980 'price_' . $this->_ids
['price_field']['cont'] => 88,
981 'id' => (int) $this->_ids
['contribution_page'],
983 'billing_first_name' => 'Billy',
984 'billing_middle_name' => 'Goat',
985 'billing_last_name' => 'Gruff',
986 'email' => 'billy@goat.gruff',
987 'selectMembership' => $this->_ids
['membership_type'],
988 'payment_processor_id' => 1,
989 'credit_card_number' => '4111111111111111',
990 'credit_card_type' => 'Visa',
991 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
994 'frequency_interval' => 1,
995 'frequency_unit' => $this->params
['recur_frequency_unit'],
998 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
999 $contribution = $this->callAPISuccess('contribution', 'getsingle', [
1000 'contribution_page_id' => $this->_ids
['contribution_page'],
1001 'contribution_status_id' => 1,
1003 $this->assertEquals($processor['payment_instrument_id'], $contribution['payment_instrument_id']);
1005 $this->assertEquals('create_first_success', $contribution['trxn_id']);
1006 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
1007 $this->assertEquals($membershipPayment['contribution_id'], $contribution['id']);
1008 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
1009 $this->assertEquals($membership['contact_id'], $contribution['contact_id']);
1010 $this->assertEquals(1, $membership['status_id']);
1011 $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $contribution['contribution_recur_id']]);
1013 $lines = $this->callAPISuccess('line_item', 'get', ['sequential' => 1, 'contribution_id' => $contribution['id']]);
1014 $this->assertEquals(2, $lines['count']);
1015 $this->assertEquals('civicrm_membership', $lines['values'][0]['entity_table']);
1016 $this->assertEquals($preExistingMembershipID +
1, $lines['values'][0]['entity_id']);
1017 $this->assertEquals('civicrm_contribution', $lines['values'][1]['entity_table']);
1018 $this->assertEquals($contribution['id'], $lines['values'][1]['entity_id']);
1019 $this->callAPISuccessGetSingle('MembershipPayment', ['contribution_id' => $contribution['id'], 'membership_id' => $preExistingMembershipID +
1]);
1021 //renew it with processor setting completed - should extend membership
1022 $submitParams['contact_id'] = $contribution['contact_id'];
1023 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_second_success']);
1024 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1025 $renewContribution = $this->callAPISuccess('contribution', 'getsingle', [
1026 'id' => ['NOT IN' => [$contribution['id']]],
1027 'contribution_page_id' => $this->_ids
['contribution_page'],
1028 'contribution_status_id' => 1,
1030 $lines = $this->callAPISuccess('line_item', 'get', ['sequential' => 1, 'contribution_id' => $renewContribution['id']]);
1031 $this->assertEquals(2, $lines['count']);
1032 $this->assertEquals('civicrm_membership', $lines['values'][0]['entity_table']);
1033 $this->assertEquals($preExistingMembershipID +
1, $lines['values'][0]['entity_id']);
1034 $this->assertEquals('civicrm_contribution', $lines['values'][1]['entity_table']);
1035 $this->assertEquals($renewContribution['id'], $lines['values'][1]['entity_id']);
1037 $renewedMembership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
1038 $this->assertEquals(date('Y-m-d', strtotime('+ 1 ' . $this->params
['recur_frequency_unit'], strtotime($membership['end_date']))), $renewedMembership['end_date']);
1039 $recurringContribution = $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $contribution['contribution_recur_id']]);
1040 $this->assertEquals($processor['payment_instrument_id'], $recurringContribution['payment_instrument_id']);
1041 $this->assertEquals(5, $recurringContribution['contribution_status_id']);
1045 * Test submit recurring membership with immediate confirmation (IATS style).
1047 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
1048 * processor (IATS style - denoted by returning trxn_id)
1049 * - the first creates a new membership, completed contribution, in progress recurring. Check these
1050 * - create another - end date should be extended
1052 public function testSubmitMembershipComplexPriceSetPaymentPaymentProcessorRecurInstantPayment() {
1053 $this->params
['is_recur'] = 1;
1054 $this->params
['recur_frequency_unit'] = $membershipTypeParams['duration_unit'] = 'year';
1055 // Add a membership so membership & contribution are not both 1.
1056 $preExistingMembershipID = $this->contactMembershipCreate(['contact_id' => $this->contactIds
[0]]);
1057 $this->createPriceSetWithPage();
1058 $this->addSecondOrganizationMembershipToPriceSet();
1059 $this->setupPaymentProcessor();
1061 $dummyPP = Civi\Payment\System
::singleton()->getByProcessor($this->_paymentProcessor
);
1062 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_first_success']);
1063 $processor = $dummyPP->getPaymentProcessor();
1066 'price_' . $this->_ids
['price_field'][0] => $this->_ids
['price_field_value']['cont'],
1067 'price_' . $this->_ids
['price_field']['org1'] => $this->_ids
['price_field_value']['org1'],
1068 'price_' . $this->_ids
['price_field']['org2'] => $this->_ids
['price_field_value']['org2'],
1069 'id' => (int) $this->_ids
['contribution_page'],
1071 'billing_first_name' => 'Billy',
1072 'billing_middle_name' => 'Goat',
1073 'billing_last_name' => 'Gruff',
1074 'email' => 'billy@goat.gruff',
1075 'selectMembership' => NULL,
1076 'payment_processor_id' => 1,
1077 'credit_card_number' => '4111111111111111',
1078 'credit_card_type' => 'Visa',
1079 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
1081 'frequency_interval' => 1,
1082 'frequency_unit' => $this->params
['recur_frequency_unit'],
1085 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
1086 $contribution = $this->callAPISuccess('contribution', 'getsingle', [
1087 'contribution_page_id' => $this->_ids
['contribution_page'],
1088 'contribution_status_id' => 1,
1090 $this->assertEquals($processor['payment_instrument_id'], $contribution['payment_instrument_id']);
1092 $this->assertEquals('create_first_success', $contribution['trxn_id']);
1093 $membershipPayments = $this->callAPISuccess('membership_payment', 'get', [
1095 'contribution_id' => $contribution['id'],
1097 $this->assertEquals(2, $membershipPayments['count']);
1098 $lines = $this->callAPISuccess('line_item', 'get', ['sequential' => 1, 'contribution_id' => $contribution['id']]);
1099 $this->assertEquals(3, $lines['count']);
1100 $this->assertEquals('civicrm_membership', $lines['values'][0]['entity_table']);
1101 $this->assertEquals($preExistingMembershipID +
1, $lines['values'][0]['entity_id']);
1102 $this->assertEquals('civicrm_contribution', $lines['values'][1]['entity_table']);
1103 $this->assertEquals($contribution['id'], $lines['values'][1]['entity_id']);
1104 $this->assertEquals('civicrm_membership', $lines['values'][2]['entity_table']);
1105 $this->assertEquals($preExistingMembershipID +
2, $lines['values'][2]['entity_id']);
1107 $this->callAPISuccessGetSingle('MembershipPayment', ['contribution_id' => $contribution['id'], 'membership_id' => $preExistingMembershipID +
1]);
1108 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $preExistingMembershipID +
1]);
1110 //renew it with processor setting completed - should extend membership
1111 $submitParams['contact_id'] = $contribution['contact_id'];
1112 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_second_success']);
1113 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1114 $renewContribution = $this->callAPISuccess('contribution', 'getsingle', [
1115 'id' => ['NOT IN' => [$contribution['id']]],
1116 'contribution_page_id' => $this->_ids
['contribution_page'],
1117 'contribution_status_id' => 1,
1119 $lines = $this->callAPISuccess('line_item', 'get', ['sequential' => 1, 'contribution_id' => $renewContribution['id']]);
1120 $this->assertEquals(3, $lines['count']);
1121 $this->assertEquals('civicrm_membership', $lines['values'][0]['entity_table']);
1122 $this->assertEquals($preExistingMembershipID +
1, $lines['values'][0]['entity_id']);
1123 $this->assertEquals('civicrm_contribution', $lines['values'][1]['entity_table']);
1124 $this->assertEquals($renewContribution['id'], $lines['values'][1]['entity_id']);
1126 $renewedMembership = $this->callAPISuccessGetSingle('membership', ['id' => $preExistingMembershipID +
1]);
1127 $this->assertEquals(date('Y-m-d', strtotime('+ 1 ' . $this->params
['recur_frequency_unit'], strtotime($membership['end_date']))), $renewedMembership['end_date']);
1131 * Extend the price set with a second organisation's membership.
1133 public function addSecondOrganizationMembershipToPriceSet() {
1134 $organization2ID = $this->organizationCreate();
1135 $membershipTypes = $this->callAPISuccess('MembershipType', 'get', []);
1136 $this->_ids
['membership_type'] = array_keys($membershipTypes['values']);
1137 $this->_ids
['membership_type']['org2'] = $this->membershipTypeCreate(['contact_id' => $organization2ID, 'name' => 'Org 2']);
1138 $priceField = $this->callAPISuccess('PriceField', 'create', [
1139 'price_set_id' => $this->_ids
['price_set'],
1140 'html_type' => 'Radio',
1141 'name' => 'Org1 Price',
1142 'label' => 'Org1Price',
1144 $this->_ids
['price_field']['org1'] = $priceField['id'];
1146 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
1147 'name' => 'org1 amount',
1148 'label' => 'org 1 Amount',
1150 'financial_type_id' => 'Member Dues',
1151 'format.only_id' => TRUE,
1152 'membership_type_id' => reset($this->_ids
['membership_type']),
1153 'price_field_id' => $priceField['id'],
1155 $this->_ids
['price_field_value']['org1'] = $priceFieldValue;
1157 $priceField = $this->callAPISuccess('PriceField', 'create', [
1158 'price_set_id' => $this->_ids
['price_set'],
1159 'html_type' => 'Radio',
1160 'name' => 'Org2 Price',
1161 'label' => 'Org2Price',
1163 $this->_ids
['price_field']['org2'] = $priceField['id'];
1165 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
1166 'name' => 'org2 amount',
1167 'label' => 'org 2 Amount',
1169 'financial_type_id' => 'Member Dues',
1170 'format.only_id' => TRUE,
1171 'membership_type_id' => $this->_ids
['membership_type']['org2'],
1172 'price_field_id' => $priceField['id'],
1174 $this->_ids
['price_field_value']['org2'] = $priceFieldValue;
1179 * Test submit recurring membership with immediate confirmation (IATS style).
1181 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
1182 * processor (IATS style - denoted by returning trxn_id)
1183 * - the first creates a new membership, completed contribution, in progress recurring. Check these
1184 * - create another - end date should be extended
1186 public function testSubmitMembershipPriceSetPaymentPaymentProcessorSeparatePaymentRecurInstantPayment() {
1188 $this->setUpMembershipContributionPage(TRUE);
1189 $dummyPP = Civi\Payment\System
::singleton()->getByProcessor($this->_paymentProcessor
);
1190 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_first_success']);
1193 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
1194 'id' => (int) $this->_ids
['contribution_page'],
1196 'billing_first_name' => 'Billy',
1197 'billing_middle_name' => 'Goat',
1198 'billing_last_name' => 'Gruff',
1199 'email' => 'billy@goat.gruff',
1200 'selectMembership' => $this->_ids
['membership_type'],
1201 'payment_processor_id' => 1,
1202 'credit_card_number' => '4111111111111111',
1203 'credit_card_type' => 'Visa',
1204 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
1207 'auto_renew' => TRUE,
1208 'frequency_interval' => 1,
1209 'frequency_unit' => 'month',
1212 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
1213 $contribution = $this->callAPISuccess('contribution', 'get', [
1214 'contribution_page_id' => $this->_ids
['contribution_page'],
1215 'contribution_status_id' => 1,
1218 $this->assertEquals(2, $contribution['count']);
1219 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
1220 $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
1221 $this->assertNotEmpty($contribution['values'][$membershipPayment['contribution_id']]['contribution_recur_id']);
1222 $this->callAPISuccess('contribution_recur', 'getsingle', []);
1226 * Test submit recurring membership with delayed confirmation (Authorize.net style)
1227 * - we process 2 membership transactions against with a recurring contribution against a contribution page with a delayed
1228 * processor (Authorize.net style - denoted by NOT returning trxn_id)
1229 * - the first creates a pending membership, pending contribution, penging recurring. Check these
1230 * - complete the transaction
1231 * - create another - end date should NOT be extended
1233 public function testSubmitMembershipPriceSetPaymentPaymentProcessorRecurDelayed() {
1234 $this->params
['is_recur'] = 1;
1235 $this->params
['recur_frequency_unit'] = $membershipTypeParams['duration_unit'] = 'year';
1236 $this->setUpMembershipContributionPage();
1237 $dummyPP = Civi\Payment\System
::singleton()->getByProcessor($this->_paymentProcessor
);
1238 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 2]);
1239 $this->membershipTypeCreate(['name' => 'Student']);
1241 // Add a contribution & a couple of memberships so the id will not be 1 & will differ from membership id.
1242 // This saves us from 'accidental success'.
1243 $this->contributionCreate(['contact_id' => $this->contactIds
[0]]);
1244 $this->contactMembershipCreate(['contact_id' => $this->contactIds
[0]]);
1245 $this->contactMembershipCreate(['contact_id' => $this->contactIds
[0], 'membership_type_id' => 'Student']);
1248 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
1249 'id' => (int) $this->_ids
['contribution_page'],
1251 'billing_first_name' => 'Billy',
1252 'billing_middle_name' => 'Goat',
1253 'billing_last_name' => 'Gruff',
1254 'email' => 'billy@goat.gruff',
1255 'selectMembership' => $this->_ids
['membership_type'],
1256 'payment_processor_id' => 1,
1257 'credit_card_number' => '4111111111111111',
1258 'credit_card_type' => 'Visa',
1259 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
1262 'frequency_interval' => 1,
1263 'frequency_unit' => $this->params
['recur_frequency_unit'],
1266 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
1267 $contribution = $this->callAPISuccess('contribution', 'getsingle', [
1268 'contribution_page_id' => $this->_ids
['contribution_page'],
1269 'contribution_status_id' => 2,
1272 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
1273 $this->assertEquals($membershipPayment['contribution_id'], $contribution['id']);
1274 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
1275 $this->assertEquals($membership['contact_id'], $contribution['contact_id']);
1276 $this->assertEquals(5, $membership['status_id']);
1278 $line = $this->callAPISuccess('line_item', 'getsingle', ['contribution_id' => $contribution['id']]);
1279 $this->assertEquals('civicrm_membership', $line['entity_table']);
1280 $this->assertEquals($membership['id'], $line['entity_id']);
1282 $this->callAPISuccess('contribution', 'completetransaction', [
1283 'id' => $contribution['id'],
1284 'trxn_id' => 'ipn_called',
1285 'payment_processor_id' => $this->_paymentProcessor
['id'],
1287 $line = $this->callAPISuccess('line_item', 'getsingle', ['contribution_id' => $contribution['id']]);
1288 $this->assertEquals('civicrm_membership', $line['entity_table']);
1289 $this->assertEquals($membership['id'], $line['entity_id']);
1291 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
1292 //renew it with processor setting completed - should extend membership
1293 $submitParams = array_merge($submitParams, [
1294 'contact_id' => $contribution['contact_id'],
1296 'frequency_interval' => 1,
1297 'frequency_unit' => $this->params
['recur_frequency_unit'],
1300 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 2]);
1301 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1302 $newContribution = $this->callAPISuccess('contribution', 'getsingle', [
1304 'NOT IN' => [$contribution['id']],
1306 'contribution_page_id' => $this->_ids
['contribution_page'],
1307 'contribution_status_id' => 2,
1309 $line = $this->callAPISuccess('line_item', 'getsingle', ['contribution_id' => $newContribution['id']]);
1310 $this->assertEquals('civicrm_membership', $line['entity_table']);
1311 $this->assertEquals($membership['id'], $line['entity_id']);
1313 $renewedMembership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
1314 //no renewal as the date hasn't changed
1315 $this->assertEquals($membership['end_date'], $renewedMembership['end_date']);
1316 $recurringContribution = $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $newContribution['contribution_recur_id']]);
1317 $this->assertEquals(2, $recurringContribution['contribution_status_id']);
1321 * Test non-recur contribution with membership payment
1323 public function testSubmitMembershipIsSeparatePaymentNotRecur() {
1324 //Create recur contribution page.
1325 $this->setUpMembershipContributionPage(TRUE, TRUE);
1326 $dummyPP = Civi\Payment\System
::singleton()->getByProcessor($this->_paymentProcessor
);
1327 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_first_success']);
1329 //Sumbit payment with recur disabled.
1331 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
1332 'id' => (int) $this->_ids
['contribution_page'],
1334 'frequency_interval' => 1,
1335 'frequency_unit' => 'month',
1336 'billing_first_name' => 'Billy',
1337 'billing_middle_name' => 'Goat',
1338 'billing_last_name' => 'Gruff',
1339 'email' => 'billy@goat.gruff',
1340 'selectMembership' => $this->_ids
['membership_type'],
1341 'payment_processor_id' => 1,
1342 'credit_card_number' => '4111111111111111',
1343 'credit_card_type' => 'Visa',
1344 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
1348 //Assert if recur contribution is created.
1349 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1350 $recur = $this->callAPISuccess('contribution_recur', 'get', []);
1351 $this->assertEmpty($recur['count']);
1355 * Set up membership contribution page.
1356 * @param bool $isSeparatePayment
1357 * @param bool $isRecur
1358 * @param array $membershipTypeParams Parameters to pass to membershiptype.create API
1360 public function setUpMembershipContributionPage($isSeparatePayment = FALSE, $isRecur = FALSE, $membershipTypeParams = []) {
1361 $this->setUpMembershipBlockPriceSet($membershipTypeParams);
1362 $this->setupPaymentProcessor();
1363 $this->setUpContributionPage($isRecur);
1365 $this->callAPISuccess('membership_block', 'create', [
1366 'entity_id' => $this->_ids
['contribution_page'],
1367 'entity_table' => 'civicrm_contribution_page',
1368 'is_required' => TRUE,
1369 'is_active' => TRUE,
1370 'is_separate_payment' => $isSeparatePayment,
1371 'membership_type_default' => $this->_ids
['membership_type'],
1376 * Set up pledge block.
1378 public function setUpPledgeBlock() {
1380 'entity_table' => 'civicrm_contribution_page',
1381 'entity_id' => $this->_ids
['contribution_page'],
1382 'pledge_frequency_unit' => 'week',
1383 'is_pledge_interval' => 0,
1384 'pledge_start_date' => json_encode(['calendar_date' => date('Ymd', strtotime("+1 month"))]),
1386 $pledgeBlock = CRM_Pledge_BAO_PledgeBlock
::create($params);
1387 $this->_ids
['pledge_block_id'] = $pledgeBlock->id
;
1391 * The default data set does not include a complete default membership price set - not quite sure why.
1393 * This function ensures it exists & populates $this->_ids with it's data
1395 public function setUpMembershipBlockPriceSet($membershipTypeParams = []) {
1396 $this->_ids
['price_set'][] = $this->callAPISuccess('price_set', 'getvalue', [
1397 'name' => 'default_membership_type_amount',
1400 if (empty($this->_ids
['membership_type'])) {
1401 $membershipTypeParams = array_merge([
1403 ], $membershipTypeParams);
1404 $this->_ids
['membership_type'] = [$this->membershipTypeCreate($membershipTypeParams)];
1406 $priceField = $this->callAPISuccess('price_field', 'create', [
1407 'price_set_id' => reset($this->_ids
['price_set']),
1408 'name' => 'membership_amount',
1409 'label' => 'Membership Amount',
1410 'html_type' => 'Radio',
1413 $this->_ids
['price_field'][] = $priceField['id'];
1415 foreach ($this->_ids
['membership_type'] as $membershipTypeID) {
1416 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
1417 'name' => 'membership_amount',
1418 'label' => 'Membership Amount',
1419 'amount' => $this->_membershipBlockAmount
,
1420 'financial_type_id' => 'Donation',
1421 'format.only_id' => TRUE,
1422 'membership_type_id' => $membershipTypeID,
1423 'price_field_id' => $priceField['id'],
1425 $this->_ids
['price_field_value'][] = $priceFieldValue;
1427 if (!empty($this->_ids
['membership_type']['org2'])) {
1428 $priceField = $this->callAPISuccess('price_field', 'create', [
1429 'price_set_id' => reset($this->_ids
['price_set']),
1430 'name' => 'membership_org2',
1431 'label' => 'Membership Org2',
1432 'html_type' => 'Checkbox',
1435 $this->_ids
['price_field']['org2'] = $priceField['id'];
1437 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
1438 'name' => 'membership_org2',
1439 'label' => 'Membership org 2',
1441 'financial_type_id' => 'Member Dues',
1442 'format.only_id' => TRUE,
1443 'membership_type_id' => $this->_ids
['membership_type']['org2'],
1444 'price_field_id' => $priceField['id'],
1446 $this->_ids
['price_field_value']['org2'] = $priceFieldValue;
1448 $priceField = $this->callAPISuccess('price_field', 'create', [
1449 'price_set_id' => reset($this->_ids
['price_set']),
1450 'name' => 'Contribution',
1451 'label' => 'Contribution',
1452 'html_type' => 'Text',
1454 'is_enter_qty' => 1,
1456 $this->_ids
['price_field']['cont'] = $priceField['id'];
1457 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
1458 'name' => 'contribution',
1459 'label' => 'Give me money',
1461 'financial_type_id' => 'Donation',
1462 'format.only_id' => TRUE,
1463 'price_field_id' => $priceField['id'],
1465 $this->_ids
['price_field_value'][] = $priceFieldValue;
1469 * Add text field other amount to the price set.
1471 public function addOtherAmountFieldToMembershipPriceSet() {
1472 $this->_ids
['price_field']['other_amount'] = $this->callAPISuccess('price_field', 'create', [
1473 'price_set_id' => reset($this->_ids
['price_set']),
1474 'name' => 'other_amount',
1475 'label' => 'Other Amount',
1476 'html_type' => 'Text',
1477 'format.only_id' => TRUE,
1480 $this->_ids
['price_field_value']['other_amount'] = $this->callAPISuccess('price_field_value', 'create', [
1481 'financial_type_id' => 'Donation',
1482 'format.only_id' => TRUE,
1483 'label' => 'Other Amount',
1485 'price_field_id' => $this->_ids
['price_field']['other_amount'],
1490 * Help function to set up contribution page with some defaults.
1491 * @param bool $isRecur
1493 public function setUpContributionPage($isRecur = FALSE) {
1495 $this->params
['is_recur'] = 1;
1496 $this->params
['recur_frequency_unit'] = 'month';
1498 $contributionPageResult = $this->callAPISuccess($this->_entity
, 'create', $this->params
);
1499 if (empty($this->_ids
['price_set'])) {
1500 $priceSet = $this->callAPISuccess('price_set', 'create', $this->_priceSetParams
);
1501 $this->_ids
['price_set'][] = $priceSet['id'];
1503 $priceSetID = reset($this->_ids
['price_set']);
1504 CRM_Price_BAO_PriceSet
::addTo('civicrm_contribution_page', $contributionPageResult['id'], $priceSetID);
1506 if (empty($this->_ids
['price_field'])) {
1507 $priceField = $this->callAPISuccess('price_field', 'create', [
1508 'price_set_id' => $priceSetID,
1509 'label' => 'Goat Breed',
1510 'html_type' => 'Radio',
1512 $this->_ids
['price_field'] = [$priceField['id']];
1514 if (empty($this->_ids
['price_field_value'])) {
1515 $this->callAPISuccess('price_field_value', 'create', [
1516 'price_set_id' => $priceSetID,
1517 'price_field_id' => $priceField['id'],
1518 'label' => 'Long Haired Goat',
1519 'financial_type_id' => 'Donation',
1521 'non_deductible_amount' => 15,
1523 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
1524 'price_set_id' => $priceSetID,
1525 'price_field_id' => $priceField['id'],
1526 'label' => 'Shoe-eating Goat',
1527 'financial_type_id' => 'Donation',
1529 'non_deductible_amount' => 5,
1531 $this->_ids
['price_field_value'] = [$priceFieldValue['id']];
1533 $this->_ids
['price_field_value']['cheapskate'] = $this->callAPISuccess('price_field_value', 'create', [
1534 'price_set_id' => $priceSetID,
1535 'price_field_id' => $priceField['id'],
1536 'label' => 'Stingy Goat',
1537 'financial_type_id' => 'Donation',
1539 'non_deductible_amount' => 0,
1542 $this->_ids
['contribution_page'] = $contributionPageResult['id'];
1546 * Helper function to set up contribution page which can be used to purchase a
1547 * membership type for different intervals.
1549 public function setUpMultiIntervalMembershipContributionPage() {
1550 $this->setupPaymentProcessor();
1551 $contributionPage = $this->callAPISuccess($this->_entity
, 'create', $this->params
);
1552 $this->_ids
['contribution_page'] = $contributionPage['id'];
1554 $this->_ids
['membership_type'] = $this->membershipTypeCreate([
1557 'duration_unit' => 'month',
1560 $priceSet = civicrm_api3('PriceSet', 'create', [
1561 'is_quick_config' => 0,
1562 'extends' => 'CiviMember',
1563 'financial_type_id' => 'Member Dues',
1564 'title' => 'CRM-21177',
1566 $this->_ids
['price_set'] = $priceSet['id'];
1568 $priceField = $this->callAPISuccess('price_field', 'create', [
1569 'price_set_id' => $this->_ids
['price_set'],
1570 'name' => 'membership_type',
1571 'label' => 'Membership Type',
1572 'html_type' => 'Radio',
1574 $this->_ids
['price_field'] = $priceField['id'];
1576 $priceFieldValueMonthly = $this->callAPISuccess('price_field_value', 'create', [
1577 'name' => 'CRM-21177_Monthly',
1578 'label' => 'CRM-21177 - Monthly',
1580 'membership_num_terms' => 1,
1581 'membership_type_id' => $this->_ids
['membership_type'],
1582 'price_field_id' => $this->_ids
['price_field'],
1583 'financial_type_id' => 'Member Dues',
1585 $this->_ids
['price_field_value_monthly'] = $priceFieldValueMonthly['id'];
1587 $priceFieldValueYearly = $this->callAPISuccess('price_field_value', 'create', [
1588 'name' => 'CRM-21177_Yearly',
1589 'label' => 'CRM-21177 - Yearly',
1591 'membership_num_terms' => 12,
1592 'membership_type_id' => $this->_ids
['membership_type'],
1593 'price_field_id' => $this->_ids
['price_field'],
1594 'financial_type_id' => 'Member Dues',
1596 $this->_ids
['price_field_value_yearly'] = $priceFieldValueYearly['id'];
1598 CRM_Price_BAO_PriceSet
::addTo('civicrm_contribution_page', $this->_ids
['contribution_page'], $this->_ids
['price_set']);
1600 $this->callAPISuccess('membership_block', 'create', [
1601 'entity_id' => $this->_ids
['contribution_page'],
1602 'entity_table' => 'civicrm_contribution_page',
1603 'is_required' => TRUE,
1604 'is_separate_payment' => FALSE,
1605 'is_active' => TRUE,
1606 'membership_type_default' => $this->_ids
['membership_type'],
1611 * Test submit with a membership block in place.
1613 public function testSubmitMultiIntervalMembershipContributionPage() {
1614 $this->setUpMultiIntervalMembershipContributionPage();
1616 'price_' . $this->_ids
['price_field'] => $this->_ids
['price_field_value_monthly'],
1617 'id' => (int) $this->_ids
['contribution_page'],
1619 'first_name' => 'Billy',
1620 'last_name' => 'Gruff',
1621 'email' => 'billy@goat.gruff',
1622 'payment_processor_id' => $this->_ids
['payment_processor'],
1623 'credit_card_number' => '4111111111111111',
1624 'credit_card_type' => 'Visa',
1625 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
1629 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1631 $submitParams['price_' . $this->_ids
['price_field']] = $this->_ids
['price_field_value_yearly'];
1632 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1634 $contribution = $this->callAPISuccess('Contribution', 'get', [
1635 'contribution_page_id' => $this->_ids
['contribution_page'],
1637 'api.ContributionRecur.getsingle' => [],
1639 $this->assertEquals(1, $contribution['values'][0]['api.ContributionRecur.getsingle']['frequency_interval']);
1640 //$this->assertEquals(12, $contribution['values'][1]['api.ContributionRecur.getsingle']['frequency_interval']);
1643 public static function setUpBeforeClass() {
1644 // put stuff here that should happen before all tests in this unit
1648 * @throws \Exception
1650 public static function tearDownAfterClass() {
1651 $tablesToTruncate = [
1653 'civicrm_financial_type',
1654 'civicrm_contribution',
1655 'civicrm_contribution_page',
1657 $unitTest = new CiviUnitTestCase();
1658 $unitTest->quickCleanup($tablesToTruncate);
1662 * Create a payment processor instance.
1664 protected function setupPaymentProcessor() {
1665 $this->params
['payment_processor_id'] = $this->_ids
['payment_processor'] = $this->paymentProcessorCreate([
1666 'payment_processor_type_id' => 'Dummy',
1667 'class_name' => 'Payment_Dummy',
1668 'billing_mode' => 1,
1670 $this->_paymentProcessor
= $this->callAPISuccess('payment_processor', 'getsingle', ['id' => $this->params
['payment_processor_id']]);
1674 * Test submit recurring pledge.
1676 * - 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.
1678 public function testSubmitPledgePaymentPaymentProcessorRecurFuturePayment() {
1679 $this->params
['adjust_recur_start_date'] = TRUE;
1680 $this->params
['is_pay_later'] = FALSE;
1681 $this->setUpContributionPage();
1682 $this->setUpPledgeBlock();
1683 $this->setupPaymentProcessor();
1684 $dummyPP = Civi\Payment\System
::singleton()->getByProcessor($this->_paymentProcessor
);
1685 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_first_success']);
1688 'id' => (int) $this->_ids
['contribution_page'],
1690 'billing_first_name' => 'Billy',
1691 'billing_middle_name' => 'Goat',
1692 'billing_last_name' => 'Gruff',
1693 'email' => 'billy@goat.gruff',
1694 'payment_processor_id' => 1,
1695 'credit_card_number' => '4111111111111111',
1696 'credit_card_type' => 'Visa',
1697 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
1699 'pledge_frequency_interval' => 1,
1700 'pledge_frequency_unit' => 'week',
1701 'pledge_installments' => 3,
1702 'is_pledge' => TRUE,
1703 'pledge_block_id' => (int) $this->_ids
['pledge_block_id'],
1706 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
1708 // Check if contribution created.
1709 $contribution = $this->callAPISuccess('contribution', 'getsingle', [
1710 'contribution_page_id' => $this->_ids
['contribution_page'],
1711 // Will be pending when actual payment processor is used (dummy processor does not support future payments).
1712 'contribution_status_id' => 'Completed',
1715 $this->assertEquals('create_first_success', $contribution['trxn_id']);
1717 // Check if pledge created.
1718 $pledge = $this->callAPISuccess('pledge', 'getsingle', []);
1719 $this->assertEquals(date('Ymd', strtotime($pledge['pledge_start_date'])), date('Ymd', strtotime("+1 month")));
1720 $this->assertEquals($pledge['pledge_amount'], 300.00);
1722 // Check if pledge payments created.
1724 'pledge_id' => $pledge['id'],
1726 $pledgePayment = $this->callAPISuccess('pledge_payment', 'get', $params);
1727 $this->assertEquals($pledgePayment['count'], 3);
1728 $this->assertEquals(date('Ymd', strtotime($pledgePayment['values'][1]['scheduled_date'])), date('Ymd', strtotime("+1 month")));
1729 $this->assertEquals($pledgePayment['values'][1]['scheduled_amount'], 100.00);
1730 // Will be pending when actual payment processor is used (dummy processor does not support future payments).
1731 $this->assertEquals($pledgePayment['values'][1]['status_id'], 1);
1733 // Check contribution recur record.
1734 $recur = $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $contribution['contribution_recur_id']]);
1735 $this->assertEquals(date('Ymd', strtotime($recur['start_date'])), date('Ymd', strtotime("+1 month")));
1736 $this->assertEquals($recur['amount'], 100.00);
1737 // In progress status.
1738 $this->assertEquals($recur['contribution_status_id'], 5);
1742 * Test submit pledge payment.
1744 * - test submitting a pledge payment using contribution form.
1746 public function testSubmitPledgePayment() {
1747 $this->testSubmitPledgePaymentPaymentProcessorRecurFuturePayment();
1748 $pledge = $this->callAPISuccess('pledge', 'getsingle', []);
1750 'pledge_id' => $pledge['id'],
1753 'id' => (int) $pledge['pledge_contribution_page_id'],
1754 'pledge_amount' => [2 => 1],
1755 'billing_first_name' => 'Billy',
1756 'billing_middle_name' => 'Goat',
1757 'billing_last_name' => 'Gruff',
1758 'email' => 'billy@goat.gruff',
1759 'payment_processor_id' => 1,
1760 'credit_card_number' => '4111111111111111',
1761 'credit_card_type' => 'Visa',
1762 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
1764 'pledge_id' => $pledge['id'],
1765 'cid' => $pledge['contact_id'],
1766 'contact_id' => $pledge['contact_id'],
1768 'is_pledge' => TRUE,
1769 'pledge_block_id' => $this->_ids
['pledge_block_id'],
1771 $pledgePayment = $this->callAPISuccess('pledge_payment', 'get', $params);
1772 $this->assertEquals($pledgePayment['values'][2]['status_id'], 2);
1774 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
1776 // Check if contribution created.
1777 $contribution = $this->callAPISuccess('contribution', 'getsingle', [
1778 'contribution_page_id' => $pledge['pledge_contribution_page_id'],
1779 'contribution_status_id' => 'Completed',
1780 'contact_id' => $pledge['contact_id'],
1781 'contribution_recur_id' => ['IS NULL' => 1],
1784 $this->assertEquals(100.00, $contribution['total_amount']);
1785 $pledgePayment = $this->callAPISuccess('pledge_payment', 'get', $params);
1786 $this->assertEquals($pledgePayment['values'][2]['status_id'], 1, "This pledge payment should have been completed");
1787 $this->assertEquals($pledgePayment['values'][2]['contribution_id'], $contribution['id']);
1791 * Test form submission with multiple option price set.
1793 * @param string $thousandSeparator
1794 * punctuation used to refer to thousands.
1796 * @dataProvider getThousandSeparators
1798 public function testSubmitContributionPageWithPriceSet($thousandSeparator) {
1799 $this->setCurrencySeparators($thousandSeparator);
1800 $this->_priceSetParams
['is_quick_config'] = 0;
1801 $this->setUpContributionPage();
1803 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
1804 'id' => (int) $this->_ids
['contribution_page'],
1806 'first_name' => 'Billy',
1807 'last_name' => 'Gruff',
1808 'email' => 'billy@goat.gruff',
1809 'is_pay_later' => TRUE,
1811 $this->addPriceFields($submitParams);
1813 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1814 $contribution = $this->callAPISuccessGetSingle('contribution', [
1815 'contribution_page_id' => $this->_ids
['contribution_page'],
1816 'contribution_status_id' => 'Pending',
1818 $this->assertEquals(80, $contribution['total_amount']);
1819 $lineItems = $this->callAPISuccess('LineItem', 'get', [
1820 'contribution_id' => $contribution['id'],
1822 $this->assertEquals(3, $lineItems['count']);
1823 $totalLineAmount = 0;
1824 foreach ($lineItems['values'] as $lineItem) {
1825 $totalLineAmount = $totalLineAmount +
$lineItem['line_total'];
1827 $this->assertEquals(80, $totalLineAmount);
1831 * Function to add additional price fields to priceset.
1832 * @param array $params
1834 public function addPriceFields(&$params) {
1835 $priceSetID = reset($this->_ids
['price_set']);
1836 $priceField = $this->callAPISuccess('price_field', 'create', [
1837 'price_set_id' => $priceSetID,
1838 'label' => 'Chicken Breed',
1839 'html_type' => 'CheckBox',
1841 $priceFieldValue1 = $this->callAPISuccess('price_field_value', 'create', [
1842 'price_set_id' => $priceSetID,
1843 'price_field_id' => $priceField['id'],
1844 'label' => 'Shoe-eating chicken -1',
1845 'financial_type_id' => 'Donation',
1848 $priceFieldValue2 = $this->callAPISuccess('price_field_value', 'create', [
1849 'price_set_id' => $priceSetID,
1850 'price_field_id' => $priceField['id'],
1851 'label' => 'Shoe-eating chicken -2',
1852 'financial_type_id' => 'Donation',
1855 $params['price_' . $priceField['id']] = [
1856 $priceFieldValue1['id'] => 1,
1857 $priceFieldValue2['id'] => 1,
1862 * Test Tax Amount is calculated properly when using PriceSet with Field Type = Text/Numeric Quantity
1864 * @param string $thousandSeparator
1865 * punctuation used to refer to thousands.
1867 * @dataProvider getThousandSeparators
1869 public function testSubmitContributionPageWithPriceSetQuantity($thousandSeparator) {
1870 $this->setCurrencySeparators($thousandSeparator);
1871 $this->_priceSetParams
['is_quick_config'] = 0;
1872 $this->enableTaxAndInvoicing();
1873 $financialType = $this->createFinancialType();
1874 $financialTypeId = $financialType['id'];
1875 // 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%
1876 $this->relationForFinancialTypeWithFinancialAccount($financialType['id'], 5);
1878 $this->setUpContributionPage();
1880 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
1881 'id' => (int) $this->_ids
['contribution_page'],
1882 'first_name' => 'J',
1884 'email' => 'JT@ohcanada.ca',
1885 'is_pay_later' => TRUE,
1886 'receive_date' => date('Y-m-d H:i:s'),
1889 // Create PriceSet/PriceField
1890 $priceSetID = reset($this->_ids
['price_set']);
1891 $priceField = $this->callAPISuccess('price_field', 'create', [
1892 'price_set_id' => $priceSetID,
1893 'label' => 'Printing Rights',
1894 'html_type' => 'Text',
1896 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
1897 'price_set_id' => $priceSetID,
1898 'price_field_id' => $priceField['id'],
1899 'label' => 'Printing Rights',
1900 'financial_type_id' => $financialTypeId,
1901 'amount' => '16.95',
1903 $priceFieldId = $priceField['id'];
1905 // Set quantity for our test
1906 $submitParams['price_' . $priceFieldId] = 180;
1908 // contribution_page submit requires amount and tax_amount - and that's ok we're not testing that - we're testing at the LineItem level
1909 $submitParams['amount'] = $this->formatMoneyInput(180 * 16.95);
1910 // This is the correct Tax Amount - use it later to compare to what the CiviCRM Core came up with at the LineItem level
1911 $submitParams['tax_amount'] = $this->formatMoneyInput(180 * 16.95 * 0.10);
1913 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1914 $contribution = $this->callAPISuccessGetSingle('contribution', [
1915 'contribution_page_id' => $this->_ids
['contribution_page'],
1918 // Retrieve the lineItem that belongs to the Printing Rights and check the tax_amount CiviCRM Core calculated for it
1919 $lineItem = $this->callAPISuccessGetSingle('LineItem', [
1920 'contribution_id' => $contribution['id'],
1921 'label' => 'Printing Rights',
1924 $lineItem_TaxAmount = round($lineItem['tax_amount'], 2);
1926 $this->assertEquals($lineItem['line_total'], $contribution['total_amount'], 'Contribution total should match line total');
1927 $this->assertEquals($lineItem_TaxAmount, round(180 * 16.95 * 0.10, 2), 'Wrong Sales Tax Amount is calculated and stored.');
1931 * Test validating a contribution page submit.
1933 public function testValidate() {
1934 $this->setUpContributionPage();
1935 $errors = $this->callAPISuccess('ContributionPage', 'validate', array_merge($this->getBasicSubmitParams(), ['action' => 'submit']))['values'];
1936 $this->assertEmpty($errors);
1940 * Test validating a contribution page submit in POST context.
1942 * A likely use case for the validation is when the is being submitted and some handling is
1943 * to be done before processing but the validity of input needs to be checked first.
1945 * For example Paypal Checkout will replace the confirm button with it's own but we are able to validate
1946 * before paypal launches it's modal. In this case the $_REQUEST is post but we need validation to succeed.
1948 public function testValidatePost() {
1949 $_SERVER['REQUEST_METHOD'] = 'POST';
1950 $this->setUpContributionPage();
1951 $errors = $this->callAPISuccess('ContributionPage', 'validate', array_merge($this->getBasicSubmitParams(), ['action' => 'submit']))['values'];
1952 $this->assertEmpty($errors);
1953 unset($_SERVER['REQUEST_METHOD']);
1957 * Test that an error is generated if required fields are not submitted.
1959 public function testValidateOutputOnMissingRecurFields() {
1960 $this->params
['is_recur_interval'] = 1;
1961 $this->setUpContributionPage(TRUE);
1962 $submitParams = array_merge($this->getBasicSubmitParams(), ['action' => 'submit']);
1963 $submitParams['is_recur'] = 1;
1964 $submitParams['frequency_interval'] = '';
1965 $submitParams['frequency_unit'] = '';
1966 $errors = $this->callAPISuccess('ContributionPage', 'validate', $submitParams)['values'];
1967 $this->assertEquals('Please enter a number for how often you want to make this recurring contribution (EXAMPLE: Every 3 months).', $errors['frequency_interval']);
1971 * Implements hook_civicrm_alterPaymentProcessorParams().
1973 * @throws \Exception
1975 public function hook_civicrm_alterPaymentProcessorParams($paymentObj, &$rawParams, &$cookedParams) {
1976 // Ensure total_amount are the same if they're both given.
1977 $total_amount = CRM_Utils_Array
::value('total_amount', $rawParams);
1978 $amount = CRM_Utils_Array
::value('amount', $rawParams);
1979 if (!empty($total_amount) && !empty($amount) && $total_amount != $amount) {
1980 throw new Exception("total_amount '$total_amount' and amount '$amount' differ.");
1983 // Log parameters for later debugging and testing.
1984 $message = __FUNCTION__
. ": {$rawParams['TEST_UNIQ']}:";
1985 $log_params = array_intersect_key($rawParams, [
1987 'total_amount' => 1,
1988 'contributionID' => 1,
1990 $message .= json_encode($log_params);
1991 $log = new CRM_Utils_SystemLogger();
1992 $log->debug($message, $_REQUEST);
1996 * Get the params for a basic simple submit.
2000 protected function getBasicSubmitParams() {
2001 $priceFieldID = reset($this->_ids
['price_field']);
2002 $priceFieldValueID = reset($this->_ids
['price_field_value']);
2004 'price_' . $priceFieldID => $priceFieldValueID,
2005 'id' => (int) $this->_ids
['contribution_page'],
2007 'priceSetId' => $this->_ids
['price_set'][0],
2008 'payment_processor_id' => 0,
2010 return $submitParams;