3 +--------------------------------------------------------------------+
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2020 |
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 'pay_later_text' => 'Send check',
72 'is_monetary' => TRUE,
73 'is_email_receipt' => TRUE,
74 'receipt_from_email' => 'yourconscience@donate.com',
75 'receipt_from_name' => 'Ego Freud',
78 $this->_priceSetParams
= [
79 'is_quick_config' => 1,
80 'extends' => 'CiviContribute',
81 'financial_type_id' => 'Donation',
86 public function tearDown() {
87 foreach ($this->contactIds
as $id) {
88 $this->callAPISuccess('contact', 'delete', ['id' => $id]);
90 $this->quickCleanUpFinancialEntities();
96 * @dataProvider versionThreeAndFour
98 public function testCreateContributionPage($version) {
99 $this->_apiversion
= $version;
100 $result = $this->callAPIAndDocument($this->_entity
, 'create', $this->params
, __FUNCTION__
, __FILE__
);
101 $this->assertEquals(1, $result['count']);
102 $this->assertNotNull($result['values'][$result['id']]['id']);
103 $this->getAndCheck($this->params
, $result['id'], $this->_entity
);
107 * @param int $version
108 * @dataProvider versionThreeAndFour
110 public function testGetBasicContributionPage($version) {
111 $this->_apiversion
= $version;
112 $createResult = $this->callAPISuccess($this->_entity
, 'create', $this->params
);
113 $this->id
= $createResult['id'];
116 'financial_type_id' => 1,
118 $getResult = $this->callAPIAndDocument($this->_entity
, 'get', $getParams, __FUNCTION__
, __FILE__
);
119 $this->assertEquals(1, $getResult['count']);
122 public function testGetContributionPageByAmount() {
123 $createResult = $this->callAPISuccess($this->_entity
, 'create', $this->params
);
124 $this->id
= $createResult['id'];
127 'amount' => '' . $this->testAmount
,
129 'financial_type_id' => 1,
131 $getResult = $this->callAPISuccess($this->_entity
, 'get', $getParams);
132 $this->assertEquals(1, $getResult['count']);
136 * @param int $version
137 * @dataProvider versionThreeAndFour
139 public function testDeleteContributionPage($version) {
140 $this->_apiversion
= $version;
141 $createResult = $this->callAPISuccess($this->_entity
, 'create', $this->params
);
142 $deleteParams = ['id' => $createResult['id']];
143 $this->callAPIAndDocument($this->_entity
, 'delete', $deleteParams, __FUNCTION__
, __FILE__
);
144 $checkDeleted = $this->callAPISuccess($this->_entity
, 'get', []);
145 $this->assertEquals(0, $checkDeleted['count']);
148 public function testGetFieldsContributionPage() {
149 $result = $this->callAPISuccess($this->_entity
, 'getfields', ['action' => 'create']);
150 $this->assertEquals(12, $result['values']['start_date']['type']);
154 * Test form submission with basic price set.
156 public function testSubmit() {
157 $this->setUpContributionPage();
158 $submitParams = $this->getBasicSubmitParams();
160 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
161 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['contribution_page_id' => $this->_ids
['contribution_page']]);
162 //assert non-deductible amount
163 $this->assertEquals(5.00, $contribution['non_deductible_amount']);
167 * Test form submission with basic price set.
169 public function testSubmitZeroDollar() {
170 $this->setUpContributionPage();
171 $priceFieldID = reset($this->_ids
['price_field']);
173 'price_' . $priceFieldID => $this->_ids
['price_field_value']['cheapskate'],
174 'id' => (int) $this->_ids
['contribution_page'],
176 'priceSetId' => $this->_ids
['price_set'][0],
177 'payment_processor_id' => '',
180 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
181 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['contribution_page_id' => $this->_ids
['contribution_page']]);
183 $this->assertEquals($this->formatMoneyInput(0), $contribution['non_deductible_amount']);
184 $this->assertEquals($this->formatMoneyInput(0), $contribution['total_amount']);
188 * Test form submission with billing first & last name where the contact does NOT
189 * otherwise have one.
191 public function testSubmitNewBillingNameData() {
192 $this->setUpContributionPage();
193 $contact = $this->callAPISuccess('Contact', 'create', ['contact_type' => 'Individual', 'email' => 'wonderwoman@amazon.com']);
194 $priceFieldID = reset($this->_ids
['price_field']);
195 $priceFieldValueID = reset($this->_ids
['price_field_value']);
197 'price_' . $priceFieldID => $priceFieldValueID,
198 'id' => (int) $this->_ids
['contribution_page'],
200 'billing_first_name' => 'Wonder',
201 'billing_last_name' => 'Woman',
202 'contactID' => $contact['id'],
203 'email' => 'wonderwoman@amazon.com',
206 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
207 $contact = $this->callAPISuccess('Contact', 'get', [
208 'id' => $contact['id'],
216 $this->assertEquals([
217 'first_name' => 'Wonder',
218 'last_name' => 'Woman',
219 'display_name' => 'Wonder Woman',
220 'sort_name' => 'Woman, Wonder',
221 'id' => $contact['id'],
222 'contact_id' => $contact['id'],
223 ], $contact['values'][$contact['id']]);
228 * Test form submission with billing first & last name where the contact does
229 * otherwise have one and should not be overwritten.
231 public function testSubmitNewBillingNameDoNotOverwrite() {
232 $this->setUpContributionPage();
233 $contact = $this->callAPISuccess('Contact', 'create', [
234 'contact_type' => 'Individual',
235 'email' => 'wonderwoman@amazon.com',
236 'first_name' => 'Super',
237 'last_name' => 'Boy',
239 $priceFieldID = reset($this->_ids
['price_field']);
240 $priceFieldValueID = reset($this->_ids
['price_field_value']);
242 'price_' . $priceFieldID => $priceFieldValueID,
243 'id' => (int) $this->_ids
['contribution_page'],
245 'billing_first_name' => 'Wonder',
246 'billing_last_name' => 'Woman',
247 'contactID' => $contact['id'],
248 'email' => 'wonderwoman@amazon.com',
251 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
252 $contact = $this->callAPISuccess('Contact', 'get', [
253 'id' => $contact['id'],
261 $this->assertEquals([
262 'first_name' => 'Super',
263 'last_name' => 'Boy',
264 'display_name' => 'Super Boy',
265 'sort_name' => 'Boy, Super',
266 'id' => $contact['id'],
267 'contact_id' => $contact['id'],
268 ], $contact['values'][$contact['id']]);
273 * Test process with instant payment when more than one configured for the page.
277 public function testSubmitRecurMultiProcessorInstantPayment() {
278 $this->setUpContributionPage();
279 $this->setupPaymentProcessor();
280 $paymentProcessor2ID = $this->paymentProcessorCreate([
281 'payment_processor_type_id' => 'Dummy',
282 'name' => 'processor 2',
283 'class_name' => 'Payment_Dummy',
286 $dummyPP = Civi\Payment\System
::singleton()->getById($paymentProcessor2ID);
287 $dummyPP->setDoDirectPaymentResult([
288 'payment_status_id' => 1,
289 'trxn_id' => 'create_first_success',
292 $processor = $dummyPP->getPaymentProcessor();
293 $this->callAPISuccess('ContributionPage', 'create', [
294 'id' => $this->_ids
['contribution_page'],
295 'payment_processor' => [$paymentProcessor2ID, $this->_ids
['payment_processor']],
298 $priceFieldID = reset($this->_ids
['price_field']);
299 $priceFieldValueID = reset($this->_ids
['price_field_value']);
301 'price_' . $priceFieldID => $priceFieldValueID,
302 'id' => (int) $this->_ids
['contribution_page'],
305 'frequency_interval' => 1,
306 'frequency_unit' => 'month',
307 'payment_processor_id' => $paymentProcessor2ID,
310 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
311 $contribution = $this->callAPISuccess('contribution', 'getsingle', [
312 'contribution_page_id' => $this->_ids
['contribution_page'],
313 'contribution_status_id' => 1,
315 $this->assertEquals('create_first_success', $contribution['trxn_id']);
316 $this->assertEquals(10, $contribution['total_amount']);
317 $this->assertEquals(.85, $contribution['fee_amount']);
318 $this->assertEquals(9.15, $contribution['net_amount']);
319 $this->_checkFinancialRecords([
320 'id' => $contribution['id'],
321 'total_amount' => $contribution['total_amount'],
322 'payment_instrument_id' => $processor['payment_instrument_id'],
327 * Test submit with a membership block in place.
329 public function testSubmitMembershipBlockNotSeparatePayment() {
330 $this->setUpMembershipContributionPage();
332 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
333 'id' => (int) $this->_ids
['contribution_page'],
335 'billing_first_name' => 'Billy',
336 'billing_middle_name' => 'Goat',
337 'billing_last_name' => 'Gruff',
338 'selectMembership' => $this->_ids
['membership_type'],
342 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
343 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['contribution_page_id' => $this->_ids
['contribution_page']]);
344 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', ['contribution_id' => $contribution['id']]);
345 $this->callAPISuccessGetSingle('LineItem', ['contribution_id' => $contribution['id'], 'entity_id' => $membershipPayment['id']]);
349 * Test submit with a membership block in place works with renewal.
351 * @throws \CRM_Core_Exception
353 public function testSubmitMembershipBlockNotSeparatePaymentProcessorInstantRenew() {
354 $this->setUpMembershipContributionPage();
355 $dummyPP = Civi\Payment\System
::singleton()->getByProcessor($this->_paymentProcessor
);
356 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1]);
358 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
359 'id' => (int) $this->_ids
['contribution_page'],
361 'billing_first_name' => 'Billy',
362 'billing_middle_name' => 'Goat',
363 'billing_last_name' => 'Gruff',
364 'selectMembership' => $this->_ids
['membership_type'],
365 'payment_processor_id' => 1,
366 'credit_card_number' => '4111111111111111',
367 'credit_card_type' => 'Visa',
368 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
372 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
373 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['contribution_page_id' => $this->_ids
['contribution_page']]);
374 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', ['contribution_id' => $contribution['id']]);
375 $this->callAPISuccessGetCount('LineItem', [
376 'entity_table' => 'civicrm_membership',
377 'entity_id' => $membershipPayment['id'],
380 $submitParams['contact_id'] = $contribution['contact_id'];
382 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
383 $this->callAPISuccessGetCount('LineItem', [
384 'entity_table' => 'civicrm_membership',
385 'entity_id' => $membershipPayment['id'],
387 $membership = $this->callAPISuccessGetSingle('Membership', [
388 'id' => $membershipPayment['membership_id'],
389 'return' => ['end_date', 'join_date', 'start_date'],
391 $this->assertEquals(date('Y-m-d'), $membership['start_date']);
392 $this->assertEquals(date('Y-m-d'), $membership['join_date']);
393 $this->assertEquals(date('Y-m-d', strtotime('+ 2 year - 1 day')), $membership['end_date']);
397 * Test submit with a membership block in place.
399 public function testSubmitMembershipBlockNotSeparatePaymentWithEmail() {
400 $mut = new CiviMailUtils($this, TRUE);
401 $this->setUpMembershipContributionPage();
402 $this->addProfile('supporter_profile', $this->_ids
['contribution_page']);
405 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
406 'id' => (int) $this->_ids
['contribution_page'],
408 'billing_first_name' => 'Billy',
409 'billing_middle_name' => 'Goat',
410 'billing_last_name' => 'Gruff',
411 'selectMembership' => $this->_ids
['membership_type'],
412 'email-Primary' => 'billy-goat@the-bridge.net',
413 'payment_processor_id' => $this->_paymentProcessor
['id'],
414 'credit_card_number' => '4111111111111111',
415 'credit_card_type' => 'Visa',
416 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
420 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
421 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['contribution_page_id' => $this->_ids
['contribution_page']]);
422 $this->callAPISuccess('membership_payment', 'getsingle', ['contribution_id' => $contribution['id']]);
424 'Membership Type: General',
425 'Test Frontend title',
428 $mut->clearMessages();
432 * Test submit with a membership block in place.
434 public function testSubmitMembershipBlockNotSeparatePaymentZeroDollarsWithEmail() {
435 $mut = new CiviMailUtils($this, TRUE);
436 $this->_ids
['membership_type'] = [$this->membershipTypeCreate(['minimum_fee' => 0])];
437 $this->setUpMembershipContributionPage();
438 $this->addProfile('supporter_profile', $this->_ids
['contribution_page']);
441 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
442 'id' => (int) $this->_ids
['contribution_page'],
444 'billing_first_name' => 'Billy',
445 'billing_middle_name' => 'Goat',
446 'billing_last_name' => 'Gruffier',
447 'selectMembership' => $this->_ids
['membership_type'],
448 'email-Primary' => 'billy-goat@the-new-bridge.net',
449 'payment_processor_id' => $this->params
['payment_processor_id'],
452 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
453 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['contribution_page_id' => $this->_ids
['contribution_page']]);
454 $this->callAPISuccess('membership_payment', 'getsingle', ['contribution_id' => $contribution['id']]);
455 //Assert only one mail is being sent.
456 $msgs = $mut->getAllMessages();
457 $this->assertCount(1, $msgs);
460 'Membership Type: General',
466 $mut->clearMessages();
470 * Test submit with a pay later abnd check line item in mails.
472 public function testSubmitMembershipBlockIsSeparatePaymentPayLaterWithEmail() {
473 $mut = new CiviMailUtils($this, TRUE);
474 $this->setUpMembershipContributionPage();
476 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
477 'id' => (int) $this->_ids
['contribution_page'],
479 'billing_first_name' => 'Billy',
480 'billing_middle_name' => 'Goat',
481 'billing_last_name' => 'Gruff',
483 'selectMembership' => $this->_ids
['membership_type'],
484 'email-Primary' => 'billy-goat@the-bridge.net',
487 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
488 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['contribution_page_id' => $this->_ids
['contribution_page']]);
489 $this->callAPISuccess('membership_payment', 'getsingle', ['contribution_id' => $contribution['id']]);
491 'Membership Amount -... $ 2.00',
494 $mut->clearMessages();
498 * Test submit with a membership block in place.
500 public function testSubmitMembershipBlockIsSeparatePayment() {
501 $this->setUpMembershipContributionPage(TRUE);
502 $this->_ids
['membership_type'] = [$this->membershipTypeCreate(['minimum_fee' => 2])];
504 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
505 'id' => (int) $this->_ids
['contribution_page'],
507 'billing_first_name' => 'Billy',
508 'billing_middle_name' => 'Goat',
509 'billing_last_name' => 'Gruff',
510 'selectMembership' => $this->_ids
['membership_type'],
513 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
514 $contributions = $this->callAPISuccess('contribution', 'get', ['contribution_page_id' => $this->_ids
['contribution_page']]);
515 $this->assertCount(2, $contributions['values']);
516 $lines = $this->callAPISuccess('LineItem', 'get', ['sequential' => 1]);
517 $this->assertEquals(10, $lines['values'][0]['line_total']);
518 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
519 $this->assertTrue(in_array($membershipPayment['contribution_id'], array_keys($contributions['values'])));
520 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
521 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
525 * Test submit with a membership block in place.
527 public function testSubmitMembershipBlockIsSeparatePaymentWithPayLater() {
528 $this->setUpMembershipContributionPage(TRUE);
529 $this->_ids
['membership_type'] = [$this->membershipTypeCreate(['minimum_fee' => 2])];
532 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
533 'id' => (int) $this->_ids
['contribution_page'],
535 'billing_first_name' => 'Billy',
536 'billing_middle_name' => 'Goat',
537 'billing_last_name' => 'Gruff',
539 'selectMembership' => $this->_ids
['membership_type'],
542 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
543 $contributions = $this->callAPISuccess('contribution', 'get', ['contribution_page_id' => $this->_ids
['contribution_page']]);
544 $this->assertCount(2, $contributions['values']);
545 foreach ($contributions['values'] as $val) {
546 $this->assertEquals(CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending'), $val['contribution_status_id']);
549 //Membership should be in Pending state.
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 $pendingStatus = $this->callAPISuccessGetSingle('MembershipStatus', [
557 $this->assertEquals($membership['status_id'], $pendingStatus['id']);
558 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
562 * Test submit with a membership block in place.
564 public function testSubmitMembershipBlockIsSeparatePaymentWithEmail() {
565 $mut = new CiviMailUtils($this, TRUE);
566 $this->setUpMembershipContributionPage(TRUE);
567 $this->addProfile('supporter_profile', $this->_ids
['contribution_page']);
570 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
571 'id' => (int) $this->_ids
['contribution_page'],
573 'billing_first_name' => 'Billy',
574 'billing_middle_name' => 'Goat',
575 'billing_last_name' => 'Gruff',
576 'selectMembership' => $this->_ids
['membership_type'],
577 'email-Primary' => 'billy-goat@the-bridge.net',
578 'payment_processor_id' => $this->_paymentProcessor
['id'],
579 'credit_card_number' => '4111111111111111',
580 'credit_card_type' => 'Visa',
581 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
585 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
586 $contributions = $this->callAPISuccess('contribution', 'get', ['contribution_page_id' => $this->_ids
['contribution_page']]);
587 $this->assertCount(2, $contributions['values']);
588 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
589 $this->assertTrue(in_array($membershipPayment['contribution_id'], array_keys($contributions['values'])));
590 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
591 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
592 // We should have two separate email messages, each with their own amount
593 // line and no total line.
594 $mut->checkAllMailLog(
605 $mut->clearMessages();
609 * Test submit with a membership block in place.
611 public function testSubmitMembershipBlockIsSeparatePaymentZeroDollarsPayLaterWithEmail() {
612 $mut = new CiviMailUtils($this, TRUE);
613 $this->_ids
['membership_type'] = [$this->membershipTypeCreate(['minimum_fee' => 0])];
614 $this->setUpMembershipContributionPage(TRUE);
615 $this->addProfile('supporter_profile', $this->_ids
['contribution_page']);
618 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
619 'id' => (int) $this->_ids
['contribution_page'],
621 'billing_first_name' => 'Billy',
622 'billing_middle_name' => 'Goat',
623 'billing_last_name' => 'Gruffalo',
624 'selectMembership' => $this->_ids
['membership_type'],
625 'payment_processor_id' => 0,
626 'email-Primary' => 'gruffalo@the-bridge.net',
629 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
630 $contributions = $this->callAPISuccess('contribution', 'get', ['contribution_page_id' => $this->_ids
['contribution_page']]);
631 $this->assertCount(2, $contributions['values']);
632 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
633 $this->assertTrue(in_array($membershipPayment['contribution_id'], array_keys($contributions['values'])));
634 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
635 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
638 'General Membership: $ 0.00',
642 $mut->clearMessages();
646 * Test submit with a membership block in place.
648 public function testSubmitMembershipBlockTwoTypesIsSeparatePayment() {
649 $this->_ids
['membership_type'] = [$this->membershipTypeCreate(['minimum_fee' => 6])];
650 $this->_ids
['membership_type'][] = $this->membershipTypeCreate(['name' => 'Student', 'minimum_fee' => 50]);
651 $this->setUpMembershipContributionPage(TRUE);
653 'price_' . $this->_ids
['price_field'][0] => $this->_ids
['price_field_value'][1],
654 'id' => (int) $this->_ids
['contribution_page'],
656 'billing_first_name' => 'Billy',
657 'billing_middle_name' => 'Goat',
658 'billing_last_name' => 'Gruff',
659 'selectMembership' => $this->_ids
['membership_type'][1],
662 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
663 $contributions = $this->callAPISuccess('contribution', 'get', ['contribution_page_id' => $this->_ids
['contribution_page']]);
664 $this->assertCount(2, $contributions['values']);
665 $ids = array_keys($contributions['values']);
666 $this->assertEquals('10.00', $contributions['values'][$ids[0]]['total_amount']);
667 $this->assertEquals('50.00', $contributions['values'][$ids[1]]['total_amount']);
668 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
669 $this->assertArrayHasKey($membershipPayment['contribution_id'], $contributions['values']);
670 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
671 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
675 * Test submit with a membership block in place.
677 * We are expecting a separate payment for the membership vs the contribution.
679 * @throws \CRM_Core_Exception
680 * @throws \CiviCRM_API3_Exception
682 public function testSubmitMembershipBlockIsSeparatePaymentPaymentProcessorNow() {
683 $mut = new CiviMailUtils($this, TRUE);
684 $this->setUpMembershipContributionPage(TRUE);
685 $processor = Civi\Payment\System
::singleton()->getById($this->_paymentProcessor
['id']);
686 $processor->setDoDirectPaymentResult(['fee_amount' => .72]);
688 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
689 'id' => (int) $this->_ids
['contribution_page'],
691 'billing_first_name' => 'Billy',
692 'billing_middle_name' => 'Goat',
693 'billing_last_name' => 'Gruff',
694 'email-Primary' => 'henry@8th.king',
695 'selectMembership' => $this->_ids
['membership_type'],
696 'payment_processor_id' => $this->_paymentProcessor
['id'],
697 'credit_card_number' => '4111111111111111',
698 'credit_card_type' => 'Visa',
699 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
703 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
704 $contributions = $this->callAPISuccess('contribution', 'get', [
705 'contribution_page_id' => $this->_ids
['contribution_page'],
706 'contribution_status_id' => 1,
708 $this->assertCount(2, $contributions['values']);
709 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
710 $this->assertTrue(in_array($membershipPayment['contribution_id'], array_keys($contributions['values'])));
711 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
712 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
713 $lineItem = $this->callAPISuccessGetSingle('LineItem', ['entity_table' => 'civicrm_membership']);
714 $this->assertEquals($lineItem['entity_id'], $membership['id']);
715 $this->assertEquals($lineItem['contribution_id'], $membershipPayment['contribution_id']);
716 $this->assertEquals($lineItem['qty'], 1);
717 $this->assertEquals($lineItem['unit_price'], 2);
718 $this->assertEquals($lineItem['line_total'], 2);
719 foreach ($contributions['values'] as $contribution) {
720 $this->assertEquals(.72, $contribution['fee_amount']);
721 $this->assertEquals($contribution['total_amount'] - .72, $contribution['net_amount']);
723 // The total string is currently absent & it seems worse with - although at some point
724 // it may have been intended
725 $mut->checkAllMailLog(['$ 2.00', 'Contribution Amount', '$ 10.00'], ['Total:']);
727 $mut->clearMessages();
731 * Test submit with a membership block in place.
733 * Ensure a separate payment for the membership vs the contribution, with
736 * @param string $thousandSeparator
737 * punctuation used to refer to thousands.
739 * @dataProvider getThousandSeparators
741 public function testSubmitMembershipBlockIsSeparatePaymentPaymentProcessorNowChargesCorrectAmounts($thousandSeparator) {
742 $this->setCurrencySeparators($thousandSeparator);
743 $this->setUpMembershipContributionPage(TRUE);
744 $processor = Civi\Payment\System
::singleton()->getById($this->_paymentProcessor
['id']);
745 $processor->setDoDirectPaymentResult(['fee_amount' => .72]);
746 $test_uniq = uniqid();
747 $contributionPageAmount = 10;
749 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
750 'id' => (int) $this->_ids
['contribution_page'],
751 'amount' => $contributionPageAmount,
752 'billing_first_name' => 'Billy',
753 'billing_middle_name' => 'Goat',
754 'billing_last_name' => 'Gruff',
755 'email-Primary' => 'henry@8th.king',
756 'selectMembership' => $this->_ids
['membership_type'],
757 'payment_processor_id' => $this->_paymentProcessor
['id'],
758 'credit_card_number' => '4111111111111111',
759 'credit_card_type' => 'Visa',
760 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
762 'TEST_UNIQ' => $test_uniq,
766 $this->hookClass
->setHook('civicrm_alterPaymentProcessorParams', [$this, 'hook_civicrm_alterPaymentProcessorParams']);
768 $this->callAPISuccess('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
769 $contributions = $this->callAPISuccess('contribution', 'get', [
770 'contribution_page_id' => $this->_ids
['contribution_page'],
771 'contribution_status_id' => 1,
774 $result = civicrm_api3('SystemLog', 'get', [
776 'message' => ['LIKE' => "%{$test_uniq}%"],
778 $this->assertCount(2, $result['values'], "Expected exactly 2 log entries matching {$test_uniq}.");
780 // Examine logged entries to ensure correct values.
781 $contribution_ids = [];
782 $found_membership_amount = $found_contribution_amount = FALSE;
783 foreach ($result['values'] as $value) {
784 list($junk, $json) = explode("$test_uniq:", $value['message']);
785 $logged_contribution = json_decode($json, TRUE);
786 $contribution_ids[] = $logged_contribution['contributionID'];
787 if (!empty($logged_contribution['total_amount'])) {
788 $amount = $logged_contribution['total_amount'];
791 $amount = $logged_contribution['amount'];
794 if ($amount == $this->_membershipBlockAmount
) {
795 $found_membership_amount = TRUE;
797 if ($amount == $contributionPageAmount) {
798 $found_contribution_amount = TRUE;
802 $distinct_contribution_ids = array_unique($contribution_ids);
803 $this->assertCount(2, $distinct_contribution_ids, "Expected exactly 2 log contributions with distinct contributionIDs.");
804 $this->assertTrue($found_contribution_amount, "Expected one log contribution with amount '$contributionPageAmount' (the contribution page amount)");
805 $this->assertTrue($found_membership_amount, "Expected one log contribution with amount '$this->_membershipBlockAmount' (the membership amount)");
809 * Test that when a transaction fails the pending contribution remains.
811 * An activity should also be created. CRM-16417.
813 public function testSubmitPaymentProcessorFailure() {
814 $this->setUpContributionPage();
815 $this->setupPaymentProcessor();
816 $this->createLoggedInUser();
817 $priceFieldID = reset($this->_ids
['price_field']);
818 $priceFieldValueID = reset($this->_ids
['price_field_value']);
820 'price_' . $priceFieldID => $priceFieldValueID,
821 'id' => (int) $this->_ids
['contribution_page'],
823 'payment_processor_id' => 1,
824 'credit_card_number' => '4111111111111111',
825 'credit_card_type' => 'Visa',
826 'credit_card_exp_date' => ['M' => 9, 'Y' => 2008],
830 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
831 $contribution = $this->callAPISuccessGetSingle('contribution', [
832 'contribution_page_id' => $this->_ids
['contribution_page'],
833 'contribution_status_id' => 'Failed',
836 $this->callAPISuccessGetSingle('activity', [
837 'source_record_id' => $contribution['id'],
838 'activity_type_id' => 'Failed Payment',
844 * Test submit recurring (yearly) membership with immediate confirmation (IATS style).
846 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
847 * processor (IATS style - denoted by returning trxn_id)
848 * - the first creates a new membership, completed contribution, in progress recurring. Check these
849 * - create another - end date should be extended
851 public function testSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPaymentYear() {
852 $this->doSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPayment(['duration_unit' => 'year', 'recur_frequency_unit' => 'year']);
856 * Test submit recurring (monthly) membership with immediate confirmation (IATS style).
858 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
859 * processor (IATS style - denoted by returning trxn_id)
860 * - the first creates a new membership, completed contribution, in progress recurring. Check these
861 * - create another - end date should be extended
863 public function testSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPaymentMonth() {
864 $this->doSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPayment(['duration_unit' => 'month', 'recur_frequency_unit' => 'month']);
868 * Test submit recurring (mismatched frequency unit) membership with immediate confirmation (IATS style).
870 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
871 * processor (IATS style - denoted by returning trxn_id)
872 * - the first creates a new membership, completed contribution, in progress recurring. Check these
873 * - create another - end date should be extended
875 //public function testSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPaymentDifferentFrequency() {
876 // $this->doSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPayment(array('duration_unit' => 'year', 'recur_frequency_unit' => 'month'));
880 * Helper function for testSubmitMembershipPriceSetPaymentProcessorRecurInstantPayment*
881 * @param array $params
883 * @throws \CRM_Core_Exception
886 public function doSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPayment($params = []) {
887 $this->params
['is_recur'] = 1;
888 $this->params
['recur_frequency_unit'] = $params['recur_frequency_unit'];
889 $membershipTypeParams['duration_unit'] = $params['duration_unit'];
890 if ($params['recur_frequency_unit'] === $params['duration_unit']) {
891 $durationUnit = $params['duration_unit'];
894 $durationUnit = NULL;
896 $this->setUpMembershipContributionPage(FALSE, FALSE, $membershipTypeParams);
897 $dummyPP = Civi\Payment\System
::singleton()->getByProcessor($this->_paymentProcessor
);
898 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_first_success']);
899 $processor = $dummyPP->getPaymentProcessor();
901 if ($params['recur_frequency_unit'] === $params['duration_unit']) {
902 // Membership will be in "New" state because it will get confirmed as payment matches
903 $expectedMembershipStatus = 1;
906 // Membership will still be in "Pending" state as it won't get confirmed as payment doesn't match
907 $expectedMembershipStatus = 5;
911 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
912 'id' => (int) $this->_ids
['contribution_page'],
914 'billing_first_name' => 'Billy',
915 'billing_middle_name' => 'Goat',
916 'billing_last_name' => 'Gruff',
917 'email' => 'billy@goat.gruff',
918 'selectMembership' => $this->_ids
['membership_type'],
919 'payment_processor_id' => 1,
920 'credit_card_number' => '4111111111111111',
921 'credit_card_type' => 'Visa',
922 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
925 'frequency_interval' => 1,
926 'frequency_unit' => $this->params
['recur_frequency_unit'],
929 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
930 $contribution = $this->callAPISuccess('contribution', 'getsingle', [
931 'contribution_page_id' => $this->_ids
['contribution_page'],
932 'contribution_status_id' => 1,
934 $this->assertEquals($processor['payment_instrument_id'], $contribution['payment_instrument_id']);
936 $this->assertEquals('create_first_success', $contribution['trxn_id']);
937 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
938 $this->assertEquals($membershipPayment['contribution_id'], $contribution['id']);
939 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
940 $this->assertEquals($membership['contact_id'], $contribution['contact_id']);
941 $this->assertEquals($expectedMembershipStatus, $membership['status_id']);
942 $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $contribution['contribution_recur_id']]);
943 $this->assertEquals($contribution['contribution_recur_id'], $membership['contribution_recur_id']);
945 $this->callAPISuccess('line_item', 'getsingle', ['contribution_id' => $contribution['id'], 'entity_id' => $membership['id']]);
946 //renew it with processor setting completed - should extend membership
947 $submitParams['contact_id'] = $contribution['contact_id'];
948 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_second_success']);
949 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
950 $this->callAPISuccess('contribution', 'getsingle', [
951 'id' => ['NOT IN' => [$contribution['id']]],
952 'contribution_page_id' => $this->_ids
['contribution_page'],
953 'contribution_status_id' => 1,
955 $renewedMembership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
957 // We only have an end_date if frequency units match, otherwise membership won't be autorenewed and dates won't be calculated.
958 $renewedMembershipEndDate = $this->membershipRenewalDate($durationUnit, $membership['end_date']);
959 $this->assertEquals($renewedMembershipEndDate, $renewedMembership['end_date']);
961 $recurringContribution = $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $contribution['contribution_recur_id']]);
962 $this->assertEquals($processor['payment_instrument_id'], $recurringContribution['payment_instrument_id']);
963 $this->assertEquals(5, $recurringContribution['contribution_status_id']);
967 * Test submit recurring membership with immediate confirmation (IATS style).
969 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
970 * processor (IATS style - denoted by returning trxn_id)
971 * - the first creates a new membership, completed contribution, in progress recurring. Check these
972 * - create another - end date should be extended
974 public function testSubmitMembershipComplexNonPriceSetPaymentPaymentProcessorRecurInstantPayment() {
975 $this->params
['is_recur'] = 1;
976 $this->params
['recur_frequency_unit'] = $membershipTypeParams['duration_unit'] = 'year';
977 // Add a membership so membership & contribution are not both 1.
978 $preExistingMembershipID = $this->contactMembershipCreate(['contact_id' => $this->contactIds
[0]]);
979 $this->setUpMembershipContributionPage(FALSE, FALSE, $membershipTypeParams);
980 $dummyPP = Civi\Payment\System
::singleton()->getByProcessor($this->_paymentProcessor
);
981 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_first_success']);
982 $processor = $dummyPP->getPaymentProcessor();
985 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
986 'price_' . $this->_ids
['price_field']['cont'] => 88,
987 'id' => (int) $this->_ids
['contribution_page'],
989 'billing_first_name' => 'Billy',
990 'billing_middle_name' => 'Goat',
991 'billing_last_name' => 'Gruff',
992 'email' => 'billy@goat.gruff',
993 'selectMembership' => $this->_ids
['membership_type'],
994 'payment_processor_id' => 1,
995 'credit_card_number' => '4111111111111111',
996 'credit_card_type' => 'Visa',
997 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
1000 'frequency_interval' => 1,
1001 'frequency_unit' => $this->params
['recur_frequency_unit'],
1004 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
1005 $contribution = $this->callAPISuccess('contribution', 'getsingle', [
1006 'contribution_page_id' => $this->_ids
['contribution_page'],
1007 'contribution_status_id' => 1,
1009 $this->assertEquals($processor['payment_instrument_id'], $contribution['payment_instrument_id']);
1011 $this->assertEquals('create_first_success', $contribution['trxn_id']);
1012 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
1013 $this->assertEquals($membershipPayment['contribution_id'], $contribution['id']);
1014 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
1015 $this->assertEquals($membership['contact_id'], $contribution['contact_id']);
1016 $this->assertEquals(1, $membership['status_id']);
1017 $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $contribution['contribution_recur_id']]);
1019 $lines = $this->callAPISuccess('line_item', 'get', ['sequential' => 1, 'contribution_id' => $contribution['id']]);
1020 $this->assertEquals(2, $lines['count']);
1021 $this->assertEquals('civicrm_membership', $lines['values'][0]['entity_table']);
1022 $this->assertEquals($preExistingMembershipID +
1, $lines['values'][0]['entity_id']);
1023 $this->assertEquals('civicrm_contribution', $lines['values'][1]['entity_table']);
1024 $this->assertEquals($contribution['id'], $lines['values'][1]['entity_id']);
1025 $this->callAPISuccessGetSingle('MembershipPayment', ['contribution_id' => $contribution['id'], 'membership_id' => $preExistingMembershipID +
1]);
1027 //renew it with processor setting completed - should extend membership
1028 $submitParams['contact_id'] = $contribution['contact_id'];
1029 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_second_success']);
1030 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1031 $renewContribution = $this->callAPISuccess('contribution', 'getsingle', [
1032 'id' => ['NOT IN' => [$contribution['id']]],
1033 'contribution_page_id' => $this->_ids
['contribution_page'],
1034 'contribution_status_id' => 1,
1036 $lines = $this->callAPISuccess('line_item', 'get', ['sequential' => 1, 'contribution_id' => $renewContribution['id']]);
1037 $this->assertEquals(2, $lines['count']);
1038 $this->assertEquals('civicrm_membership', $lines['values'][0]['entity_table']);
1039 $this->assertEquals($preExistingMembershipID +
1, $lines['values'][0]['entity_id']);
1040 $this->assertEquals('civicrm_contribution', $lines['values'][1]['entity_table']);
1041 $this->assertEquals($renewContribution['id'], $lines['values'][1]['entity_id']);
1043 $renewedMembership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
1044 $this->assertEquals(date('Y-m-d', strtotime('+ 1 ' . $this->params
['recur_frequency_unit'], strtotime($membership['end_date']))), $renewedMembership['end_date']);
1045 $recurringContribution = $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $contribution['contribution_recur_id']]);
1046 $this->assertEquals($processor['payment_instrument_id'], $recurringContribution['payment_instrument_id']);
1047 $this->assertEquals(5, $recurringContribution['contribution_status_id']);
1051 * Test submit recurring membership with immediate confirmation (IATS style).
1053 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
1054 * processor (IATS style - denoted by returning trxn_id)
1055 * - the first creates a new membership, completed contribution, in progress recurring. Check these
1056 * - create another - end date should be extended
1058 public function testSubmitMembershipComplexPriceSetPaymentPaymentProcessorRecurInstantPayment() {
1059 $this->params
['is_recur'] = 1;
1060 $this->params
['recur_frequency_unit'] = $membershipTypeParams['duration_unit'] = 'year';
1061 // Add a membership so membership & contribution are not both 1.
1062 $preExistingMembershipID = $this->contactMembershipCreate(['contact_id' => $this->contactIds
[0]]);
1063 $this->createPriceSetWithPage();
1064 $this->addSecondOrganizationMembershipToPriceSet();
1065 $this->setupPaymentProcessor();
1067 $dummyPP = Civi\Payment\System
::singleton()->getByProcessor($this->_paymentProcessor
);
1068 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_first_success']);
1069 $processor = $dummyPP->getPaymentProcessor();
1072 'price_' . $this->_ids
['price_field'][0] => $this->_ids
['price_field_value']['cont'],
1073 'price_' . $this->_ids
['price_field']['org1'] => $this->_ids
['price_field_value']['org1'],
1074 'price_' . $this->_ids
['price_field']['org2'] => $this->_ids
['price_field_value']['org2'],
1075 'id' => (int) $this->_ids
['contribution_page'],
1077 'billing_first_name' => 'Billy',
1078 'billing_middle_name' => 'Goat',
1079 'billing_last_name' => 'Gruff',
1080 'email' => 'billy@goat.gruff',
1081 'selectMembership' => NULL,
1082 'payment_processor_id' => 1,
1083 'credit_card_number' => '4111111111111111',
1084 'credit_card_type' => 'Visa',
1085 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
1087 'frequency_interval' => 1,
1088 'frequency_unit' => $this->params
['recur_frequency_unit'],
1091 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
1092 $contribution = $this->callAPISuccess('contribution', 'getsingle', [
1093 'contribution_page_id' => $this->_ids
['contribution_page'],
1094 'contribution_status_id' => 1,
1096 $this->assertEquals($processor['payment_instrument_id'], $contribution['payment_instrument_id']);
1098 $this->assertEquals('create_first_success', $contribution['trxn_id']);
1099 $membershipPayments = $this->callAPISuccess('membership_payment', 'get', [
1101 'contribution_id' => $contribution['id'],
1103 $this->assertEquals(2, $membershipPayments['count']);
1104 $lines = $this->callAPISuccess('line_item', 'get', ['sequential' => 1, 'contribution_id' => $contribution['id']]);
1105 $this->assertEquals(3, $lines['count']);
1106 $this->assertEquals('civicrm_membership', $lines['values'][0]['entity_table']);
1107 $this->assertEquals($preExistingMembershipID +
1, $lines['values'][0]['entity_id']);
1108 $this->assertEquals('civicrm_contribution', $lines['values'][1]['entity_table']);
1109 $this->assertEquals($contribution['id'], $lines['values'][1]['entity_id']);
1110 $this->assertEquals('civicrm_membership', $lines['values'][2]['entity_table']);
1111 $this->assertEquals($preExistingMembershipID +
2, $lines['values'][2]['entity_id']);
1113 $this->callAPISuccessGetSingle('MembershipPayment', ['contribution_id' => $contribution['id'], 'membership_id' => $preExistingMembershipID +
1]);
1114 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $preExistingMembershipID +
1]);
1116 //renew it with processor setting completed - should extend membership
1117 $submitParams['contact_id'] = $contribution['contact_id'];
1118 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_second_success']);
1119 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1120 $renewContribution = $this->callAPISuccess('contribution', 'getsingle', [
1121 'id' => ['NOT IN' => [$contribution['id']]],
1122 'contribution_page_id' => $this->_ids
['contribution_page'],
1123 'contribution_status_id' => 1,
1125 $lines = $this->callAPISuccess('line_item', 'get', ['sequential' => 1, 'contribution_id' => $renewContribution['id']]);
1126 $this->assertEquals(3, $lines['count']);
1127 $this->assertEquals('civicrm_membership', $lines['values'][0]['entity_table']);
1128 $this->assertEquals($preExistingMembershipID +
1, $lines['values'][0]['entity_id']);
1129 $this->assertEquals('civicrm_contribution', $lines['values'][1]['entity_table']);
1130 $this->assertEquals($renewContribution['id'], $lines['values'][1]['entity_id']);
1132 $renewedMembership = $this->callAPISuccessGetSingle('membership', ['id' => $preExistingMembershipID +
1]);
1133 $this->assertEquals(date('Y-m-d', strtotime('+ 1 ' . $this->params
['recur_frequency_unit'], strtotime($membership['end_date']))), $renewedMembership['end_date']);
1137 * Extend the price set with a second organisation's membership.
1139 public function addSecondOrganizationMembershipToPriceSet() {
1140 $organization2ID = $this->organizationCreate();
1141 $membershipTypes = $this->callAPISuccess('MembershipType', 'get', []);
1142 $this->_ids
['membership_type'] = array_keys($membershipTypes['values']);
1143 $this->_ids
['membership_type']['org2'] = $this->membershipTypeCreate(['contact_id' => $organization2ID, 'name' => 'Org 2']);
1144 $priceField = $this->callAPISuccess('PriceField', 'create', [
1145 'price_set_id' => $this->_ids
['price_set'],
1146 'html_type' => 'Radio',
1147 'name' => 'Org1 Price',
1148 'label' => 'Org1Price',
1150 $this->_ids
['price_field']['org1'] = $priceField['id'];
1152 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
1153 'name' => 'org1 amount',
1154 'label' => 'org 1 Amount',
1156 'financial_type_id' => 'Member Dues',
1157 'format.only_id' => TRUE,
1158 'membership_type_id' => reset($this->_ids
['membership_type']),
1159 'price_field_id' => $priceField['id'],
1161 $this->_ids
['price_field_value']['org1'] = $priceFieldValue;
1163 $priceField = $this->callAPISuccess('PriceField', 'create', [
1164 'price_set_id' => $this->_ids
['price_set'],
1165 'html_type' => 'Radio',
1166 'name' => 'Org2 Price',
1167 'label' => 'Org2Price',
1169 $this->_ids
['price_field']['org2'] = $priceField['id'];
1171 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
1172 'name' => 'org2 amount',
1173 'label' => 'org 2 Amount',
1175 'financial_type_id' => 'Member Dues',
1176 'format.only_id' => TRUE,
1177 'membership_type_id' => $this->_ids
['membership_type']['org2'],
1178 'price_field_id' => $priceField['id'],
1180 $this->_ids
['price_field_value']['org2'] = $priceFieldValue;
1185 * Test submit recurring membership with immediate confirmation (IATS style).
1187 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
1188 * processor (IATS style - denoted by returning trxn_id)
1189 * - the first creates a new membership, completed contribution, in progress recurring. Check these
1190 * - create another - end date should be extended
1192 public function testSubmitMembershipPriceSetPaymentPaymentProcessorSeparatePaymentRecurInstantPayment() {
1194 $this->setUpMembershipContributionPage(TRUE);
1195 $dummyPP = Civi\Payment\System
::singleton()->getByProcessor($this->_paymentProcessor
);
1196 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_first_success']);
1199 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
1200 'id' => (int) $this->_ids
['contribution_page'],
1202 'billing_first_name' => 'Billy',
1203 'billing_middle_name' => 'Goat',
1204 'billing_last_name' => 'Gruff',
1205 'email' => 'billy@goat.gruff',
1206 'selectMembership' => $this->_ids
['membership_type'],
1207 'payment_processor_id' => 1,
1208 'credit_card_number' => '4111111111111111',
1209 'credit_card_type' => 'Visa',
1210 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
1213 'auto_renew' => TRUE,
1214 'frequency_interval' => 1,
1215 'frequency_unit' => 'month',
1218 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
1219 $contribution = $this->callAPISuccess('contribution', 'get', [
1220 'contribution_page_id' => $this->_ids
['contribution_page'],
1221 'contribution_status_id' => 1,
1224 $this->assertEquals(2, $contribution['count']);
1225 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
1226 $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
1227 $this->assertNotEmpty($contribution['values'][$membershipPayment['contribution_id']]['contribution_recur_id']);
1228 $this->callAPISuccess('contribution_recur', 'getsingle', []);
1232 * Test submit recurring membership with delayed confirmation (Authorize.net style)
1233 * - we process 2 membership transactions against with a recurring contribution against a contribution page with a delayed
1234 * processor (Authorize.net style - denoted by NOT returning trxn_id)
1235 * - the first creates a pending membership, pending contribution, penging recurring. Check these
1236 * - complete the transaction
1237 * - create another - end date should NOT be extended
1239 public function testSubmitMembershipPriceSetPaymentPaymentProcessorRecurDelayed() {
1240 $this->params
['is_recur'] = 1;
1241 $this->params
['recur_frequency_unit'] = $membershipTypeParams['duration_unit'] = 'year';
1242 $this->setUpMembershipContributionPage();
1243 $dummyPP = Civi\Payment\System
::singleton()->getByProcessor($this->_paymentProcessor
);
1244 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 2]);
1245 $this->membershipTypeCreate(['name' => 'Student']);
1247 // Add a contribution & a couple of memberships so the id will not be 1 & will differ from membership id.
1248 // This saves us from 'accidental success'.
1249 $this->contributionCreate(['contact_id' => $this->contactIds
[0]]);
1250 $this->contactMembershipCreate(['contact_id' => $this->contactIds
[0]]);
1251 $this->contactMembershipCreate(['contact_id' => $this->contactIds
[0], 'membership_type_id' => 'Student']);
1254 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
1255 'id' => (int) $this->_ids
['contribution_page'],
1257 'billing_first_name' => 'Billy',
1258 'billing_middle_name' => 'Goat',
1259 'billing_last_name' => 'Gruff',
1260 'email' => 'billy@goat.gruff',
1261 'selectMembership' => $this->_ids
['membership_type'],
1262 'payment_processor_id' => 1,
1263 'credit_card_number' => '4111111111111111',
1264 'credit_card_type' => 'Visa',
1265 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
1268 'frequency_interval' => 1,
1269 'frequency_unit' => $this->params
['recur_frequency_unit'],
1272 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
1273 $contribution = $this->callAPISuccess('contribution', 'getsingle', [
1274 'contribution_page_id' => $this->_ids
['contribution_page'],
1275 'contribution_status_id' => 2,
1278 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', []);
1279 $this->assertEquals($membershipPayment['contribution_id'], $contribution['id']);
1280 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
1281 $this->assertEquals($membership['contact_id'], $contribution['contact_id']);
1282 $this->assertEquals(5, $membership['status_id']);
1284 $line = $this->callAPISuccess('line_item', 'getsingle', ['contribution_id' => $contribution['id']]);
1285 $this->assertEquals('civicrm_membership', $line['entity_table']);
1286 $this->assertEquals($membership['id'], $line['entity_id']);
1288 $this->callAPISuccess('contribution', 'completetransaction', [
1289 'id' => $contribution['id'],
1290 'trxn_id' => 'ipn_called',
1291 'payment_processor_id' => $this->_paymentProcessor
['id'],
1293 $line = $this->callAPISuccess('line_item', 'getsingle', ['contribution_id' => $contribution['id']]);
1294 $this->assertEquals('civicrm_membership', $line['entity_table']);
1295 $this->assertEquals($membership['id'], $line['entity_id']);
1297 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
1298 //renew it with processor setting completed - should extend membership
1299 $submitParams = array_merge($submitParams, [
1300 'contact_id' => $contribution['contact_id'],
1302 'frequency_interval' => 1,
1303 'frequency_unit' => $this->params
['recur_frequency_unit'],
1306 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 2]);
1307 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1308 $newContribution = $this->callAPISuccess('contribution', 'getsingle', [
1310 'NOT IN' => [$contribution['id']],
1312 'contribution_page_id' => $this->_ids
['contribution_page'],
1313 'contribution_status_id' => 2,
1315 $line = $this->callAPISuccess('line_item', 'getsingle', ['contribution_id' => $newContribution['id']]);
1316 $this->assertEquals('civicrm_membership', $line['entity_table']);
1317 $this->assertEquals($membership['id'], $line['entity_id']);
1319 $renewedMembership = $this->callAPISuccessGetSingle('membership', ['id' => $membershipPayment['membership_id']]);
1320 //no renewal as the date hasn't changed
1321 $this->assertEquals($membership['end_date'], $renewedMembership['end_date']);
1322 $recurringContribution = $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $newContribution['contribution_recur_id']]);
1323 $this->assertEquals(2, $recurringContribution['contribution_status_id']);
1327 * Test non-recur contribution with membership payment
1329 public function testSubmitMembershipIsSeparatePaymentNotRecur() {
1330 //Create recur contribution page.
1331 $this->setUpMembershipContributionPage(TRUE, TRUE);
1332 $dummyPP = Civi\Payment\System
::singleton()->getByProcessor($this->_paymentProcessor
);
1333 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_first_success']);
1335 //Sumbit payment with recur disabled.
1337 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
1338 'id' => (int) $this->_ids
['contribution_page'],
1340 'frequency_interval' => 1,
1341 'frequency_unit' => 'month',
1342 'billing_first_name' => 'Billy',
1343 'billing_middle_name' => 'Goat',
1344 'billing_last_name' => 'Gruff',
1345 'email' => 'billy@goat.gruff',
1346 'selectMembership' => $this->_ids
['membership_type'],
1347 'payment_processor_id' => 1,
1348 'credit_card_number' => '4111111111111111',
1349 'credit_card_type' => 'Visa',
1350 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
1354 //Assert if recur contribution is created.
1355 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1356 $recur = $this->callAPISuccess('contribution_recur', 'get', []);
1357 $this->assertEmpty($recur['count']);
1361 * Set up membership contribution page.
1362 * @param bool $isSeparatePayment
1363 * @param bool $isRecur
1364 * @param array $membershipTypeParams Parameters to pass to membershiptype.create API
1366 public function setUpMembershipContributionPage($isSeparatePayment = FALSE, $isRecur = FALSE, $membershipTypeParams = []) {
1367 $this->setUpMembershipBlockPriceSet($membershipTypeParams);
1368 $this->setupPaymentProcessor();
1369 $this->setUpContributionPage($isRecur);
1371 $this->callAPISuccess('membership_block', 'create', [
1372 'entity_id' => $this->_ids
['contribution_page'],
1373 'entity_table' => 'civicrm_contribution_page',
1374 'is_required' => TRUE,
1375 'is_active' => TRUE,
1376 'is_separate_payment' => $isSeparatePayment,
1377 'membership_type_default' => $this->_ids
['membership_type'],
1382 * Set up pledge block.
1384 public function setUpPledgeBlock() {
1386 'entity_table' => 'civicrm_contribution_page',
1387 'entity_id' => $this->_ids
['contribution_page'],
1388 'pledge_frequency_unit' => 'week',
1389 'is_pledge_interval' => 0,
1390 'pledge_start_date' => json_encode(['calendar_date' => date('Ymd', strtotime("+1 month"))]),
1392 $pledgeBlock = CRM_Pledge_BAO_PledgeBlock
::create($params);
1393 $this->_ids
['pledge_block_id'] = $pledgeBlock->id
;
1397 * The default data set does not include a complete default membership price set - not quite sure why.
1399 * This function ensures it exists & populates $this->_ids with it's data
1401 public function setUpMembershipBlockPriceSet($membershipTypeParams = []) {
1402 $this->_ids
['price_set'][] = $this->callAPISuccess('price_set', 'getvalue', [
1403 'name' => 'default_membership_type_amount',
1406 if (empty($this->_ids
['membership_type'])) {
1407 $membershipTypeParams = array_merge([
1409 ], $membershipTypeParams);
1410 $this->_ids
['membership_type'] = [$this->membershipTypeCreate($membershipTypeParams)];
1412 $priceField = $this->callAPISuccess('price_field', 'create', [
1413 'price_set_id' => reset($this->_ids
['price_set']),
1414 'name' => 'membership_amount',
1415 'label' => 'Membership Amount',
1416 'html_type' => 'Radio',
1419 $this->_ids
['price_field'][] = $priceField['id'];
1421 foreach ($this->_ids
['membership_type'] as $membershipTypeID) {
1422 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
1423 'name' => 'membership_amount',
1424 'label' => 'Membership Amount',
1425 'amount' => $this->_membershipBlockAmount
,
1426 'financial_type_id' => 'Donation',
1427 'format.only_id' => TRUE,
1428 'membership_type_id' => $membershipTypeID,
1429 'price_field_id' => $priceField['id'],
1431 $this->_ids
['price_field_value'][] = $priceFieldValue;
1433 if (!empty($this->_ids
['membership_type']['org2'])) {
1434 $priceField = $this->callAPISuccess('price_field', 'create', [
1435 'price_set_id' => reset($this->_ids
['price_set']),
1436 'name' => 'membership_org2',
1437 'label' => 'Membership Org2',
1438 'html_type' => 'Checkbox',
1441 $this->_ids
['price_field']['org2'] = $priceField['id'];
1443 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
1444 'name' => 'membership_org2',
1445 'label' => 'Membership org 2',
1447 'financial_type_id' => 'Member Dues',
1448 'format.only_id' => TRUE,
1449 'membership_type_id' => $this->_ids
['membership_type']['org2'],
1450 'price_field_id' => $priceField['id'],
1452 $this->_ids
['price_field_value']['org2'] = $priceFieldValue;
1454 $priceField = $this->callAPISuccess('price_field', 'create', [
1455 'price_set_id' => reset($this->_ids
['price_set']),
1456 'name' => 'Contribution',
1457 'label' => 'Contribution',
1458 'html_type' => 'Text',
1460 'is_enter_qty' => 1,
1462 $this->_ids
['price_field']['cont'] = $priceField['id'];
1463 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
1464 'name' => 'contribution',
1465 'label' => 'Give me money',
1467 'financial_type_id' => 'Donation',
1468 'format.only_id' => TRUE,
1469 'price_field_id' => $priceField['id'],
1471 $this->_ids
['price_field_value'][] = $priceFieldValue;
1475 * Add text field other amount to the price set.
1477 public function addOtherAmountFieldToMembershipPriceSet() {
1478 $this->_ids
['price_field']['other_amount'] = $this->callAPISuccess('price_field', 'create', [
1479 'price_set_id' => reset($this->_ids
['price_set']),
1480 'name' => 'other_amount',
1481 'label' => 'Other Amount',
1482 'html_type' => 'Text',
1483 'format.only_id' => TRUE,
1486 $this->_ids
['price_field_value']['other_amount'] = $this->callAPISuccess('price_field_value', 'create', [
1487 'financial_type_id' => 'Donation',
1488 'format.only_id' => TRUE,
1489 'label' => 'Other Amount',
1491 'price_field_id' => $this->_ids
['price_field']['other_amount'],
1496 * Help function to set up contribution page with some defaults.
1497 * @param bool $isRecur
1499 public function setUpContributionPage($isRecur = FALSE) {
1501 $this->params
['is_recur'] = 1;
1502 $this->params
['recur_frequency_unit'] = 'month';
1504 $this->params
['frontend_title'] = 'Test Frontend title';
1505 $contributionPageResult = $this->callAPISuccess($this->_entity
, 'create', $this->params
);
1506 if (empty($this->_ids
['price_set'])) {
1507 $priceSet = $this->callAPISuccess('price_set', 'create', $this->_priceSetParams
);
1508 $this->_ids
['price_set'][] = $priceSet['id'];
1510 $priceSetID = reset($this->_ids
['price_set']);
1511 CRM_Price_BAO_PriceSet
::addTo('civicrm_contribution_page', $contributionPageResult['id'], $priceSetID);
1513 if (empty($this->_ids
['price_field'])) {
1514 $priceField = $this->callAPISuccess('price_field', 'create', [
1515 'price_set_id' => $priceSetID,
1516 'label' => 'Goat Breed',
1517 'html_type' => 'Radio',
1519 $this->_ids
['price_field'] = [$priceField['id']];
1521 if (empty($this->_ids
['price_field_value'])) {
1522 $this->callAPISuccess('price_field_value', 'create', [
1523 'price_set_id' => $priceSetID,
1524 'price_field_id' => $priceField['id'],
1525 'label' => 'Long Haired Goat',
1526 'financial_type_id' => 'Donation',
1528 'non_deductible_amount' => 15,
1530 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
1531 'price_set_id' => $priceSetID,
1532 'price_field_id' => $priceField['id'],
1533 'label' => 'Shoe-eating Goat',
1534 'financial_type_id' => 'Donation',
1536 'non_deductible_amount' => 5,
1538 $this->_ids
['price_field_value'] = [$priceFieldValue['id']];
1540 $this->_ids
['price_field_value']['cheapskate'] = $this->callAPISuccess('price_field_value', 'create', [
1541 'price_set_id' => $priceSetID,
1542 'price_field_id' => $priceField['id'],
1543 'label' => 'Stingy Goat',
1544 'financial_type_id' => 'Donation',
1546 'non_deductible_amount' => 0,
1549 $this->_ids
['contribution_page'] = $contributionPageResult['id'];
1553 * Helper function to set up contribution page which can be used to purchase a
1554 * membership type for different intervals.
1556 public function setUpMultiIntervalMembershipContributionPage() {
1557 $this->setupPaymentProcessor();
1558 $contributionPage = $this->callAPISuccess($this->_entity
, 'create', $this->params
);
1559 $this->_ids
['contribution_page'] = $contributionPage['id'];
1561 $this->_ids
['membership_type'] = $this->membershipTypeCreate([
1564 'duration_unit' => 'month',
1567 $priceSet = civicrm_api3('PriceSet', 'create', [
1568 'is_quick_config' => 0,
1569 'extends' => 'CiviMember',
1570 'financial_type_id' => 'Member Dues',
1571 'title' => 'CRM-21177',
1573 $this->_ids
['price_set'] = $priceSet['id'];
1575 $priceField = $this->callAPISuccess('price_field', 'create', [
1576 'price_set_id' => $this->_ids
['price_set'],
1577 'name' => 'membership_type',
1578 'label' => 'Membership Type',
1579 'html_type' => 'Radio',
1581 $this->_ids
['price_field'] = $priceField['id'];
1583 $priceFieldValueMonthly = $this->callAPISuccess('price_field_value', 'create', [
1584 'name' => 'CRM-21177_Monthly',
1585 'label' => 'CRM-21177 - Monthly',
1587 'membership_num_terms' => 1,
1588 'membership_type_id' => $this->_ids
['membership_type'],
1589 'price_field_id' => $this->_ids
['price_field'],
1590 'financial_type_id' => 'Member Dues',
1592 $this->_ids
['price_field_value_monthly'] = $priceFieldValueMonthly['id'];
1594 $priceFieldValueYearly = $this->callAPISuccess('price_field_value', 'create', [
1595 'name' => 'CRM-21177_Yearly',
1596 'label' => 'CRM-21177 - Yearly',
1598 'membership_num_terms' => 12,
1599 'membership_type_id' => $this->_ids
['membership_type'],
1600 'price_field_id' => $this->_ids
['price_field'],
1601 'financial_type_id' => 'Member Dues',
1603 $this->_ids
['price_field_value_yearly'] = $priceFieldValueYearly['id'];
1605 CRM_Price_BAO_PriceSet
::addTo('civicrm_contribution_page', $this->_ids
['contribution_page'], $this->_ids
['price_set']);
1607 $this->callAPISuccess('membership_block', 'create', [
1608 'entity_id' => $this->_ids
['contribution_page'],
1609 'entity_table' => 'civicrm_contribution_page',
1610 'is_required' => TRUE,
1611 'is_separate_payment' => FALSE,
1612 'is_active' => TRUE,
1613 'membership_type_default' => $this->_ids
['membership_type'],
1618 * Test submit with a membership block in place.
1620 public function testSubmitMultiIntervalMembershipContributionPage() {
1621 $this->setUpMultiIntervalMembershipContributionPage();
1623 'price_' . $this->_ids
['price_field'] => $this->_ids
['price_field_value_monthly'],
1624 'id' => (int) $this->_ids
['contribution_page'],
1626 'first_name' => 'Billy',
1627 'last_name' => 'Gruff',
1628 'email' => 'billy@goat.gruff',
1629 'payment_processor_id' => $this->_ids
['payment_processor'],
1630 'credit_card_number' => '4111111111111111',
1631 'credit_card_type' => 'Visa',
1632 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
1636 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1638 $submitParams['price_' . $this->_ids
['price_field']] = $this->_ids
['price_field_value_yearly'];
1639 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1641 $contribution = $this->callAPISuccess('Contribution', 'get', [
1642 'contribution_page_id' => $this->_ids
['contribution_page'],
1644 'api.ContributionRecur.getsingle' => [],
1646 $this->assertEquals(1, $contribution['values'][0]['api.ContributionRecur.getsingle']['frequency_interval']);
1647 //$this->assertEquals(12, $contribution['values'][1]['api.ContributionRecur.getsingle']['frequency_interval']);
1650 public static function setUpBeforeClass() {
1651 // put stuff here that should happen before all tests in this unit
1655 * @throws \Exception
1657 public static function tearDownAfterClass() {
1658 $tablesToTruncate = [
1660 'civicrm_financial_type',
1661 'civicrm_contribution',
1662 'civicrm_contribution_page',
1664 $unitTest = new CiviUnitTestCase();
1665 $unitTest->quickCleanup($tablesToTruncate);
1669 * Create a payment processor instance.
1671 protected function setupPaymentProcessor() {
1672 $this->params
['payment_processor_id'] = $this->_ids
['payment_processor'] = $this->paymentProcessorCreate([
1673 'payment_processor_type_id' => 'Dummy',
1674 'class_name' => 'Payment_Dummy',
1675 'billing_mode' => 1,
1677 $this->_paymentProcessor
= $this->callAPISuccess('payment_processor', 'getsingle', ['id' => $this->params
['payment_processor_id']]);
1681 * Test submit recurring pledge.
1683 * - 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.
1685 public function testSubmitPledgePaymentPaymentProcessorRecurFuturePayment() {
1686 $this->params
['adjust_recur_start_date'] = TRUE;
1687 $this->params
['is_pay_later'] = FALSE;
1688 $this->setUpContributionPage();
1689 $this->setUpPledgeBlock();
1690 $this->setupPaymentProcessor();
1691 $dummyPP = Civi\Payment\System
::singleton()->getByProcessor($this->_paymentProcessor
);
1692 $dummyPP->setDoDirectPaymentResult(['payment_status_id' => 1, 'trxn_id' => 'create_first_success']);
1695 'id' => (int) $this->_ids
['contribution_page'],
1697 'billing_first_name' => 'Billy',
1698 'billing_middle_name' => 'Goat',
1699 'billing_last_name' => 'Gruff',
1700 'email' => 'billy@goat.gruff',
1701 'payment_processor_id' => 1,
1702 'credit_card_number' => '4111111111111111',
1703 'credit_card_type' => 'Visa',
1704 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
1706 'pledge_frequency_interval' => 1,
1707 'pledge_frequency_unit' => 'week',
1708 'pledge_installments' => 3,
1709 'is_pledge' => TRUE,
1710 'pledge_block_id' => (int) $this->_ids
['pledge_block_id'],
1713 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
1715 // Check if contribution created.
1716 $contribution = $this->callAPISuccess('contribution', 'getsingle', [
1717 'contribution_page_id' => $this->_ids
['contribution_page'],
1718 // Will be pending when actual payment processor is used (dummy processor does not support future payments).
1719 'contribution_status_id' => 'Completed',
1722 $this->assertEquals('create_first_success', $contribution['trxn_id']);
1724 // Check if pledge created.
1725 $pledge = $this->callAPISuccess('pledge', 'getsingle', []);
1726 $this->assertEquals(date('Ymd', strtotime($pledge['pledge_start_date'])), date('Ymd', strtotime("+1 month")));
1727 $this->assertEquals($pledge['pledge_amount'], 300.00);
1729 // Check if pledge payments created.
1731 'pledge_id' => $pledge['id'],
1733 $pledgePayment = $this->callAPISuccess('pledge_payment', 'get', $params);
1734 $this->assertEquals($pledgePayment['count'], 3);
1735 $this->assertEquals(date('Ymd', strtotime($pledgePayment['values'][1]['scheduled_date'])), date('Ymd', strtotime("+1 month")));
1736 $this->assertEquals($pledgePayment['values'][1]['scheduled_amount'], 100.00);
1737 // Will be pending when actual payment processor is used (dummy processor does not support future payments).
1738 $this->assertEquals($pledgePayment['values'][1]['status_id'], 1);
1740 // Check contribution recur record.
1741 $recur = $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $contribution['contribution_recur_id']]);
1742 $this->assertEquals(date('Ymd', strtotime($recur['start_date'])), date('Ymd', strtotime("+1 month")));
1743 $this->assertEquals($recur['amount'], 100.00);
1744 // In progress status.
1745 $this->assertEquals($recur['contribution_status_id'], 5);
1749 * Test submit pledge payment.
1751 * - test submitting a pledge payment using contribution form.
1753 public function testSubmitPledgePayment() {
1754 $this->testSubmitPledgePaymentPaymentProcessorRecurFuturePayment();
1755 $pledge = $this->callAPISuccess('pledge', 'getsingle', []);
1757 'pledge_id' => $pledge['id'],
1760 'id' => (int) $pledge['pledge_contribution_page_id'],
1761 'pledge_amount' => [2 => 1],
1762 'billing_first_name' => 'Billy',
1763 'billing_middle_name' => 'Goat',
1764 'billing_last_name' => 'Gruff',
1765 'email' => 'billy@goat.gruff',
1766 'payment_processor_id' => 1,
1767 'credit_card_number' => '4111111111111111',
1768 'credit_card_type' => 'Visa',
1769 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040],
1771 'pledge_id' => $pledge['id'],
1772 'cid' => $pledge['contact_id'],
1773 'contact_id' => $pledge['contact_id'],
1775 'is_pledge' => TRUE,
1776 'pledge_block_id' => $this->_ids
['pledge_block_id'],
1778 $pledgePayment = $this->callAPISuccess('pledge_payment', 'get', $params);
1779 $this->assertEquals($pledgePayment['values'][2]['status_id'], 2);
1781 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__
, __FILE__
, 'submit contribution page', NULL);
1783 // Check if contribution created.
1784 $contribution = $this->callAPISuccess('contribution', 'getsingle', [
1785 'contribution_page_id' => $pledge['pledge_contribution_page_id'],
1786 'contribution_status_id' => 'Completed',
1787 'contact_id' => $pledge['contact_id'],
1788 'contribution_recur_id' => ['IS NULL' => 1],
1791 $this->assertEquals(100.00, $contribution['total_amount']);
1792 $pledgePayment = $this->callAPISuccess('pledge_payment', 'get', $params);
1793 $this->assertEquals($pledgePayment['values'][2]['status_id'], 1, "This pledge payment should have been completed");
1794 $this->assertEquals($pledgePayment['values'][2]['contribution_id'], $contribution['id']);
1798 * Test form submission with multiple option price set.
1800 * @param string $thousandSeparator
1801 * punctuation used to refer to thousands.
1803 * @dataProvider getThousandSeparators
1805 public function testSubmitContributionPageWithPriceSet($thousandSeparator) {
1806 $this->setCurrencySeparators($thousandSeparator);
1807 $this->_priceSetParams
['is_quick_config'] = 0;
1808 $this->setUpContributionPage();
1810 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
1811 'id' => (int) $this->_ids
['contribution_page'],
1813 'first_name' => 'Billy',
1814 'last_name' => 'Gruff',
1815 'email' => 'billy@goat.gruff',
1816 'is_pay_later' => TRUE,
1818 $this->addPriceFields($submitParams);
1820 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1821 $contribution = $this->callAPISuccessGetSingle('contribution', [
1822 'contribution_page_id' => $this->_ids
['contribution_page'],
1823 'contribution_status_id' => 'Pending',
1825 $this->assertEquals(80, $contribution['total_amount']);
1826 $lineItems = $this->callAPISuccess('LineItem', 'get', [
1827 'contribution_id' => $contribution['id'],
1829 $this->assertEquals(3, $lineItems['count']);
1830 $totalLineAmount = 0;
1831 foreach ($lineItems['values'] as $lineItem) {
1832 $totalLineAmount = $totalLineAmount +
$lineItem['line_total'];
1834 $this->assertEquals(80, $totalLineAmount);
1838 * Function to add additional price fields to priceset.
1839 * @param array $params
1841 public function addPriceFields(&$params) {
1842 $priceSetID = reset($this->_ids
['price_set']);
1843 $priceField = $this->callAPISuccess('price_field', 'create', [
1844 'price_set_id' => $priceSetID,
1845 'label' => 'Chicken Breed',
1846 'html_type' => 'CheckBox',
1848 $priceFieldValue1 = $this->callAPISuccess('price_field_value', 'create', [
1849 'price_set_id' => $priceSetID,
1850 'price_field_id' => $priceField['id'],
1851 'label' => 'Shoe-eating chicken -1',
1852 'financial_type_id' => 'Donation',
1855 $priceFieldValue2 = $this->callAPISuccess('price_field_value', 'create', [
1856 'price_set_id' => $priceSetID,
1857 'price_field_id' => $priceField['id'],
1858 'label' => 'Shoe-eating chicken -2',
1859 'financial_type_id' => 'Donation',
1862 $params['price_' . $priceField['id']] = [
1863 $priceFieldValue1['id'] => 1,
1864 $priceFieldValue2['id'] => 1,
1869 * Test Tax Amount is calculated properly when using PriceSet with Field Type = Text/Numeric Quantity
1871 * @param string $thousandSeparator
1872 * punctuation used to refer to thousands.
1874 * @dataProvider getThousandSeparators
1876 public function testSubmitContributionPageWithPriceSetQuantity($thousandSeparator) {
1877 $this->setCurrencySeparators($thousandSeparator);
1878 $this->_priceSetParams
['is_quick_config'] = 0;
1879 $this->enableTaxAndInvoicing();
1880 $financialType = $this->createFinancialType();
1881 $financialTypeId = $financialType['id'];
1882 // 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%
1883 $this->relationForFinancialTypeWithFinancialAccount($financialType['id'], 5);
1885 $this->setUpContributionPage();
1887 'price_' . $this->_ids
['price_field'][0] => reset($this->_ids
['price_field_value']),
1888 'id' => (int) $this->_ids
['contribution_page'],
1889 'first_name' => 'J',
1891 'email' => 'JT@ohcanada.ca',
1892 'is_pay_later' => TRUE,
1893 'receive_date' => date('Y-m-d H:i:s'),
1896 // Create PriceSet/PriceField
1897 $priceSetID = reset($this->_ids
['price_set']);
1898 $priceField = $this->callAPISuccess('price_field', 'create', [
1899 'price_set_id' => $priceSetID,
1900 'label' => 'Printing Rights',
1901 'html_type' => 'Text',
1903 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
1904 'price_set_id' => $priceSetID,
1905 'price_field_id' => $priceField['id'],
1906 'label' => 'Printing Rights',
1907 'financial_type_id' => $financialTypeId,
1908 'amount' => '16.95',
1910 $priceFieldId = $priceField['id'];
1912 // Set quantity for our test
1913 $submitParams['price_' . $priceFieldId] = 180;
1915 // contribution_page submit requires amount and tax_amount - and that's ok we're not testing that - we're testing at the LineItem level
1916 $submitParams['amount'] = $this->formatMoneyInput(180 * 16.95);
1917 // This is the correct Tax Amount - use it later to compare to what the CiviCRM Core came up with at the LineItem level
1918 $submitParams['tax_amount'] = $this->formatMoneyInput(180 * 16.95 * 0.10);
1920 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1921 $contribution = $this->callAPISuccessGetSingle('contribution', [
1922 'contribution_page_id' => $this->_ids
['contribution_page'],
1925 // Retrieve the lineItem that belongs to the Printing Rights and check the tax_amount CiviCRM Core calculated for it
1926 $lineItem = $this->callAPISuccessGetSingle('LineItem', [
1927 'contribution_id' => $contribution['id'],
1928 'label' => 'Printing Rights',
1931 $lineItem_TaxAmount = round($lineItem['tax_amount'], 2);
1933 $this->assertEquals($lineItem['line_total'], $contribution['total_amount'], 'Contribution total should match line total');
1934 $this->assertEquals($lineItem_TaxAmount, round(180 * 16.95 * 0.10, 2), 'Wrong Sales Tax Amount is calculated and stored.');
1938 * Test validating a contribution page submit.
1940 * @throws \CRM_Core_Exception
1942 public function testValidate() {
1943 $this->setUpContributionPage();
1944 $errors = $this->callAPISuccess('ContributionPage', 'validate', array_merge($this->getBasicSubmitParams(), ['action' => 'submit']))['values'];
1945 $this->assertEmpty($errors);
1949 * Test validating a contribution page submit in POST context.
1951 * A likely use case for the validation is when the is being submitted and some handling is
1952 * to be done before processing but the validity of input needs to be checked first.
1954 * For example Paypal Checkout will replace the confirm button with it's own but we are able to validate
1955 * before paypal launches it's modal. In this case the $_REQUEST is post but we need validation to succeed.
1957 public function testValidatePost() {
1958 $_SERVER['REQUEST_METHOD'] = 'POST';
1959 $this->setUpContributionPage();
1960 $errors = $this->callAPISuccess('ContributionPage', 'validate', array_merge($this->getBasicSubmitParams(), ['action' => 'submit']))['values'];
1961 $this->assertEmpty($errors);
1962 unset($_SERVER['REQUEST_METHOD']);
1966 * Test that an error is generated if required fields are not submitted.
1968 public function testValidateOutputOnMissingRecurFields() {
1969 $this->params
['is_recur_interval'] = 1;
1970 $this->setUpContributionPage(TRUE);
1971 $submitParams = array_merge($this->getBasicSubmitParams(), ['action' => 'submit']);
1972 $submitParams['is_recur'] = 1;
1973 $submitParams['frequency_interval'] = '';
1974 $submitParams['frequency_unit'] = '';
1975 $errors = $this->callAPISuccess('ContributionPage', 'validate', $submitParams)['values'];
1976 $this->assertEquals('Please enter a number for how often you want to make this recurring contribution (EXAMPLE: Every 3 months).', $errors['frequency_interval']);
1980 * Implements hook_civicrm_alterPaymentProcessorParams().
1982 * @throws \Exception
1984 public function hook_civicrm_alterPaymentProcessorParams($paymentObj, &$rawParams, &$cookedParams) {
1985 // Ensure total_amount are the same if they're both given.
1986 $total_amount = CRM_Utils_Array
::value('total_amount', $rawParams);
1987 $amount = CRM_Utils_Array
::value('amount', $rawParams);
1988 if (!empty($total_amount) && !empty($amount) && $total_amount != $amount) {
1989 throw new Exception("total_amount '$total_amount' and amount '$amount' differ.");
1992 // Log parameters for later debugging and testing.
1993 $message = __FUNCTION__
. ": {$rawParams['TEST_UNIQ']}:";
1994 $log_params = array_intersect_key($rawParams, [
1996 'total_amount' => 1,
1997 'contributionID' => 1,
1999 $message .= json_encode($log_params);
2000 $log = new CRM_Utils_SystemLogger();
2001 $log->debug($message, $_REQUEST);
2005 * Get the params for a basic simple submit.
2009 protected function getBasicSubmitParams() {
2010 $priceFieldID = reset($this->_ids
['price_field']);
2011 $priceFieldValueID = reset($this->_ids
['price_field_value']);
2013 'price_' . $priceFieldID => $priceFieldValueID,
2014 'id' => (int) $this->_ids
['contribution_page'],
2016 'priceSetId' => $this->_ids
['price_set'][0],
2017 'payment_processor_id' => 0,
2019 return $submitParams;