Merge pull request #13337 from GinkgoFJG/crmPageTitle
[civicrm-core.git] / tests / phpunit / api / v3 / ContributionPageTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 * Test APIv3 civicrm_contribute_recur* functions
30 *
31 * @package CiviCRM_APIv3
32 * @subpackage API_Contribution
33 * @group headless
34 */
35 class api_v3_ContributionPageTest extends CiviUnitTestCase {
36 protected $_apiversion = 3;
37 protected $testAmount = 34567;
38 protected $params;
39 protected $id = 0;
40 protected $contactIds = array();
41 protected $_entity = 'contribution_page';
42 protected $contribution_result = NULL;
43 protected $_priceSetParams = array();
44 protected $_membershipBlockAmount = 2;
45 /**
46 * Payment processor details.
47 * @var array
48 */
49 protected $_paymentProcessor = array();
50
51 /**
52 * @var array
53 * - contribution_page
54 * - price_set
55 * - price_field
56 * - price_field_value
57 */
58 protected $_ids = array();
59
60
61 public $DBResetRequired = TRUE;
62
63 public function setUp() {
64 parent::setUp();
65 $this->contactIds[] = $this->individualCreate();
66 $this->params = array(
67 'title' => "Test Contribution Page",
68 'financial_type_id' => 1,
69 'currency' => 'NZD',
70 'goal_amount' => $this->testAmount,
71 'is_pay_later' => 1,
72 'is_monetary' => TRUE,
73 'is_email_receipt' => TRUE,
74 'receipt_from_email' => 'yourconscience@donate.com',
75 'receipt_from_name' => 'Ego Freud',
76 );
77
78 $this->_priceSetParams = array(
79 'is_quick_config' => 1,
80 'extends' => 'CiviContribute',
81 'financial_type_id' => 'Donation',
82 'title' => 'my Page',
83 );
84 }
85
86 public function tearDown() {
87 foreach ($this->contactIds as $id) {
88 $this->callAPISuccess('contact', 'delete', array('id' => $id));
89 }
90 $this->quickCleanUpFinancialEntities();
91 }
92
93 public function testCreateContributionPage() {
94 $result = $this->callAPIAndDocument($this->_entity, 'create', $this->params, __FUNCTION__, __FILE__);
95 $this->assertEquals(1, $result['count']);
96 $this->assertNotNull($result['values'][$result['id']]['id']);
97 $this->getAndCheck($this->params, $result['id'], $this->_entity);
98 }
99
100 public function testGetBasicContributionPage() {
101 $createResult = $this->callAPISuccess($this->_entity, 'create', $this->params);
102 $this->id = $createResult['id'];
103 $getParams = array(
104 'currency' => 'NZD',
105 'financial_type_id' => 1,
106 );
107 $getResult = $this->callAPIAndDocument($this->_entity, 'get', $getParams, __FUNCTION__, __FILE__);
108 $this->assertEquals(1, $getResult['count']);
109 }
110
111 public function testGetContributionPageByAmount() {
112 $createResult = $this->callAPISuccess($this->_entity, 'create', $this->params);
113 $this->id = $createResult['id'];
114 $getParams = array(
115 'amount' => '' . $this->testAmount, // 3456
116 'currency' => 'NZD',
117 'financial_type_id' => 1,
118 );
119 $getResult = $this->callAPISuccess($this->_entity, 'get', $getParams);
120 $this->assertEquals(1, $getResult['count']);
121 }
122
123 public function testDeleteContributionPage() {
124 $createResult = $this->callAPISuccess($this->_entity, 'create', $this->params);
125 $deleteParams = array('id' => $createResult['id']);
126 $this->callAPIAndDocument($this->_entity, 'delete', $deleteParams, __FUNCTION__, __FILE__);
127 $checkDeleted = $this->callAPISuccess($this->_entity, 'get', array());
128 $this->assertEquals(0, $checkDeleted['count']);
129 }
130
131 public function testGetFieldsContributionPage() {
132 $result = $this->callAPISuccess($this->_entity, 'getfields', array('action' => 'create'));
133 $this->assertEquals(12, $result['values']['start_date']['type']);
134 }
135
136
137 /**
138 * Test form submission with basic price set.
139 */
140 public function testSubmit() {
141 $this->setUpContributionPage();
142 $submitParams = $this->getBasicSubmitParams();
143
144 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
145 $contribution = $this->callAPISuccess('contribution', 'getsingle', array('contribution_page_id' => $this->_ids['contribution_page']));
146 //assert non-deductible amount
147 $this->assertEquals(5.00, $contribution['non_deductible_amount']);
148 }
149
150 /**
151 * Test form submission with basic price set.
152 */
153 public function testSubmitZeroDollar() {
154 $this->setUpContributionPage();
155 $priceFieldID = reset($this->_ids['price_field']);
156 $submitParams = [
157 'price_' . $priceFieldID => $this->_ids['price_field_value']['cheapskate'],
158 'id' => (int) $this->_ids['contribution_page'],
159 'amount' => 0,
160 'priceSetId' => $this->_ids['price_set'][0],
161 'payment_processor_id' => '',
162 ];
163
164 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
165 $contribution = $this->callAPISuccess('contribution', 'getsingle', array('contribution_page_id' => $this->_ids['contribution_page']));
166
167 $this->assertEquals($this->formatMoneyInput(0), $contribution['non_deductible_amount']);
168 $this->assertEquals($this->formatMoneyInput(0), $contribution['total_amount']);
169 }
170
171 /**
172 * Test form submission with billing first & last name where the contact does NOT
173 * otherwise have one.
174 */
175 public function testSubmitNewBillingNameData() {
176 $this->setUpContributionPage();
177 $contact = $this->callAPISuccess('Contact', 'create', array('contact_type' => 'Individual', 'email' => 'wonderwoman@amazon.com'));
178 $priceFieldID = reset($this->_ids['price_field']);
179 $priceFieldValueID = reset($this->_ids['price_field_value']);
180 $submitParams = array(
181 'price_' . $priceFieldID => $priceFieldValueID,
182 'id' => (int) $this->_ids['contribution_page'],
183 'amount' => 10,
184 'billing_first_name' => 'Wonder',
185 'billing_last_name' => 'Woman',
186 'contactID' => $contact['id'],
187 'email' => 'wonderwoman@amazon.com',
188 );
189
190 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
191 $contact = $this->callAPISuccess('Contact', 'get', array(
192 'id' => $contact['id'],
193 'return' => array(
194 'first_name',
195 'last_name',
196 'sort_name',
197 'display_name',
198 ),
199 ));
200 $this->assertEquals(array(
201 'first_name' => 'Wonder',
202 'last_name' => 'Woman',
203 'display_name' => 'Wonder Woman',
204 'sort_name' => 'Woman, Wonder',
205 'id' => $contact['id'],
206 'contact_id' => $contact['id'],
207 ), $contact['values'][$contact['id']]);
208
209 }
210
211 /**
212 * Test form submission with billing first & last name where the contact does
213 * otherwise have one and should not be overwritten.
214 */
215 public function testSubmitNewBillingNameDoNotOverwrite() {
216 $this->setUpContributionPage();
217 $contact = $this->callAPISuccess('Contact', 'create', array(
218 'contact_type' => 'Individual',
219 'email' => 'wonderwoman@amazon.com',
220 'first_name' => 'Super',
221 'last_name' => 'Boy',
222 ));
223 $priceFieldID = reset($this->_ids['price_field']);
224 $priceFieldValueID = reset($this->_ids['price_field_value']);
225 $submitParams = array(
226 'price_' . $priceFieldID => $priceFieldValueID,
227 'id' => (int) $this->_ids['contribution_page'],
228 'amount' => 10,
229 'billing_first_name' => 'Wonder',
230 'billing_last_name' => 'Woman',
231 'contactID' => $contact['id'],
232 'email' => 'wonderwoman@amazon.com',
233 );
234
235 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
236 $contact = $this->callAPISuccess('Contact', 'get', array(
237 'id' => $contact['id'],
238 'return' => array(
239 'first_name',
240 'last_name',
241 'sort_name',
242 'display_name',
243 ),
244 ));
245 $this->assertEquals(array(
246 'first_name' => 'Super',
247 'last_name' => 'Boy',
248 'display_name' => 'Super Boy',
249 'sort_name' => 'Boy, Super',
250 'id' => $contact['id'],
251 'contact_id' => $contact['id'],
252 ), $contact['values'][$contact['id']]);
253
254 }
255
256 /**
257 * Test process with instant payment when more than one configured for the page.
258 *
259 * CRM-16923
260 */
261 public function testSubmitRecurMultiProcessorInstantPayment() {
262 $this->setUpContributionPage();
263 $this->setupPaymentProcessor();
264 $paymentProcessor2ID = $this->paymentProcessorCreate(array(
265 'payment_processor_type_id' => 'Dummy',
266 'name' => 'processor 2',
267 'class_name' => 'Payment_Dummy',
268 'billing_mode' => 1,
269 ));
270 $dummyPP = Civi\Payment\System::singleton()->getById($paymentProcessor2ID);
271 $dummyPP->setDoDirectPaymentResult(array(
272 'payment_status_id' => 1,
273 'trxn_id' => 'create_first_success',
274 'fee_amount' => .85,
275 ));
276 $processor = $dummyPP->getPaymentProcessor();
277 $this->callAPISuccess('ContributionPage', 'create', array(
278 'id' => $this->_ids['contribution_page'],
279 'payment_processor' => array($paymentProcessor2ID, $this->_ids['payment_processor']),
280 ));
281
282 $priceFieldID = reset($this->_ids['price_field']);
283 $priceFieldValueID = reset($this->_ids['price_field_value']);
284 $submitParams = array(
285 'price_' . $priceFieldID => $priceFieldValueID,
286 'id' => (int) $this->_ids['contribution_page'],
287 'amount' => 10,
288 'is_recur' => 1,
289 'frequency_interval' => 1,
290 'frequency_unit' => 'month',
291 'payment_processor_id' => $paymentProcessor2ID,
292 );
293
294 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
295 $contribution = $this->callAPISuccess('contribution', 'getsingle', array(
296 'contribution_page_id' => $this->_ids['contribution_page'],
297 'contribution_status_id' => 1,
298 ));
299 $this->assertEquals('create_first_success', $contribution['trxn_id']);
300 $this->assertEquals(10, $contribution['total_amount']);
301 $this->assertEquals(.85, $contribution['fee_amount']);
302 $this->assertEquals(9.15, $contribution['net_amount']);
303 $this->_checkFinancialRecords(array(
304 'id' => $contribution['id'],
305 'total_amount' => $contribution['total_amount'],
306 'payment_instrument_id' => $processor['payment_instrument_id'],
307 ), 'online');
308 }
309
310 /**
311 * Test submit with a membership block in place.
312 */
313 public function testSubmitMembershipBlockNotSeparatePayment() {
314 $this->setUpMembershipContributionPage();
315 $submitParams = array(
316 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
317 'id' => (int) $this->_ids['contribution_page'],
318 'amount' => 10,
319 'billing_first_name' => 'Billy',
320 'billing_middle_name' => 'Goat',
321 'billing_last_name' => 'Gruff',
322 'selectMembership' => $this->_ids['membership_type'],
323
324 );
325
326 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
327 $contribution = $this->callAPISuccess('contribution', 'getsingle', array('contribution_page_id' => $this->_ids['contribution_page']));
328 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', array('contribution_id' => $contribution['id']));
329 $this->callAPISuccessGetSingle('LineItem', array('contribution_id' => $contribution['id'], 'entity_id' => $membershipPayment['id']));
330 }
331
332 /**
333 * Test submit with a membership block in place works with renewal.
334 */
335 public function testSubmitMembershipBlockNotSeparatePaymentProcessorInstantRenew() {
336 $this->setUpMembershipContributionPage();
337 $dummyPP = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor);
338 $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 1));
339 $submitParams = array(
340 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
341 'id' => (int) $this->_ids['contribution_page'],
342 'amount' => 10,
343 'billing_first_name' => 'Billy',
344 'billing_middle_name' => 'Goat',
345 'billing_last_name' => 'Gruff',
346 'selectMembership' => $this->_ids['membership_type'],
347 'payment_processor_id' => 1,
348 'credit_card_number' => '4111111111111111',
349 'credit_card_type' => 'Visa',
350 'credit_card_exp_date' => array('M' => 9, 'Y' => 2040),
351 'cvv2' => 123,
352 );
353
354 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
355 $contribution = $this->callAPISuccess('contribution', 'getsingle', array('contribution_page_id' => $this->_ids['contribution_page']));
356 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', array('contribution_id' => $contribution['id']));
357 $this->callAPISuccessGetCount('LineItem', array(
358 'entity_table' => 'civicrm_membership',
359 'entity_id' => $membershipPayment['id'],
360 ), 1);
361
362 $submitParams['contact_id'] = $contribution['contact_id'];
363
364 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
365 $this->callAPISuccessGetCount('LineItem', array(
366 'entity_table' => 'civicrm_membership',
367 'entity_id' => $membershipPayment['id'],
368 ), 2);
369 $membership = $this->callAPISuccessGetSingle('Membership', array(
370 'id' => $membershipPayment['membership_id'],
371 'return' => array('end_date', 'join_date', 'start_date'),
372 ));
373 $this->assertEquals(date('Y-m-d'), $membership['start_date']);
374 $this->assertEquals(date('Y-m-d'), $membership['join_date']);
375 $this->assertEquals(date('Y-m-d', strtotime('+ 2 year - 1 day')), $membership['end_date']);
376 }
377
378 /**
379 * Test submit with a membership block in place.
380 */
381 public function testSubmitMembershipBlockNotSeparatePaymentWithEmail() {
382 $mut = new CiviMailUtils($this, TRUE);
383 $this->setUpMembershipContributionPage();
384 $this->addProfile('supporter_profile', $this->_ids['contribution_page']);
385
386 $submitParams = array(
387 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
388 'id' => (int) $this->_ids['contribution_page'],
389 'amount' => 10,
390 'billing_first_name' => 'Billy',
391 'billing_middle_name' => 'Goat',
392 'billing_last_name' => 'Gruff',
393 'selectMembership' => $this->_ids['membership_type'],
394 'email-Primary' => 'billy-goat@the-bridge.net',
395 'payment_processor_id' => $this->_paymentProcessor['id'],
396 'credit_card_number' => '4111111111111111',
397 'credit_card_type' => 'Visa',
398 'credit_card_exp_date' => array('M' => 9, 'Y' => 2040),
399 'cvv2' => 123,
400 );
401
402 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
403 $contribution = $this->callAPISuccess('contribution', 'getsingle', array('contribution_page_id' => $this->_ids['contribution_page']));
404 $this->callAPISuccess('membership_payment', 'getsingle', array('contribution_id' => $contribution['id']));
405 $mut->checkMailLog(array(
406 'Membership Type: General',
407 ));
408 $mut->stop();
409 $mut->clearMessages();
410 }
411
412 /**
413 * Test submit with a membership block in place.
414 */
415 public function testSubmitMembershipBlockNotSeparatePaymentZeroDollarsWithEmail() {
416 $mut = new CiviMailUtils($this, TRUE);
417 $this->_ids['membership_type'] = array($this->membershipTypeCreate(array('minimum_fee' => 0)));
418 $this->setUpMembershipContributionPage();
419 $this->addProfile('supporter_profile', $this->_ids['contribution_page']);
420
421 $submitParams = array(
422 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
423 'id' => (int) $this->_ids['contribution_page'],
424 'amount' => 0,
425 'billing_first_name' => 'Billy',
426 'billing_middle_name' => 'Goat',
427 'billing_last_name' => 'Gruffier',
428 'selectMembership' => $this->_ids['membership_type'],
429 'email-Primary' => 'billy-goat@the-new-bridge.net',
430 'payment_processor_id' => $this->params['payment_processor_id'],
431 );
432
433 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
434 $contribution = $this->callAPISuccess('contribution', 'getsingle', array('contribution_page_id' => $this->_ids['contribution_page']));
435 $this->callAPISuccess('membership_payment', 'getsingle', array('contribution_id' => $contribution['id']));
436 //Assert only one mail is being sent.
437 $msgs = $mut->getAllMessages();
438 $this->assertCount(1, $msgs);
439
440 $mut->checkMailLog(array(
441 'Membership Type: General',
442 'Gruffier',
443 ),
444 array(
445 'Amount',
446 )
447 );
448 $mut->stop();
449 $mut->clearMessages();
450 }
451
452 /**
453 * Test submit with a pay later abnd check line item in mails.
454 */
455 public function testSubmitMembershipBlockIsSeparatePaymentPayLaterWithEmail() {
456 $mut = new CiviMailUtils($this, TRUE);
457 $this->setUpMembershipContributionPage();
458 $submitParams = array(
459 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
460 'id' => (int) $this->_ids['contribution_page'],
461 'amount' => 10,
462 'billing_first_name' => 'Billy',
463 'billing_middle_name' => 'Goat',
464 'billing_last_name' => 'Gruff',
465 'is_pay_later' => 1,
466 'selectMembership' => $this->_ids['membership_type'],
467 'email-Primary' => 'billy-goat@the-bridge.net',
468 );
469
470 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
471 $contribution = $this->callAPISuccess('contribution', 'getsingle', array('contribution_page_id' => $this->_ids['contribution_page']));
472 $this->callAPISuccess('membership_payment', 'getsingle', array('contribution_id' => $contribution['id']));
473 $mut->checkMailLog(array(
474 'Membership Amount -... $ 2.00',
475 ));
476 $mut->stop();
477 $mut->clearMessages();
478 }
479
480 /**
481 * Test submit with a membership block in place.
482 */
483 public function testSubmitMembershipBlockIsSeparatePayment() {
484 $this->setUpMembershipContributionPage(TRUE);
485 $this->_ids['membership_type'] = array($this->membershipTypeCreate(array('minimum_fee' => 2)));
486 $submitParams = array(
487 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
488 'id' => (int) $this->_ids['contribution_page'],
489 'amount' => 10,
490 'billing_first_name' => 'Billy',
491 'billing_middle_name' => 'Goat',
492 'billing_last_name' => 'Gruff',
493 'selectMembership' => $this->_ids['membership_type'],
494 );
495
496 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
497 $contributions = $this->callAPISuccess('contribution', 'get', array('contribution_page_id' => $this->_ids['contribution_page']));
498 $this->assertCount(2, $contributions['values']);
499 $lines = $this->callAPISuccess('LineItem', 'get', array('sequential' => 1));
500 $this->assertEquals(10, $lines['values'][0]['line_total']);
501 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', array());
502 $this->assertTrue(in_array($membershipPayment['contribution_id'], array_keys($contributions['values'])));
503 $membership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
504 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
505 }
506
507
508 /**
509 * Test submit with a membership block in place.
510 */
511 public function testSubmitMembershipBlockIsSeparatePaymentWithPayLater() {
512 $this->setUpMembershipContributionPage(TRUE);
513 $this->_ids['membership_type'] = array($this->membershipTypeCreate(array('minimum_fee' => 2)));
514 //Pay later
515 $submitParams = array(
516 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
517 'id' => (int) $this->_ids['contribution_page'],
518 'amount' => 0,
519 'billing_first_name' => 'Billy',
520 'billing_middle_name' => 'Goat',
521 'billing_last_name' => 'Gruff',
522 'is_pay_later' => 1,
523 'selectMembership' => $this->_ids['membership_type'],
524 );
525
526 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
527 $contributions = $this->callAPISuccess('contribution', 'get', array('contribution_page_id' => $this->_ids['contribution_page']));
528 $this->assertCount(2, $contributions['values']);
529 foreach ($contributions['values'] as $val) {
530 $this->assertEquals('Pending', $val['contribution_status']);
531 }
532
533 //Membership should be in Pending state.
534 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', array());
535 $this->assertTrue(in_array($membershipPayment['contribution_id'], array_keys($contributions['values'])));
536 $membership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
537 $pendingStatus = $this->callAPISuccessGetSingle('MembershipStatus', array(
538 'return' => array("id"),
539 'name' => "Pending",
540 ));
541 $this->assertEquals($membership['status_id'], $pendingStatus['id']);
542 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
543 }
544
545 /**
546 * Test submit with a membership block in place.
547 */
548 public function testSubmitMembershipBlockIsSeparatePaymentWithEmail() {
549 $mut = new CiviMailUtils($this, TRUE);
550 $this->setUpMembershipContributionPage(TRUE);
551 $this->addProfile('supporter_profile', $this->_ids['contribution_page']);
552
553 $submitParams = array(
554 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
555 'id' => (int) $this->_ids['contribution_page'],
556 'amount' => 10,
557 'billing_first_name' => 'Billy',
558 'billing_middle_name' => 'Goat',
559 'billing_last_name' => 'Gruff',
560 'selectMembership' => $this->_ids['membership_type'],
561 'email-Primary' => 'billy-goat@the-bridge.net',
562 'payment_processor_id' => $this->_paymentProcessor['id'],
563 'credit_card_number' => '4111111111111111',
564 'credit_card_type' => 'Visa',
565 'credit_card_exp_date' => array('M' => 9, 'Y' => 2040),
566 'cvv2' => 123,
567 );
568
569 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
570 $contributions = $this->callAPISuccess('contribution', 'get', array('contribution_page_id' => $this->_ids['contribution_page']));
571 $this->assertCount(2, $contributions['values']);
572 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', array());
573 $this->assertTrue(in_array($membershipPayment['contribution_id'], array_keys($contributions['values'])));
574 $membership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
575 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
576 // We should have two separate email messages, each with their own amount
577 // line and no total line.
578 $mut->checkAllMailLog(
579 array(
580 'Amount: $ 2.00',
581 'Amount: $ 10.00',
582 'Membership Fee',
583 ),
584 array(
585 'Total: $',
586 )
587 );
588 $mut->stop();
589 $mut->clearMessages();
590 }
591
592 /**
593 * Test submit with a membership block in place.
594 */
595 public function testSubmitMembershipBlockIsSeparatePaymentZeroDollarsPayLaterWithEmail() {
596 $mut = new CiviMailUtils($this, TRUE);
597 $this->_ids['membership_type'] = array($this->membershipTypeCreate(array('minimum_fee' => 0)));
598 $this->setUpMembershipContributionPage(TRUE);
599 $this->addProfile('supporter_profile', $this->_ids['contribution_page']);
600
601 $submitParams = array(
602 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
603 'id' => (int) $this->_ids['contribution_page'],
604 'amount' => 0,
605 'billing_first_name' => 'Billy',
606 'billing_middle_name' => 'Goat',
607 'billing_last_name' => 'Gruffalo',
608 'selectMembership' => $this->_ids['membership_type'],
609 'payment_processor_id' => 0,
610 'email-Primary' => 'gruffalo@the-bridge.net',
611 );
612
613 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
614 $contributions = $this->callAPISuccess('contribution', 'get', array('contribution_page_id' => $this->_ids['contribution_page']));
615 $this->assertCount(2, $contributions['values']);
616 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', array());
617 $this->assertTrue(in_array($membershipPayment['contribution_id'], array_keys($contributions['values'])));
618 $membership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
619 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
620 $mut->checkMailLog(array(
621 'Gruffalo',
622 'General Membership: $ 0.00',
623 'Membership Fee',
624 ));
625 $mut->stop();
626 $mut->clearMessages();
627 }
628
629 /**
630 * Test submit with a membership block in place.
631 */
632 public function testSubmitMembershipBlockTwoTypesIsSeparatePayment() {
633 $this->_ids['membership_type'] = array($this->membershipTypeCreate(array('minimum_fee' => 6)));
634 $this->_ids['membership_type'][] = $this->membershipTypeCreate(array('name' => 'Student', 'minimum_fee' => 50));
635 $this->setUpMembershipContributionPage(TRUE);
636 $submitParams = array(
637 'price_' . $this->_ids['price_field'][0] => $this->_ids['price_field_value'][1],
638 'id' => (int) $this->_ids['contribution_page'],
639 'amount' => 10,
640 'billing_first_name' => 'Billy',
641 'billing_middle_name' => 'Goat',
642 'billing_last_name' => 'Gruff',
643 'selectMembership' => $this->_ids['membership_type'][1],
644 );
645
646 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
647 $contributions = $this->callAPISuccess('contribution', 'get', array('contribution_page_id' => $this->_ids['contribution_page']));
648 $this->assertCount(2, $contributions['values']);
649 $ids = array_keys($contributions['values']);
650 $this->assertEquals('10.00', $contributions['values'][$ids[0]]['total_amount']);
651 $this->assertEquals('50.00', $contributions['values'][$ids[1]]['total_amount']);
652 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', array());
653 $this->assertArrayHasKey($membershipPayment['contribution_id'], $contributions['values']);
654 $membership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
655 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
656 }
657
658 /**
659 * Test submit with a membership block in place.
660 *
661 * We are expecting a separate payment for the membership vs the contribution.
662 */
663 public function testSubmitMembershipBlockIsSeparatePaymentPaymentProcessorNow() {
664 $mut = new CiviMailUtils($this, TRUE);
665 $this->setUpMembershipContributionPage(TRUE);
666 $processor = Civi\Payment\System::singleton()->getById($this->_paymentProcessor['id']);
667 $processor->setDoDirectPaymentResult(array('fee_amount' => .72));
668 $submitParams = array(
669 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
670 'id' => (int) $this->_ids['contribution_page'],
671 'amount' => 10,
672 'billing_first_name' => 'Billy',
673 'billing_middle_name' => 'Goat',
674 'billing_last_name' => 'Gruff',
675 'email-Primary' => 'henry@8th.king',
676 'selectMembership' => $this->_ids['membership_type'],
677 'payment_processor_id' => $this->_paymentProcessor['id'],
678 'credit_card_number' => '4111111111111111',
679 'credit_card_type' => 'Visa',
680 'credit_card_exp_date' => array('M' => 9, 'Y' => 2040),
681 'cvv2' => 123,
682 );
683
684 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
685 $contributions = $this->callAPISuccess('contribution', 'get', array(
686 'contribution_page_id' => $this->_ids['contribution_page'],
687 'contribution_status_id' => 1,
688 ));
689 $this->assertCount(2, $contributions['values']);
690 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', array());
691 $this->assertTrue(in_array($membershipPayment['contribution_id'], array_keys($contributions['values'])));
692 $membership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
693 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
694 $lineItem = $this->callAPISuccessGetSingle('LineItem', array('entity_table' => 'civicrm_membership'));
695 $this->assertEquals($lineItem['entity_id'], $membership['id']);
696 $this->assertEquals($lineItem['contribution_id'], $membershipPayment['contribution_id']);
697 $this->assertEquals($lineItem['qty'], 1);
698 $this->assertEquals($lineItem['unit_price'], 2);
699 $this->assertEquals($lineItem['line_total'], 2);
700 foreach ($contributions['values'] as $contribution) {
701 $this->assertEquals(.72, $contribution['fee_amount']);
702 $this->assertEquals($contribution['total_amount'] - .72, $contribution['net_amount']);
703 }
704 // The total string is currently absent & it seems worse with - although at some point
705 // it may have been intended
706 $mut->checkAllMailLog(array('$ 2.00', 'Contribution Amount', '$ 10.00'), array('Total:'));
707 $mut->stop();
708 $mut->clearMessages();
709 }
710
711 /**
712 * Test submit with a membership block in place.
713 *
714 * Ensure a separate payment for the membership vs the contribution, with
715 * correct amounts.
716 *
717 * @param string $thousandSeparator
718 * punctuation used to refer to thousands.
719 *
720 * @dataProvider getThousandSeparators
721 */
722 public function testSubmitMembershipBlockIsSeparatePaymentPaymentProcessorNowChargesCorrectAmounts($thousandSeparator) {
723 $this->setCurrencySeparators($thousandSeparator);
724 $this->setUpMembershipContributionPage(TRUE);
725 $processor = Civi\Payment\System::singleton()->getById($this->_paymentProcessor['id']);
726 $processor->setDoDirectPaymentResult(array('fee_amount' => .72));
727 $test_uniq = uniqid();
728 $contributionPageAmount = 10;
729 $submitParams = array(
730 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
731 'id' => (int) $this->_ids['contribution_page'],
732 'amount' => $contributionPageAmount,
733 'billing_first_name' => 'Billy',
734 'billing_middle_name' => 'Goat',
735 'billing_last_name' => 'Gruff',
736 'email-Primary' => 'henry@8th.king',
737 'selectMembership' => $this->_ids['membership_type'],
738 'payment_processor_id' => $this->_paymentProcessor['id'],
739 'credit_card_number' => '4111111111111111',
740 'credit_card_type' => 'Visa',
741 'credit_card_exp_date' => array('M' => 9, 'Y' => 2040),
742 'cvv2' => 123,
743 'TEST_UNIQ' => $test_uniq,
744 );
745
746 // set custom hook
747 $this->hookClass->setHook('civicrm_alterPaymentProcessorParams', array($this, 'hook_civicrm_alterPaymentProcessorParams'));
748
749 $this->callAPISuccess('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
750 $contributions = $this->callAPISuccess('contribution', 'get', array(
751 'contribution_page_id' => $this->_ids['contribution_page'],
752 'contribution_status_id' => 1,
753 ));
754
755 $result = civicrm_api3('SystemLog', 'get', array(
756 'sequential' => 1,
757 'message' => array('LIKE' => "%{$test_uniq}%"),
758 ));
759 $this->assertCount(2, $result['values'], "Expected exactly 2 log entries matching {$test_uniq}.");
760
761 // Examine logged entries to ensure correct values.
762 $contribution_ids = array();
763 $found_membership_amount = $found_contribution_amount = FALSE;
764 foreach ($result['values'] as $value) {
765 list($junk, $json) = explode("$test_uniq:", $value['message']);
766 $logged_contribution = json_decode($json, TRUE);
767 $contribution_ids[] = $logged_contribution['contributionID'];
768 if (!empty($logged_contribution['total_amount'])) {
769 $amount = $logged_contribution['total_amount'];
770 }
771 else {
772 $amount = $logged_contribution['amount'];
773 }
774
775 if ($amount == $this->_membershipBlockAmount) {
776 $found_membership_amount = TRUE;
777 }
778 if ($amount == $contributionPageAmount) {
779 $found_contribution_amount = TRUE;
780 }
781 }
782
783 $distinct_contribution_ids = array_unique($contribution_ids);
784 $this->assertCount(2, $distinct_contribution_ids, "Expected exactly 2 log contributions with distinct contributionIDs.");
785 $this->assertTrue($found_contribution_amount, "Expected one log contribution with amount '$contributionPageAmount' (the contribution page amount)");
786 $this->assertTrue($found_membership_amount, "Expected one log contribution with amount '$this->_membershipBlockAmount' (the membership amount)");
787 }
788
789 /**
790 * Test that when a transaction fails the pending contribution remains.
791 *
792 * An activity should also be created. CRM-16417.
793 */
794 public function testSubmitPaymentProcessorFailure() {
795 $this->setUpContributionPage();
796 $this->setupPaymentProcessor();
797 $this->createLoggedInUser();
798 $priceFieldID = reset($this->_ids['price_field']);
799 $priceFieldValueID = reset($this->_ids['price_field_value']);
800 $submitParams = array(
801 'price_' . $priceFieldID => $priceFieldValueID,
802 'id' => (int) $this->_ids['contribution_page'],
803 'amount' => 10,
804 'payment_processor_id' => 1,
805 'credit_card_number' => '4111111111111111',
806 'credit_card_type' => 'Visa',
807 'credit_card_exp_date' => array('M' => 9, 'Y' => 2008),
808 'cvv2' => 123,
809 );
810
811 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
812 $contribution = $this->callAPISuccessGetSingle('contribution', array(
813 'contribution_page_id' => $this->_ids['contribution_page'],
814 'contribution_status_id' => 'Failed',
815 ));
816
817 $this->callAPISuccessGetSingle('activity', array(
818 'source_record_id' => $contribution['id'],
819 'activity_type_id' => 'Failed Payment',
820 ));
821
822 }
823
824 /**
825 * Test submit recurring (yearly) membership with immediate confirmation (IATS style).
826 *
827 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
828 * processor (IATS style - denoted by returning trxn_id)
829 * - the first creates a new membership, completed contribution, in progress recurring. Check these
830 * - create another - end date should be extended
831 */
832 public function testSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPaymentYear() {
833 $this->doSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPayment(array('duration_unit' => 'year', 'recur_frequency_unit' => 'year'));
834 }
835
836 /**
837 * Test submit recurring (monthly) membership with immediate confirmation (IATS style).
838 *
839 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
840 * processor (IATS style - denoted by returning trxn_id)
841 * - the first creates a new membership, completed contribution, in progress recurring. Check these
842 * - create another - end date should be extended
843 */
844 public function testSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPaymentMonth() {
845 $this->doSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPayment(array('duration_unit' => 'month', 'recur_frequency_unit' => 'month'));
846 }
847
848 /**
849 * Test submit recurring (mismatched frequency unit) membership with immediate confirmation (IATS style).
850 *
851 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
852 * processor (IATS style - denoted by returning trxn_id)
853 * - the first creates a new membership, completed contribution, in progress recurring. Check these
854 * - create another - end date should be extended
855 */
856 //public function testSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPaymentDifferentFrequency() {
857 // $this->doSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPayment(array('duration_unit' => 'year', 'recur_frequency_unit' => 'month'));
858 //}
859
860 /**
861 * Helper function for testSubmitMembershipPriceSetPaymentProcessorRecurInstantPayment*
862 * @param array $params
863 *
864 * @throws \CRM_Core_Exception
865 * @throws \Exception
866 */
867 public function doSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPayment($params = array()) {
868 $this->params['is_recur'] = 1;
869 $this->params['recur_frequency_unit'] = $params['recur_frequency_unit'];
870 $membershipTypeParams['duration_unit'] = $params['duration_unit'];
871 if ($params['recur_frequency_unit'] === $params['duration_unit']) {
872 $durationUnit = $params['duration_unit'];
873 }
874 else {
875 $durationUnit = NULL;
876 }
877 $this->setUpMembershipContributionPage(FALSE, FALSE, $membershipTypeParams);
878 $dummyPP = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor);
879 $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 1, 'trxn_id' => 'create_first_success'));
880 $processor = $dummyPP->getPaymentProcessor();
881
882 if ($params['recur_frequency_unit'] === $params['duration_unit']) {
883 // Membership will be in "New" state because it will get confirmed as payment matches
884 $expectedMembershipStatus = 1;
885 }
886 else {
887 // Membership will still be in "Pending" state as it won't get confirmed as payment doesn't match
888 $expectedMembershipStatus = 5;
889 }
890
891 $submitParams = array(
892 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
893 'id' => (int) $this->_ids['contribution_page'],
894 'amount' => 10,
895 'billing_first_name' => 'Billy',
896 'billing_middle_name' => 'Goat',
897 'billing_last_name' => 'Gruff',
898 'email' => 'billy@goat.gruff',
899 'selectMembership' => $this->_ids['membership_type'],
900 'payment_processor_id' => 1,
901 'credit_card_number' => '4111111111111111',
902 'credit_card_type' => 'Visa',
903 'credit_card_exp_date' => array('M' => 9, 'Y' => 2040),
904 'cvv2' => 123,
905 'is_recur' => 1,
906 'frequency_interval' => 1,
907 'frequency_unit' => $this->params['recur_frequency_unit'],
908 );
909
910 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
911 $contribution = $this->callAPISuccess('contribution', 'getsingle', array(
912 'contribution_page_id' => $this->_ids['contribution_page'],
913 'contribution_status_id' => 1,
914 ));
915 $this->assertEquals($processor['payment_instrument_id'], $contribution['payment_instrument_id']);
916
917 $this->assertEquals('create_first_success', $contribution['trxn_id']);
918 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', array());
919 $this->assertEquals($membershipPayment['contribution_id'], $contribution['id']);
920 $membership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
921 $this->assertEquals($membership['contact_id'], $contribution['contact_id']);
922 $this->assertEquals($expectedMembershipStatus, $membership['status_id']);
923 $this->callAPISuccess('contribution_recur', 'getsingle', array('id' => $contribution['contribution_recur_id']));
924
925 $this->callAPISuccess('line_item', 'getsingle', array('contribution_id' => $contribution['id'], 'entity_id' => $membership['id']));
926 //renew it with processor setting completed - should extend membership
927 $submitParams['contact_id'] = $contribution['contact_id'];
928 $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 1, 'trxn_id' => 'create_second_success'));
929 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
930 $this->callAPISuccess('contribution', 'getsingle', array(
931 'id' => array('NOT IN' => array($contribution['id'])),
932 'contribution_page_id' => $this->_ids['contribution_page'],
933 'contribution_status_id' => 1,
934 ));
935 $renewedMembership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
936 if ($durationUnit) {
937 // We only have an end_date if frequency units match, otherwise membership won't be autorenewed and dates won't be calculated.
938 $renewedMembershipEndDate = $this->membershipRenewalDate($durationUnit, $membership['end_date']);
939 $this->assertEquals($renewedMembershipEndDate, $renewedMembership['end_date']);
940 }
941 $recurringContribution = $this->callAPISuccess('contribution_recur', 'getsingle', array('id' => $contribution['contribution_recur_id']));
942 $this->assertEquals($processor['payment_instrument_id'], $recurringContribution['payment_instrument_id']);
943 $this->assertEquals(5, $recurringContribution['contribution_status_id']);
944 }
945
946 /**
947 * Test submit recurring membership with immediate confirmation (IATS style).
948 *
949 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
950 * processor (IATS style - denoted by returning trxn_id)
951 * - the first creates a new membership, completed contribution, in progress recurring. Check these
952 * - create another - end date should be extended
953 */
954 public function testSubmitMembershipComplexNonPriceSetPaymentPaymentProcessorRecurInstantPayment() {
955 $this->params['is_recur'] = 1;
956 $this->params['recur_frequency_unit'] = $membershipTypeParams['duration_unit'] = 'year';
957 // Add a membership so membership & contribution are not both 1.
958 $preExistingMembershipID = $this->contactMembershipCreate(array('contact_id' => $this->contactIds[0]));
959 $this->setUpMembershipContributionPage(FALSE, FALSE, $membershipTypeParams);
960 $dummyPP = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor);
961 $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 1, 'trxn_id' => 'create_first_success'));
962 $processor = $dummyPP->getPaymentProcessor();
963
964 $submitParams = array(
965 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
966 'price_' . $this->_ids['price_field']['cont'] => 88,
967 'id' => (int) $this->_ids['contribution_page'],
968 'amount' => 10,
969 'billing_first_name' => 'Billy',
970 'billing_middle_name' => 'Goat',
971 'billing_last_name' => 'Gruff',
972 'email' => 'billy@goat.gruff',
973 'selectMembership' => $this->_ids['membership_type'],
974 'payment_processor_id' => 1,
975 'credit_card_number' => '4111111111111111',
976 'credit_card_type' => 'Visa',
977 'credit_card_exp_date' => array('M' => 9, 'Y' => 2040),
978 'cvv2' => 123,
979 'is_recur' => 1,
980 'frequency_interval' => 1,
981 'frequency_unit' => $this->params['recur_frequency_unit'],
982 );
983
984 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
985 $contribution = $this->callAPISuccess('contribution', 'getsingle', array(
986 'contribution_page_id' => $this->_ids['contribution_page'],
987 'contribution_status_id' => 1,
988 ));
989 $this->assertEquals($processor['payment_instrument_id'], $contribution['payment_instrument_id']);
990
991 $this->assertEquals('create_first_success', $contribution['trxn_id']);
992 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', array());
993 $this->assertEquals($membershipPayment['contribution_id'], $contribution['id']);
994 $membership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
995 $this->assertEquals($membership['contact_id'], $contribution['contact_id']);
996 $this->assertEquals(1, $membership['status_id']);
997 $this->callAPISuccess('contribution_recur', 'getsingle', array('id' => $contribution['contribution_recur_id']));
998
999 $lines = $this->callAPISuccess('line_item', 'get', array('sequential' => 1, 'contribution_id' => $contribution['id']));
1000 $this->assertEquals(2, $lines['count']);
1001 $this->assertEquals('civicrm_membership', $lines['values'][0]['entity_table']);
1002 $this->assertEquals($preExistingMembershipID + 1, $lines['values'][0]['entity_id']);
1003 $this->assertEquals('civicrm_contribution', $lines['values'][1]['entity_table']);
1004 $this->assertEquals($contribution['id'], $lines['values'][1]['entity_id']);
1005 $this->callAPISuccessGetSingle('MembershipPayment', array('contribution_id' => $contribution['id'], 'membership_id' => $preExistingMembershipID + 1));
1006
1007 //renew it with processor setting completed - should extend membership
1008 $submitParams['contact_id'] = $contribution['contact_id'];
1009 $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 1, 'trxn_id' => 'create_second_success'));
1010 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1011 $renewContribution = $this->callAPISuccess('contribution', 'getsingle', array(
1012 'id' => array('NOT IN' => array($contribution['id'])),
1013 'contribution_page_id' => $this->_ids['contribution_page'],
1014 'contribution_status_id' => 1,
1015 ));
1016 $lines = $this->callAPISuccess('line_item', 'get', array('sequential' => 1, 'contribution_id' => $renewContribution['id']));
1017 $this->assertEquals(2, $lines['count']);
1018 $this->assertEquals('civicrm_membership', $lines['values'][0]['entity_table']);
1019 $this->assertEquals($preExistingMembershipID + 1, $lines['values'][0]['entity_id']);
1020 $this->assertEquals('civicrm_contribution', $lines['values'][1]['entity_table']);
1021 $this->assertEquals($renewContribution['id'], $lines['values'][1]['entity_id']);
1022
1023 $renewedMembership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
1024 $this->assertEquals(date('Y-m-d', strtotime('+ 1 ' . $this->params['recur_frequency_unit'], strtotime($membership['end_date']))), $renewedMembership['end_date']);
1025 $recurringContribution = $this->callAPISuccess('contribution_recur', 'getsingle', array('id' => $contribution['contribution_recur_id']));
1026 $this->assertEquals($processor['payment_instrument_id'], $recurringContribution['payment_instrument_id']);
1027 $this->assertEquals(5, $recurringContribution['contribution_status_id']);
1028 }
1029
1030 /**
1031 * Test submit recurring membership with immediate confirmation (IATS style).
1032 *
1033 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
1034 * processor (IATS style - denoted by returning trxn_id)
1035 * - the first creates a new membership, completed contribution, in progress recurring. Check these
1036 * - create another - end date should be extended
1037 */
1038 public function testSubmitMembershipComplexPriceSetPaymentPaymentProcessorRecurInstantPayment() {
1039 $this->params['is_recur'] = 1;
1040 $this->params['recur_frequency_unit'] = $membershipTypeParams['duration_unit'] = 'year';
1041 // Add a membership so membership & contribution are not both 1.
1042 $preExistingMembershipID = $this->contactMembershipCreate(array('contact_id' => $this->contactIds[0]));
1043 $this->createPriceSetWithPage();
1044 $this->addSecondOrganizationMembershipToPriceSet();
1045 $this->setupPaymentProcessor();
1046
1047 $dummyPP = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor);
1048 $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 1, 'trxn_id' => 'create_first_success'));
1049 $processor = $dummyPP->getPaymentProcessor();
1050
1051 $submitParams = array(
1052 'price_' . $this->_ids['price_field'][0] => $this->_ids['price_field_value']['cont'],
1053 'price_' . $this->_ids['price_field']['org1'] => $this->_ids['price_field_value']['org1'],
1054 'price_' . $this->_ids['price_field']['org2'] => $this->_ids['price_field_value']['org2'],
1055 'id' => (int) $this->_ids['contribution_page'],
1056 'amount' => 10,
1057 'billing_first_name' => 'Billy',
1058 'billing_middle_name' => 'Goat',
1059 'billing_last_name' => 'Gruff',
1060 'email' => 'billy@goat.gruff',
1061 'selectMembership' => NULL,
1062 'payment_processor_id' => 1,
1063 'credit_card_number' => '4111111111111111',
1064 'credit_card_type' => 'Visa',
1065 'credit_card_exp_date' => array('M' => 9, 'Y' => 2040),
1066 'cvv2' => 123,
1067 'frequency_interval' => 1,
1068 'frequency_unit' => $this->params['recur_frequency_unit'],
1069 );
1070
1071 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
1072 $contribution = $this->callAPISuccess('contribution', 'getsingle', array(
1073 'contribution_page_id' => $this->_ids['contribution_page'],
1074 'contribution_status_id' => 1,
1075 ));
1076 $this->assertEquals($processor['payment_instrument_id'], $contribution['payment_instrument_id']);
1077
1078 $this->assertEquals('create_first_success', $contribution['trxn_id']);
1079 $membershipPayments = $this->callAPISuccess('membership_payment', 'get', array(
1080 'sequential' => 1,
1081 'contribution_id' => $contribution['id'],
1082 ));
1083 $this->assertEquals(2, $membershipPayments['count']);
1084 $lines = $this->callAPISuccess('line_item', 'get', array('sequential' => 1, 'contribution_id' => $contribution['id']));
1085 $this->assertEquals(3, $lines['count']);
1086 $this->assertEquals('civicrm_membership', $lines['values'][0]['entity_table']);
1087 $this->assertEquals($preExistingMembershipID + 1, $lines['values'][0]['entity_id']);
1088 $this->assertEquals('civicrm_contribution', $lines['values'][1]['entity_table']);
1089 $this->assertEquals($contribution['id'], $lines['values'][1]['entity_id']);
1090 $this->assertEquals('civicrm_membership', $lines['values'][2]['entity_table']);
1091 $this->assertEquals($preExistingMembershipID + 2, $lines['values'][2]['entity_id']);
1092
1093 $this->callAPISuccessGetSingle('MembershipPayment', array('contribution_id' => $contribution['id'], 'membership_id' => $preExistingMembershipID + 1));
1094 $membership = $this->callAPISuccessGetSingle('membership', array('id' => $preExistingMembershipID + 1));
1095
1096 //renew it with processor setting completed - should extend membership
1097 $submitParams['contact_id'] = $contribution['contact_id'];
1098 $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 1, 'trxn_id' => 'create_second_success'));
1099 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1100 $renewContribution = $this->callAPISuccess('contribution', 'getsingle', array(
1101 'id' => array('NOT IN' => array($contribution['id'])),
1102 'contribution_page_id' => $this->_ids['contribution_page'],
1103 'contribution_status_id' => 1,
1104 ));
1105 $lines = $this->callAPISuccess('line_item', 'get', array('sequential' => 1, 'contribution_id' => $renewContribution['id']));
1106 $this->assertEquals(3, $lines['count']);
1107 $this->assertEquals('civicrm_membership', $lines['values'][0]['entity_table']);
1108 $this->assertEquals($preExistingMembershipID + 1, $lines['values'][0]['entity_id']);
1109 $this->assertEquals('civicrm_contribution', $lines['values'][1]['entity_table']);
1110 $this->assertEquals($renewContribution['id'], $lines['values'][1]['entity_id']);
1111
1112 $renewedMembership = $this->callAPISuccessGetSingle('membership', array('id' => $preExistingMembershipID + 1));
1113 $this->assertEquals(date('Y-m-d', strtotime('+ 1 ' . $this->params['recur_frequency_unit'], strtotime($membership['end_date']))), $renewedMembership['end_date']);
1114 }
1115
1116 /**
1117 * Extend the price set with a second organisation's membership.
1118 */
1119 public function addSecondOrganizationMembershipToPriceSet() {
1120 $organization2ID = $this->organizationCreate();
1121 $membershipTypes = $this->callAPISuccess('MembershipType', 'get', array());
1122 $this->_ids['membership_type'] = array_keys($membershipTypes['values']);
1123 $this->_ids['membership_type']['org2'] = $this->membershipTypeCreate(array('contact_id' => $organization2ID, 'name' => 'Org 2'));
1124 $priceField = $this->callAPISuccess('PriceField', 'create', array(
1125 'price_set_id' => $this->_ids['price_set'],
1126 'html_type' => 'Radio',
1127 'name' => 'Org1 Price',
1128 'label' => 'Org1Price',
1129 ));
1130 $this->_ids['price_field']['org1'] = $priceField['id'];
1131
1132 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
1133 'name' => 'org1 amount',
1134 'label' => 'org 1 Amount',
1135 'amount' => 2,
1136 'financial_type_id' => 'Member Dues',
1137 'format.only_id' => TRUE,
1138 'membership_type_id' => reset($this->_ids['membership_type']),
1139 'price_field_id' => $priceField['id'],
1140 ));
1141 $this->_ids['price_field_value']['org1'] = $priceFieldValue;
1142
1143 $priceField = $this->callAPISuccess('PriceField', 'create', array(
1144 'price_set_id' => $this->_ids['price_set'],
1145 'html_type' => 'Radio',
1146 'name' => 'Org2 Price',
1147 'label' => 'Org2Price',
1148 ));
1149 $this->_ids['price_field']['org2'] = $priceField['id'];
1150
1151 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
1152 'name' => 'org2 amount',
1153 'label' => 'org 2 Amount',
1154 'amount' => 200,
1155 'financial_type_id' => 'Member Dues',
1156 'format.only_id' => TRUE,
1157 'membership_type_id' => $this->_ids['membership_type']['org2'],
1158 'price_field_id' => $priceField['id'],
1159 ));
1160 $this->_ids['price_field_value']['org2'] = $priceFieldValue;
1161
1162 }
1163
1164 /**
1165 * Test submit recurring membership with immediate confirmation (IATS style).
1166 *
1167 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
1168 * processor (IATS style - denoted by returning trxn_id)
1169 * - the first creates a new membership, completed contribution, in progress recurring. Check these
1170 * - create another - end date should be extended
1171 */
1172 public function testSubmitMembershipPriceSetPaymentPaymentProcessorSeparatePaymentRecurInstantPayment() {
1173
1174 $this->setUpMembershipContributionPage(TRUE);
1175 $dummyPP = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor);
1176 $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 1, 'trxn_id' => 'create_first_success'));
1177
1178 $submitParams = array(
1179 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
1180 'id' => (int) $this->_ids['contribution_page'],
1181 'amount' => 10,
1182 'billing_first_name' => 'Billy',
1183 'billing_middle_name' => 'Goat',
1184 'billing_last_name' => 'Gruff',
1185 'email' => 'billy@goat.gruff',
1186 'selectMembership' => $this->_ids['membership_type'],
1187 'payment_processor_id' => 1,
1188 'credit_card_number' => '4111111111111111',
1189 'credit_card_type' => 'Visa',
1190 'credit_card_exp_date' => array('M' => 9, 'Y' => 2040),
1191 'cvv2' => 123,
1192 'is_recur' => 1,
1193 'auto_renew' => TRUE,
1194 'frequency_interval' => 1,
1195 'frequency_unit' => 'month',
1196 );
1197
1198 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
1199 $contribution = $this->callAPISuccess('contribution', 'get', array(
1200 'contribution_page_id' => $this->_ids['contribution_page'],
1201 'contribution_status_id' => 1,
1202 ));
1203
1204 $this->assertEquals(2, $contribution['count']);
1205 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', array());
1206 $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
1207 $this->assertNotEmpty($contribution['values'][$membershipPayment['contribution_id']]['contribution_recur_id']);
1208 $this->callAPISuccess('contribution_recur', 'getsingle', array());
1209 }
1210
1211 /**
1212 * Test submit recurring membership with delayed confirmation (Authorize.net style)
1213 * - we process 2 membership transactions against with a recurring contribution against a contribution page with a delayed
1214 * processor (Authorize.net style - denoted by NOT returning trxn_id)
1215 * - the first creates a pending membership, pending contribution, penging recurring. Check these
1216 * - complete the transaction
1217 * - create another - end date should NOT be extended
1218 */
1219 public function testSubmitMembershipPriceSetPaymentPaymentProcessorRecurDelayed() {
1220 $this->params['is_recur'] = 1;
1221 $this->params['recur_frequency_unit'] = $membershipTypeParams['duration_unit'] = 'year';
1222 $this->setUpMembershipContributionPage();
1223 $dummyPP = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor);
1224 $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 2));
1225 $this->membershipTypeCreate(array('name' => 'Student'));
1226
1227 // Add a contribution & a couple of memberships so the id will not be 1 & will differ from membership id.
1228 // This saves us from 'accidental success'.
1229 $this->contributionCreate(array('contact_id' => $this->contactIds[0]));
1230 $this->contactMembershipCreate(array('contact_id' => $this->contactIds[0]));
1231 $this->contactMembershipCreate(array('contact_id' => $this->contactIds[0], 'membership_type_id' => 'Student'));
1232
1233 $submitParams = array(
1234 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
1235 'id' => (int) $this->_ids['contribution_page'],
1236 'amount' => 10,
1237 'billing_first_name' => 'Billy',
1238 'billing_middle_name' => 'Goat',
1239 'billing_last_name' => 'Gruff',
1240 'email' => 'billy@goat.gruff',
1241 'selectMembership' => $this->_ids['membership_type'],
1242 'payment_processor_id' => 1,
1243 'credit_card_number' => '4111111111111111',
1244 'credit_card_type' => 'Visa',
1245 'credit_card_exp_date' => array('M' => 9, 'Y' => 2040),
1246 'cvv2' => 123,
1247 'is_recur' => 1,
1248 'frequency_interval' => 1,
1249 'frequency_unit' => $this->params['recur_frequency_unit'],
1250 );
1251
1252 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
1253 $contribution = $this->callAPISuccess('contribution', 'getsingle', array(
1254 'contribution_page_id' => $this->_ids['contribution_page'],
1255 'contribution_status_id' => 2,
1256 ));
1257
1258 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', array());
1259 $this->assertEquals($membershipPayment['contribution_id'], $contribution['id']);
1260 $membership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
1261 $this->assertEquals($membership['contact_id'], $contribution['contact_id']);
1262 $this->assertEquals(5, $membership['status_id']);
1263
1264 $line = $this->callAPISuccess('line_item', 'getsingle', array('contribution_id' => $contribution['id']));
1265 $this->assertEquals('civicrm_membership', $line['entity_table']);
1266 $this->assertEquals($membership['id'], $line['entity_id']);
1267
1268 $this->callAPISuccess('contribution', 'completetransaction', array(
1269 'id' => $contribution['id'],
1270 'trxn_id' => 'ipn_called',
1271 'payment_processor_id' => $this->_paymentProcessor['id'],
1272 ));
1273 $line = $this->callAPISuccess('line_item', 'getsingle', array('contribution_id' => $contribution['id']));
1274 $this->assertEquals('civicrm_membership', $line['entity_table']);
1275 $this->assertEquals($membership['id'], $line['entity_id']);
1276
1277 $membership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
1278 //renew it with processor setting completed - should extend membership
1279 $submitParams = array_merge($submitParams, array(
1280 'contact_id' => $contribution['contact_id'],
1281 'is_recur' => 1,
1282 'frequency_interval' => 1,
1283 'frequency_unit' => $this->params['recur_frequency_unit'],
1284 )
1285 );
1286
1287 $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 2));
1288 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1289 $newContribution = $this->callAPISuccess('contribution', 'getsingle', array(
1290 'id' => array(
1291 'NOT IN' => array($contribution['id']),
1292 ),
1293 'contribution_page_id' => $this->_ids['contribution_page'],
1294 'contribution_status_id' => 2,
1295 )
1296 );
1297 $line = $this->callAPISuccess('line_item', 'getsingle', array('contribution_id' => $newContribution['id']));
1298 $this->assertEquals('civicrm_membership', $line['entity_table']);
1299 $this->assertEquals($membership['id'], $line['entity_id']);
1300
1301 $renewedMembership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
1302 //no renewal as the date hasn't changed
1303 $this->assertEquals($membership['end_date'], $renewedMembership['end_date']);
1304 $recurringContribution = $this->callAPISuccess('contribution_recur', 'getsingle', array('id' => $newContribution['contribution_recur_id']));
1305 $this->assertEquals(2, $recurringContribution['contribution_status_id']);
1306 }
1307
1308 /**
1309 * Test non-recur contribution with membership payment
1310 */
1311 public function testSubmitMembershipIsSeparatePaymentNotRecur() {
1312 //Create recur contribution page.
1313 $this->setUpMembershipContributionPage(TRUE, TRUE);
1314 $dummyPP = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor);
1315 $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 1, 'trxn_id' => 'create_first_success'));
1316
1317 //Sumbit payment with recur disabled.
1318 $submitParams = array(
1319 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
1320 'id' => (int) $this->_ids['contribution_page'],
1321 'amount' => 10,
1322 'frequency_interval' => 1,
1323 'frequency_unit' => 'month',
1324 'billing_first_name' => 'Billy',
1325 'billing_middle_name' => 'Goat',
1326 'billing_last_name' => 'Gruff',
1327 'email' => 'billy@goat.gruff',
1328 'selectMembership' => $this->_ids['membership_type'],
1329 'payment_processor_id' => 1,
1330 'credit_card_number' => '4111111111111111',
1331 'credit_card_type' => 'Visa',
1332 'credit_card_exp_date' => array('M' => 9, 'Y' => 2040),
1333 'cvv2' => 123,
1334 );
1335
1336 //Assert if recur contribution is created.
1337 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1338 $recur = $this->callAPISuccess('contribution_recur', 'get', array());
1339 $this->assertEmpty($recur['count']);
1340 }
1341
1342
1343 /**
1344 * Set up membership contribution page.
1345 * @param bool $isSeparatePayment
1346 * @param bool $isRecur
1347 * @param array $membershipTypeParams Parameters to pass to membershiptype.create API
1348 */
1349 public function setUpMembershipContributionPage($isSeparatePayment = FALSE, $isRecur = FALSE, $membershipTypeParams = array()) {
1350 $this->setUpMembershipBlockPriceSet($membershipTypeParams);
1351 $this->setupPaymentProcessor();
1352 $this->setUpContributionPage($isRecur);
1353
1354 $this->callAPISuccess('membership_block', 'create', array(
1355 'entity_id' => $this->_ids['contribution_page'],
1356 'entity_table' => 'civicrm_contribution_page',
1357 'is_required' => TRUE,
1358 'is_active' => TRUE,
1359 'is_separate_payment' => $isSeparatePayment,
1360 'membership_type_default' => $this->_ids['membership_type'],
1361 ));
1362 }
1363
1364 /**
1365 * Set up pledge block.
1366 */
1367 public function setUpPledgeBlock() {
1368 $params = array(
1369 'entity_table' => 'civicrm_contribution_page',
1370 'entity_id' => $this->_ids['contribution_page'],
1371 'pledge_frequency_unit' => 'week',
1372 'is_pledge_interval' => 0,
1373 'pledge_start_date' => json_encode(array('calendar_date' => date('Ymd', strtotime("+1 month")))),
1374 );
1375 $pledgeBlock = CRM_Pledge_BAO_PledgeBlock::create($params);
1376 $this->_ids['pledge_block_id'] = $pledgeBlock->id;
1377 }
1378
1379 /**
1380 * The default data set does not include a complete default membership price set - not quite sure why.
1381 *
1382 * This function ensures it exists & populates $this->_ids with it's data
1383 */
1384 public function setUpMembershipBlockPriceSet($membershipTypeParams = array()) {
1385 $this->_ids['price_set'][] = $this->callAPISuccess('price_set', 'getvalue', array(
1386 'name' => 'default_membership_type_amount',
1387 'return' => 'id',
1388 ));
1389 if (empty($this->_ids['membership_type'])) {
1390 $membershipTypeParams = array_merge(array(
1391 'minimum_fee' => 2,
1392 ), $membershipTypeParams);
1393 $this->_ids['membership_type'] = array($this->membershipTypeCreate($membershipTypeParams));
1394 }
1395 $priceField = $this->callAPISuccess('price_field', 'create', array(
1396 'price_set_id' => reset($this->_ids['price_set']),
1397 'name' => 'membership_amount',
1398 'label' => 'Membership Amount',
1399 'html_type' => 'Radio',
1400 'sequential' => 1,
1401 ));
1402 $this->_ids['price_field'][] = $priceField['id'];
1403
1404 foreach ($this->_ids['membership_type'] as $membershipTypeID) {
1405 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
1406 'name' => 'membership_amount',
1407 'label' => 'Membership Amount',
1408 'amount' => $this->_membershipBlockAmount,
1409 'financial_type_id' => 'Donation',
1410 'format.only_id' => TRUE,
1411 'membership_type_id' => $membershipTypeID,
1412 'price_field_id' => $priceField['id'],
1413 ));
1414 $this->_ids['price_field_value'][] = $priceFieldValue;
1415 }
1416 if (!empty($this->_ids['membership_type']['org2'])) {
1417 $priceField = $this->callAPISuccess('price_field', 'create', array(
1418 'price_set_id' => reset($this->_ids['price_set']),
1419 'name' => 'membership_org2',
1420 'label' => 'Membership Org2',
1421 'html_type' => 'Checkbox',
1422 'sequential' => 1,
1423 ));
1424 $this->_ids['price_field']['org2'] = $priceField['id'];
1425
1426 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
1427 'name' => 'membership_org2',
1428 'label' => 'Membership org 2',
1429 'amount' => 55,
1430 'financial_type_id' => 'Member Dues',
1431 'format.only_id' => TRUE,
1432 'membership_type_id' => $this->_ids['membership_type']['org2'],
1433 'price_field_id' => $priceField['id'],
1434 ));
1435 $this->_ids['price_field_value']['org2'] = $priceFieldValue;
1436 }
1437 $priceField = $this->callAPISuccess('price_field', 'create', array(
1438 'price_set_id' => reset($this->_ids['price_set']),
1439 'name' => 'Contribution',
1440 'label' => 'Contribution',
1441 'html_type' => 'Text',
1442 'sequential' => 1,
1443 'is_enter_qty' => 1,
1444 ));
1445 $this->_ids['price_field']['cont'] = $priceField['id'];
1446 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
1447 'name' => 'contribution',
1448 'label' => 'Give me money',
1449 'amount' => 88,
1450 'financial_type_id' => 'Donation',
1451 'format.only_id' => TRUE,
1452 'price_field_id' => $priceField['id'],
1453 ));
1454 $this->_ids['price_field_value'][] = $priceFieldValue;
1455 }
1456
1457 /**
1458 * Add text field other amount to the price set.
1459 */
1460 public function addOtherAmountFieldToMembershipPriceSet() {
1461 $this->_ids['price_field']['other_amount'] = $this->callAPISuccess('price_field', 'create', array(
1462 'price_set_id' => reset($this->_ids['price_set']),
1463 'name' => 'other_amount',
1464 'label' => 'Other Amount',
1465 'html_type' => 'Text',
1466 'format.only_id' => TRUE,
1467 'sequential' => 1,
1468 ));
1469 $this->_ids['price_field_value']['other_amount'] = $this->callAPISuccess('price_field_value', 'create', array(
1470 'financial_type_id' => 'Donation',
1471 'format.only_id' => TRUE,
1472 'label' => 'Other Amount',
1473 'amount' => 1,
1474 'price_field_id' => $this->_ids['price_field']['other_amount'],
1475 ));
1476 }
1477
1478 /**
1479 * Help function to set up contribution page with some defaults.
1480 * @param bool $isRecur
1481 */
1482 public function setUpContributionPage($isRecur = FALSE) {
1483 if ($isRecur) {
1484 $this->params['is_recur'] = 1;
1485 $this->params['recur_frequency_unit'] = 'month';
1486 }
1487 $contributionPageResult = $this->callAPISuccess($this->_entity, 'create', $this->params);
1488 if (empty($this->_ids['price_set'])) {
1489 $priceSet = $this->callAPISuccess('price_set', 'create', $this->_priceSetParams);
1490 $this->_ids['price_set'][] = $priceSet['id'];
1491 }
1492 $priceSetID = reset($this->_ids['price_set']);
1493 CRM_Price_BAO_PriceSet::addTo('civicrm_contribution_page', $contributionPageResult['id'], $priceSetID);
1494
1495 if (empty($this->_ids['price_field'])) {
1496 $priceField = $this->callAPISuccess('price_field', 'create', array(
1497 'price_set_id' => $priceSetID,
1498 'label' => 'Goat Breed',
1499 'html_type' => 'Radio',
1500 ));
1501 $this->_ids['price_field'] = array($priceField['id']);
1502 }
1503 if (empty($this->_ids['price_field_value'])) {
1504 $this->callAPISuccess('price_field_value', 'create', array(
1505 'price_set_id' => $priceSetID,
1506 'price_field_id' => $priceField['id'],
1507 'label' => 'Long Haired Goat',
1508 'financial_type_id' => 'Donation',
1509 'amount' => 20,
1510 'non_deductible_amount' => 15,
1511 )
1512 );
1513 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
1514 'price_set_id' => $priceSetID,
1515 'price_field_id' => $priceField['id'],
1516 'label' => 'Shoe-eating Goat',
1517 'financial_type_id' => 'Donation',
1518 'amount' => 10,
1519 'non_deductible_amount' => 5,
1520 )
1521 );
1522 $this->_ids['price_field_value'] = array($priceFieldValue['id']);
1523
1524 $this->_ids['price_field_value']['cheapskate'] = $this->callAPISuccess('price_field_value', 'create', array(
1525 'price_set_id' => $priceSetID,
1526 'price_field_id' => $priceField['id'],
1527 'label' => 'Stingy Goat',
1528 'financial_type_id' => 'Donation',
1529 'amount' => 0,
1530 'non_deductible_amount' => 0,
1531 )
1532 )['id'];
1533 }
1534 $this->_ids['contribution_page'] = $contributionPageResult['id'];
1535 }
1536
1537 /**
1538 * Helper function to set up contribution page which can be used to purchase a
1539 * membership type for different intervals.
1540 */
1541 public function setUpMultiIntervalMembershipContributionPage() {
1542 $this->setupPaymentProcessor();
1543 $contributionPage = $this->callAPISuccess($this->_entity, 'create', $this->params);
1544 $this->_ids['contribution_page'] = $contributionPage['id'];
1545
1546 $this->_ids['membership_type'] = $this->membershipTypeCreate(array(
1547 'auto_renew' => 2, // force auto-renew
1548 'duration_unit' => 'month',
1549 ));
1550
1551 $priceSet = civicrm_api3('PriceSet', 'create', array(
1552 'is_quick_config' => 0,
1553 'extends' => 'CiviMember',
1554 'financial_type_id' => 'Member Dues',
1555 'title' => 'CRM-21177',
1556 ));
1557 $this->_ids['price_set'] = $priceSet['id'];
1558
1559 $priceField = $this->callAPISuccess('price_field', 'create', array(
1560 'price_set_id' => $this->_ids['price_set'],
1561 'name' => 'membership_type',
1562 'label' => 'Membership Type',
1563 'html_type' => 'Radio',
1564 ));
1565 $this->_ids['price_field'] = $priceField['id'];
1566
1567 $priceFieldValueMonthly = $this->callAPISuccess('price_field_value', 'create', array(
1568 'name' => 'CRM-21177_Monthly',
1569 'label' => 'CRM-21177 - Monthly',
1570 'amount' => 20,
1571 'membership_num_terms' => 1,
1572 'membership_type_id' => $this->_ids['membership_type'],
1573 'price_field_id' => $this->_ids['price_field'],
1574 'financial_type_id' => 'Member Dues',
1575 ));
1576 $this->_ids['price_field_value_monthly'] = $priceFieldValueMonthly['id'];
1577
1578 $priceFieldValueYearly = $this->callAPISuccess('price_field_value', 'create', array(
1579 'name' => 'CRM-21177_Yearly',
1580 'label' => 'CRM-21177 - Yearly',
1581 'amount' => 200,
1582 'membership_num_terms' => 12,
1583 'membership_type_id' => $this->_ids['membership_type'],
1584 'price_field_id' => $this->_ids['price_field'],
1585 'financial_type_id' => 'Member Dues',
1586 ));
1587 $this->_ids['price_field_value_yearly'] = $priceFieldValueYearly['id'];
1588
1589 CRM_Price_BAO_PriceSet::addTo('civicrm_contribution_page', $this->_ids['contribution_page'], $this->_ids['price_set']);
1590
1591 $this->callAPISuccess('membership_block', 'create', array(
1592 'entity_id' => $this->_ids['contribution_page'],
1593 'entity_table' => 'civicrm_contribution_page',
1594 'is_required' => TRUE,
1595 'is_separate_payment' => FALSE,
1596 'is_active' => TRUE,
1597 'membership_type_default' => $this->_ids['membership_type'],
1598 ));
1599 }
1600
1601 /**
1602 * Test submit with a membership block in place.
1603 */
1604 public function testSubmitMultiIntervalMembershipContributionPage() {
1605 $this->setUpMultiIntervalMembershipContributionPage();
1606 $submitParams = array(
1607 'price_' . $this->_ids['price_field'] => $this->_ids['price_field_value_monthly'],
1608 'id' => (int) $this->_ids['contribution_page'],
1609 'amount' => 20,
1610 'first_name' => 'Billy',
1611 'last_name' => 'Gruff',
1612 'email' => 'billy@goat.gruff',
1613 'payment_processor_id' => $this->_ids['payment_processor'],
1614 'credit_card_number' => '4111111111111111',
1615 'credit_card_type' => 'Visa',
1616 'credit_card_exp_date' => array('M' => 9, 'Y' => 2040),
1617 'cvv2' => 123,
1618 'auto_renew' => 1,
1619 );
1620 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1621
1622 $submitParams['price_' . $this->_ids['price_field']] = $this->_ids['price_field_value_yearly'];
1623 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1624
1625 $contribution = $this->callAPISuccess('Contribution', 'get', array(
1626 'contribution_page_id' => $this->_ids['contribution_page'],
1627 'sequential' => 1,
1628 'api.ContributionRecur.getsingle' => array(),
1629 ));
1630 $this->assertEquals(1, $contribution['values'][0]['api.ContributionRecur.getsingle']['frequency_interval']);
1631 //$this->assertEquals(12, $contribution['values'][1]['api.ContributionRecur.getsingle']['frequency_interval']);
1632 }
1633
1634 public static function setUpBeforeClass() {
1635 // put stuff here that should happen before all tests in this unit
1636 }
1637
1638 public static function tearDownAfterClass() {
1639 $tablesToTruncate = array(
1640 'civicrm_contact',
1641 'civicrm_financial_type',
1642 'civicrm_contribution',
1643 'civicrm_contribution_page',
1644 );
1645 $unitTest = new CiviUnitTestCase();
1646 $unitTest->quickCleanup($tablesToTruncate);
1647 }
1648
1649 /**
1650 * Create a payment processor instance.
1651 */
1652 protected function setupPaymentProcessor() {
1653 $this->params['payment_processor_id'] = $this->_ids['payment_processor'] = $this->paymentProcessorCreate(array(
1654 'payment_processor_type_id' => 'Dummy',
1655 'class_name' => 'Payment_Dummy',
1656 'billing_mode' => 1,
1657 ));
1658 $this->_paymentProcessor = $this->callAPISuccess('payment_processor', 'getsingle', array('id' => $this->params['payment_processor_id']));
1659 }
1660
1661 /**
1662 * Test submit recurring pledge.
1663 *
1664 * - 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.
1665 */
1666 public function testSubmitPledgePaymentPaymentProcessorRecurFuturePayment() {
1667 $this->params['adjust_recur_start_date'] = TRUE;
1668 $this->params['is_pay_later'] = FALSE;
1669 $this->setUpContributionPage();
1670 $this->setUpPledgeBlock();
1671 $this->setupPaymentProcessor();
1672 $dummyPP = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor);
1673 $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 1, 'trxn_id' => 'create_first_success'));
1674
1675 $submitParams = array(
1676 'id' => (int) $this->_ids['contribution_page'],
1677 'amount' => 100,
1678 'billing_first_name' => 'Billy',
1679 'billing_middle_name' => 'Goat',
1680 'billing_last_name' => 'Gruff',
1681 'email' => 'billy@goat.gruff',
1682 'payment_processor_id' => 1,
1683 'credit_card_number' => '4111111111111111',
1684 'credit_card_type' => 'Visa',
1685 'credit_card_exp_date' => array('M' => 9, 'Y' => 2040),
1686 'cvv2' => 123,
1687 'pledge_frequency_interval' => 1,
1688 'pledge_frequency_unit' => 'week',
1689 'pledge_installments' => 3,
1690 'is_pledge' => TRUE,
1691 'pledge_block_id' => (int) $this->_ids['pledge_block_id'],
1692 );
1693
1694 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
1695
1696 // Check if contribution created.
1697 $contribution = $this->callAPISuccess('contribution', 'getsingle', array(
1698 'contribution_page_id' => $this->_ids['contribution_page'],
1699 'contribution_status_id' => 'Completed', // Will be pending when actual payment processor is used (dummy processor does not support future payments).
1700 ));
1701
1702 $this->assertEquals('create_first_success', $contribution['trxn_id']);
1703
1704 // Check if pledge created.
1705 $pledge = $this->callAPISuccess('pledge', 'getsingle', array());
1706 $this->assertEquals(date('Ymd', strtotime($pledge['pledge_start_date'])), date('Ymd', strtotime("+1 month")));
1707 $this->assertEquals($pledge['pledge_amount'], 300.00);
1708
1709 // Check if pledge payments created.
1710 $params = array(
1711 'pledge_id' => $pledge['id'],
1712 );
1713 $pledgePayment = $this->callAPISuccess('pledge_payment', 'get', $params);
1714 $this->assertEquals($pledgePayment['count'], 3);
1715 $this->assertEquals(date('Ymd', strtotime($pledgePayment['values'][1]['scheduled_date'])), date('Ymd', strtotime("+1 month")));
1716 $this->assertEquals($pledgePayment['values'][1]['scheduled_amount'], 100.00);
1717 $this->assertEquals($pledgePayment['values'][1]['status_id'], 1); // Will be pending when actual payment processor is used (dummy processor does not support future payments).
1718
1719 // Check contribution recur record.
1720 $recur = $this->callAPISuccess('contribution_recur', 'getsingle', array('id' => $contribution['contribution_recur_id']));
1721 $this->assertEquals(date('Ymd', strtotime($recur['start_date'])), date('Ymd', strtotime("+1 month")));
1722 $this->assertEquals($recur['amount'], 100.00);
1723 $this->assertEquals($recur['contribution_status_id'], 5); // In progress status.
1724 }
1725
1726 /**
1727 * Test submit pledge payment.
1728 *
1729 * - test submitting a pledge payment using contribution form.
1730 */
1731 public function testSubmitPledgePayment() {
1732 $this->testSubmitPledgePaymentPaymentProcessorRecurFuturePayment();
1733 $pledge = $this->callAPISuccess('pledge', 'getsingle', array());
1734 $params = array(
1735 'pledge_id' => $pledge['id'],
1736 );
1737 $submitParams = array(
1738 'id' => (int) $pledge['pledge_contribution_page_id'],
1739 'pledge_amount' => array(2 => 1),
1740 'billing_first_name' => 'Billy',
1741 'billing_middle_name' => 'Goat',
1742 'billing_last_name' => 'Gruff',
1743 'email' => 'billy@goat.gruff',
1744 'payment_processor_id' => 1,
1745 'credit_card_number' => '4111111111111111',
1746 'credit_card_type' => 'Visa',
1747 'credit_card_exp_date' => array('M' => 9, 'Y' => 2040),
1748 'cvv2' => 123,
1749 'pledge_id' => $pledge['id'],
1750 'cid' => $pledge['contact_id'],
1751 'contact_id' => $pledge['contact_id'],
1752 'amount' => 100.00,
1753 'is_pledge' => TRUE,
1754 'pledge_block_id' => $this->_ids['pledge_block_id'],
1755 );
1756 $pledgePayment = $this->callAPISuccess('pledge_payment', 'get', $params);
1757 $this->assertEquals($pledgePayment['values'][2]['status_id'], 2);
1758
1759 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
1760
1761 // Check if contribution created.
1762 $contribution = $this->callAPISuccess('contribution', 'getsingle', array(
1763 'contribution_page_id' => $pledge['pledge_contribution_page_id'],
1764 'contribution_status_id' => 'Completed',
1765 'contact_id' => $pledge['contact_id'],
1766 'contribution_recur_id' => array('IS NULL' => 1),
1767 ));
1768
1769 $this->assertEquals(100.00, $contribution['total_amount']);
1770 $pledgePayment = $this->callAPISuccess('pledge_payment', 'get', $params);
1771 $this->assertEquals($pledgePayment['values'][2]['status_id'], 1, "This pledge payment should have been completed");
1772 $this->assertEquals($pledgePayment['values'][2]['contribution_id'], $contribution['id']);
1773 }
1774
1775 /**
1776 * Test form submission with multiple option price set.
1777 *
1778 * @param string $thousandSeparator
1779 * punctuation used to refer to thousands.
1780 *
1781 * @dataProvider getThousandSeparators
1782 */
1783 public function testSubmitContributionPageWithPriceSet($thousandSeparator) {
1784 $this->setCurrencySeparators($thousandSeparator);
1785 $this->_priceSetParams['is_quick_config'] = 0;
1786 $this->setUpContributionPage();
1787 $submitParams = array(
1788 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
1789 'id' => (int) $this->_ids['contribution_page'],
1790 'amount' => 80,
1791 'first_name' => 'Billy',
1792 'last_name' => 'Gruff',
1793 'email' => 'billy@goat.gruff',
1794 'is_pay_later' => TRUE,
1795 );
1796 $this->addPriceFields($submitParams);
1797
1798 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1799 $contribution = $this->callAPISuccessGetSingle('contribution', array(
1800 'contribution_page_id' => $this->_ids['contribution_page'],
1801 'contribution_status_id' => 'Pending',
1802 ));
1803 $this->assertEquals(80, $contribution['total_amount']);
1804 $lineItems = $this->callAPISuccess('LineItem', 'get', array(
1805 'contribution_id' => $contribution['id'],
1806 ));
1807 $this->assertEquals(3, $lineItems['count']);
1808 $totalLineAmount = 0;
1809 foreach ($lineItems['values'] as $lineItem) {
1810 $totalLineAmount = $totalLineAmount + $lineItem['line_total'];
1811 }
1812 $this->assertEquals(80, $totalLineAmount);
1813 }
1814
1815 /**
1816 * Function to add additional price fields to priceset.
1817 * @param array $params
1818 */
1819 public function addPriceFields(&$params) {
1820 $priceSetID = reset($this->_ids['price_set']);
1821 $priceField = $this->callAPISuccess('price_field', 'create', array(
1822 'price_set_id' => $priceSetID,
1823 'label' => 'Chicken Breed',
1824 'html_type' => 'CheckBox',
1825 ));
1826 $priceFieldValue1 = $this->callAPISuccess('price_field_value', 'create', array(
1827 'price_set_id' => $priceSetID,
1828 'price_field_id' => $priceField['id'],
1829 'label' => 'Shoe-eating chicken -1',
1830 'financial_type_id' => 'Donation',
1831 'amount' => 30,
1832 ));
1833 $priceFieldValue2 = $this->callAPISuccess('price_field_value', 'create', array(
1834 'price_set_id' => $priceSetID,
1835 'price_field_id' => $priceField['id'],
1836 'label' => 'Shoe-eating chicken -2',
1837 'financial_type_id' => 'Donation',
1838 'amount' => 40,
1839 ));
1840 $params['price_' . $priceField['id']] = array(
1841 $priceFieldValue1['id'] => 1,
1842 $priceFieldValue2['id'] => 1,
1843 );
1844 }
1845
1846 /**
1847 * Test Tax Amount is calculated properly when using PriceSet with Field Type = Text/Numeric Quantity
1848 *
1849 * @param string $thousandSeparator
1850 * punctuation used to refer to thousands.
1851 *
1852 * @dataProvider getThousandSeparators
1853 */
1854 public function testSubmitContributionPageWithPriceSetQuantity($thousandSeparator) {
1855 $this->setCurrencySeparators($thousandSeparator);
1856 $this->_priceSetParams['is_quick_config'] = 0;
1857 $this->enableTaxAndInvoicing();
1858 $financialType = $this->createFinancialType();
1859 $financialTypeId = $financialType['id'];
1860 // 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%
1861 $this->relationForFinancialTypeWithFinancialAccount($financialType['id'], 5);
1862
1863 $this->setUpContributionPage();
1864 $submitParams = array(
1865 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
1866 'id' => (int) $this->_ids['contribution_page'],
1867 'first_name' => 'J',
1868 'last_name' => 'T',
1869 'email' => 'JT@ohcanada.ca',
1870 'is_pay_later' => TRUE,
1871 'receive_date' => date('Y-m-d H:i:s'),
1872 );
1873
1874 // Create PriceSet/PriceField
1875 $priceSetID = reset($this->_ids['price_set']);
1876 $priceField = $this->callAPISuccess('price_field', 'create', array(
1877 'price_set_id' => $priceSetID,
1878 'label' => 'Printing Rights',
1879 'html_type' => 'Text',
1880 ));
1881 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
1882 'price_set_id' => $priceSetID,
1883 'price_field_id' => $priceField['id'],
1884 'label' => 'Printing Rights',
1885 'financial_type_id' => $financialTypeId,
1886 'amount' => '16.95',
1887 ));
1888 $priceFieldId = $priceField['id'];
1889
1890 // Set quantity for our test
1891 $submitParams['price_' . $priceFieldId] = 180;
1892
1893 // contribution_page submit requires amount and tax_amount - and that's ok we're not testing that - we're testing at the LineItem level
1894 $submitParams['amount'] = $this->formatMoneyInput(180 * 16.95);
1895 // This is the correct Tax Amount - use it later to compare to what the CiviCRM Core came up with at the LineItem level
1896 $submitParams['tax_amount'] = $this->formatMoneyInput(180 * 16.95 * 0.10);
1897
1898 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
1899 $contribution = $this->callAPISuccessGetSingle('contribution', array(
1900 'contribution_page_id' => $this->_ids['contribution_page'],
1901 ));
1902
1903 // Retrieve the lineItem that belongs to the Printing Rights and check the tax_amount CiviCRM Core calculated for it
1904 $lineItem = $this->callAPISuccessGetSingle('LineItem', array(
1905 'contribution_id' => $contribution['id'],
1906 'label' => 'Printing Rights',
1907 ));
1908
1909 $lineItem_TaxAmount = round($lineItem['tax_amount'], 2);
1910
1911 $this->assertEquals($lineItem['line_total'], $contribution['total_amount'], 'Contribution total should match line total');
1912 $this->assertEquals($lineItem_TaxAmount, round(180 * 16.95 * 0.10, 2), 'Wrong Sales Tax Amount is calculated and stored.');
1913 }
1914
1915
1916 /**
1917 * Test validating a contribution page submit.
1918 */
1919 public function testValidate() {
1920 $this->setUpContributionPage();
1921 $errors = $this->callAPISuccess('ContributionPage', 'validate', array_merge($this->getBasicSubmitParams(), ['action' => 'submit']))['values'];
1922 $this->assertEmpty($errors);
1923 }
1924
1925 /**
1926 * Implements hook_civicrm_alterPaymentProcessorParams().
1927 *
1928 * @throws \Exception
1929 */
1930 public function hook_civicrm_alterPaymentProcessorParams($paymentObj, &$rawParams, &$cookedParams) {
1931 // Ensure total_amount are the same if they're both given.
1932 $total_amount = CRM_Utils_Array::value('total_amount', $rawParams);
1933 $amount = CRM_Utils_Array::value('amount', $rawParams);
1934 if (!empty($total_amount) && !empty($amount) && $total_amount != $amount) {
1935 throw new Exception("total_amount '$total_amount' and amount '$amount' differ.");
1936 }
1937
1938 // Log parameters for later debugging and testing.
1939 $message = __FUNCTION__ . ": {$rawParams['TEST_UNIQ']}:";
1940 $log_params = array_intersect_key($rawParams, array(
1941 'amount' => 1,
1942 'total_amount' => 1,
1943 'contributionID' => 1,
1944 ));
1945 $message .= json_encode($log_params);
1946 $log = new CRM_Utils_SystemLogger();
1947 $log->debug($message, $_REQUEST);
1948 }
1949
1950 /**
1951 * Get the params for a basic simple submit.
1952 *
1953 * @return array
1954 */
1955 protected function getBasicSubmitParams() {
1956 $priceFieldID = reset($this->_ids['price_field']);
1957 $priceFieldValueID = reset($this->_ids['price_field_value']);
1958 $submitParams = [
1959 'price_' . $priceFieldID => $priceFieldValueID,
1960 'id' => (int) $this->_ids['contribution_page'],
1961 'amount' => 10,
1962 'priceSetId' => $this->_ids['price_set'][0],
1963 'payment_processor_id' => 0,
1964 ];
1965 return $submitParams;
1966 }
1967
1968 }