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