Merge pull request #9023 from lcdservices/CRM-19354
[civicrm-core.git] / tests / phpunit / api / v3 / ContributionPageTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2016 |
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 /**
45 * Payment processor details.
46 * @var array
47 */
48 protected $_paymentProcessor = array();
49
50 /**
51 * @var array
52 * - contribution_page
53 * - price_set
54 * - price_field
55 * - price_field_value
56 */
57 protected $_ids = array();
58
59
60 public $DBResetRequired = TRUE;
61
62 public function setUp() {
63 parent::setUp();
64 $this->contactIds[] = $this->individualCreate();
65 $this->params = array(
66 'title' => "Test Contribution Page",
67 'financial_type_id' => 1,
68 'currency' => 'NZD',
69 'goal_amount' => $this->testAmount,
70 'is_pay_later' => 1,
71 'is_monetary' => TRUE,
72 'is_email_receipt' => TRUE,
73 'receipt_from_email' => 'yourconscience@donate.com',
74 'receipt_from_name' => 'Ego Freud',
75 );
76
77 $this->_priceSetParams = array(
78 'is_quick_config' => 1,
79 'extends' => 'CiviContribute',
80 'financial_type_id' => 'Donation',
81 'title' => 'my Page',
82 );
83 }
84
85 public function tearDown() {
86 foreach ($this->contactIds as $id) {
87 $this->callAPISuccess('contact', 'delete', array('id' => $id));
88 }
89 $this->quickCleanUpFinancialEntities();
90 }
91
92 public function testCreateContributionPage() {
93 $result = $this->callAPIAndDocument($this->_entity, 'create', $this->params, __FUNCTION__, __FILE__);
94 $this->assertEquals(1, $result['count']);
95 $this->assertNotNull($result['values'][$result['id']]['id']);
96 $this->getAndCheck($this->params, $result['id'], $this->_entity);
97 }
98
99 public function testGetBasicContributionPage() {
100 $createResult = $this->callAPISuccess($this->_entity, 'create', $this->params);
101 $this->id = $createResult['id'];
102 $getParams = array(
103 'currency' => 'NZD',
104 'financial_type_id' => 1,
105 );
106 $getResult = $this->callAPIAndDocument($this->_entity, 'get', $getParams, __FUNCTION__, __FILE__);
107 $this->assertEquals(1, $getResult['count']);
108 }
109
110 public function testGetContributionPageByAmount() {
111 $createResult = $this->callAPISuccess($this->_entity, 'create', $this->params);
112 $this->id = $createResult['id'];
113 $getParams = array(
114 'amount' => '' . $this->testAmount, // 3456
115 'currency' => 'NZD',
116 'financial_type_id' => 1,
117 );
118 $getResult = $this->callAPIAndDocument($this->_entity, 'get', $getParams, __FUNCTION__, __FILE__);
119 $this->assertEquals(1, $getResult['count']);
120 }
121
122 public function testDeleteContributionPage() {
123 $createResult = $this->callAPISuccess($this->_entity, 'create', $this->params);
124 $deleteParams = array('id' => $createResult['id']);
125 $this->callAPIAndDocument($this->_entity, 'delete', $deleteParams, __FUNCTION__, __FILE__);
126 $checkDeleted = $this->callAPISuccess($this->_entity, 'get', array());
127 $this->assertEquals(0, $checkDeleted['count']);
128 }
129
130 public function testGetFieldsContributionPage() {
131 $result = $this->callAPISuccess($this->_entity, 'getfields', array('action' => 'create'));
132 $this->assertEquals(12, $result['values']['start_date']['type']);
133 }
134
135
136 /**
137 * Test form submission with basic price set.
138 */
139 public function testSubmit() {
140 $this->setUpContributionPage();
141 $priceFieldID = reset($this->_ids['price_field']);
142 $priceFieldValueID = reset($this->_ids['price_field_value']);
143 $submitParams = array(
144 'price_' . $priceFieldID => $priceFieldValueID,
145 'id' => (int) $this->_ids['contribution_page'],
146 'amount' => 10,
147 );
148
149 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
150 $this->callAPISuccess('contribution', 'getsingle', array('contribution_page_id' => $this->_ids['contribution_page']));
151 }
152
153 /**
154 * Test form submission with billing first & last name where the contact does NOT
155 * otherwise have one.
156 */
157 public function testSubmitNewBillingNameData() {
158 $this->setUpContributionPage();
159 $contact = $this->callAPISuccess('Contact', 'create', array('contact_type' => 'Individual', 'email' => 'wonderwoman@amazon.com'));
160 $priceFieldID = reset($this->_ids['price_field']);
161 $priceFieldValueID = reset($this->_ids['price_field_value']);
162 $submitParams = array(
163 'price_' . $priceFieldID => $priceFieldValueID,
164 'id' => (int) $this->_ids['contribution_page'],
165 'amount' => 10,
166 'billing_first_name' => 'Wonder',
167 'billing_last_name' => 'Woman',
168 'contactID' => $contact['id'],
169 'email' => 'wonderwoman@amazon.com',
170 );
171
172 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
173 $contact = $this->callAPISuccess('Contact', 'get', array(
174 'id' => $contact['id'],
175 'return' => array(
176 'first_name',
177 'last_name',
178 'sort_name',
179 'display_name',
180 ),
181 ));
182 $this->assertEquals(array(
183 'first_name' => 'Wonder',
184 'last_name' => 'Woman',
185 'display_name' => 'Wonder Woman',
186 'sort_name' => 'Woman, Wonder',
187 'id' => $contact['id'],
188 'contact_id' => $contact['id'],
189 ), $contact['values'][$contact['id']]);
190
191 }
192
193 /**
194 * Test form submission with billing first & last name where the contact does
195 * otherwise have one and should not be overwritten.
196 */
197 public function testSubmitNewBillingNameDoNotOverwrite() {
198 $this->setUpContributionPage();
199 $contact = $this->callAPISuccess('Contact', 'create', array(
200 'contact_type' => 'Individual',
201 'email' => 'wonderwoman@amazon.com',
202 'first_name' => 'Super',
203 'last_name' => 'Boy',
204 ));
205 $priceFieldID = reset($this->_ids['price_field']);
206 $priceFieldValueID = reset($this->_ids['price_field_value']);
207 $submitParams = array(
208 'price_' . $priceFieldID => $priceFieldValueID,
209 'id' => (int) $this->_ids['contribution_page'],
210 'amount' => 10,
211 'billing_first_name' => 'Wonder',
212 'billing_last_name' => 'Woman',
213 'contactID' => $contact['id'],
214 'email' => 'wonderwoman@amazon.com',
215 );
216
217 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
218 $contact = $this->callAPISuccess('Contact', 'get', array(
219 'id' => $contact['id'],
220 'return' => array(
221 'first_name',
222 'last_name',
223 'sort_name',
224 'display_name',
225 ),
226 ));
227 $this->assertEquals(array(
228 'first_name' => 'Super',
229 'last_name' => 'Boy',
230 'display_name' => 'Super Boy',
231 'sort_name' => 'Boy, Super',
232 'id' => $contact['id'],
233 'contact_id' => $contact['id'],
234 ), $contact['values'][$contact['id']]);
235
236 }
237
238 /**
239 * Test process with instant payment when more than one configured for the page.
240 *
241 * CRM-16923
242 */
243 public function testSubmitRecurMultiProcessorInstantPayment() {
244 $this->setUpContributionPage();
245 $this->setupPaymentProcessor();
246 $paymentProcessor2ID = $this->paymentProcessorCreate(array(
247 'payment_processor_type_id' => 'Dummy',
248 'name' => 'processor 2',
249 'class_name' => 'Payment_Dummy',
250 'billing_mode' => 1,
251 ));
252 $dummyPP = Civi\Payment\System::singleton()->getById($paymentProcessor2ID);
253 $dummyPP->setDoDirectPaymentResult(array(
254 'payment_status_id' => 1,
255 'trxn_id' => 'create_first_success',
256 'fee_amount' => .85,
257 ));
258 $this->callAPISuccess('ContributionPage', 'create', array(
259 'id' => $this->_ids['contribution_page'],
260 'payment_processor' => array($paymentProcessor2ID, $this->_ids['payment_processor']),
261 ));
262
263 $priceFieldID = reset($this->_ids['price_field']);
264 $priceFieldValueID = reset($this->_ids['price_field_value']);
265 $submitParams = array(
266 'price_' . $priceFieldID => $priceFieldValueID,
267 'id' => (int) $this->_ids['contribution_page'],
268 'amount' => 10,
269 'is_recur' => 1,
270 'frequency_interval' => 1,
271 'frequency_unit' => 'month',
272 'payment_processor_id' => $paymentProcessor2ID,
273 );
274
275 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
276 $contribution = $this->callAPISuccess('contribution', 'getsingle', array(
277 'contribution_page_id' => $this->_ids['contribution_page'],
278 'contribution_status_id' => 1,
279 ));
280 $this->assertEquals('create_first_success', $contribution['trxn_id']);
281 $this->assertEquals(10, $contribution['total_amount']);
282 $this->assertEquals(.85, $contribution['fee_amount']);
283 $this->assertEquals(9.15, $contribution['net_amount']);
284 $this->_checkFinancialRecords(array(
285 'id' => $contribution['id'],
286 'total_amount' => $contribution['total_amount'],
287 ), 'online');
288 }
289
290 /**
291 * Test submit with a membership block in place.
292 */
293 public function testSubmitMembershipBlockNotSeparatePayment() {
294 $this->setUpMembershipContributionPage();
295 $submitParams = array(
296 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
297 'id' => (int) $this->_ids['contribution_page'],
298 'amount' => 10,
299 'billing_first_name' => 'Billy',
300 'billing_middle_name' => 'Goat',
301 'billing_last_name' => 'Gruff',
302 'selectMembership' => $this->_ids['membership_type'],
303
304 );
305
306 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
307 $contribution = $this->callAPISuccess('contribution', 'getsingle', array('contribution_page_id' => $this->_ids['contribution_page']));
308 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', array('contribution_id' => $contribution['id']));
309 $this->callAPISuccessGetSingle('LineItem', array('contribution_id' => $contribution['id'], 'entity_id' => $membershipPayment['id']));
310 }
311
312 /**
313 * Test submit with a membership block in place.
314 */
315 public function testSubmitMembershipBlockNotSeparatePaymentWithEmail() {
316 $mut = new CiviMailUtils($this, TRUE);
317 $this->setUpMembershipContributionPage();
318 $this->addProfile('supporter_profile', $this->_ids['contribution_page']);
319
320 $submitParams = array(
321 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
322 'id' => (int) $this->_ids['contribution_page'],
323 'amount' => 10,
324 'billing_first_name' => 'Billy',
325 'billing_middle_name' => 'Goat',
326 'billing_last_name' => 'Gruff',
327 'selectMembership' => $this->_ids['membership_type'],
328 'email-Primary' => 'billy-goat@the-bridge.net',
329 'payment_processor_id' => $this->_paymentProcessor['id'],
330 'credit_card_number' => '4111111111111111',
331 'credit_card_type' => 'Visa',
332 'credit_card_exp_date' => array('M' => 9, 'Y' => 2040),
333 'cvv2' => 123,
334 );
335
336 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
337 $contribution = $this->callAPISuccess('contribution', 'getsingle', array('contribution_page_id' => $this->_ids['contribution_page']));
338 $this->callAPISuccess('membership_payment', 'getsingle', array('contribution_id' => $contribution['id']));
339 $mut->checkMailLog(array(
340 'Membership Type: General',
341 ));
342 $mut->stop();
343 $mut->clearMessages();
344 }
345
346 /**
347 * Test submit with a membership block in place.
348 */
349 public function testSubmitMembershipBlockNotSeparatePaymentZeroDollarsWithEmail() {
350 $mut = new CiviMailUtils($this, TRUE);
351 $this->_ids['membership_type'] = array($this->membershipTypeCreate(array('minimum_fee' => 0)));
352 $this->setUpMembershipContributionPage();
353 $this->addProfile('supporter_profile', $this->_ids['contribution_page']);
354
355 $submitParams = array(
356 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
357 'id' => (int) $this->_ids['contribution_page'],
358 'amount' => 0,
359 'billing_first_name' => 'Billy',
360 'billing_middle_name' => 'Goat',
361 'billing_last_name' => 'Gruffier',
362 'selectMembership' => $this->_ids['membership_type'],
363 'email-Primary' => 'billy-goat@the-new-bridge.net',
364 );
365
366 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
367 $contribution = $this->callAPISuccess('contribution', 'getsingle', array('contribution_page_id' => $this->_ids['contribution_page']));
368 $this->callAPISuccess('membership_payment', 'getsingle', array('contribution_id' => $contribution['id']));
369 $mut->checkMailLog(array(
370 'Membership Type: General',
371 'Gruffier',
372 ),
373 array(
374 'Amount',
375 )
376 );
377 $mut->stop();
378 $mut->clearMessages(999);
379 }
380
381 /**
382 * Test submit with a membership block in place.
383 */
384 public function testSubmitMembershipBlockIsSeparatePayment() {
385 $this->setUpMembershipContributionPage(TRUE);
386 $this->_ids['membership_type'] = array($this->membershipTypeCreate(array('minimum_fee' => 2)));
387 $submitParams = array(
388 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
389 'id' => (int) $this->_ids['contribution_page'],
390 'amount' => 10,
391 'billing_first_name' => 'Billy',
392 'billing_middle_name' => 'Goat',
393 'billing_last_name' => 'Gruff',
394 'selectMembership' => $this->_ids['membership_type'],
395 );
396
397 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
398 $contributions = $this->callAPISuccess('contribution', 'get', array('contribution_page_id' => $this->_ids['contribution_page']));
399 $this->assertCount(2, $contributions['values']);
400 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', array());
401 $this->assertTrue(in_array($membershipPayment['contribution_id'], array_keys($contributions['values'])));
402 $membership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
403 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
404 }
405
406 /**
407 * Test submit with a membership block in place.
408 */
409 public function testSubmitMembershipBlockIsSeparatePaymentWithEmail() {
410 $mut = new CiviMailUtils($this, TRUE);
411 $this->setUpMembershipContributionPage(TRUE);
412 $this->addProfile('supporter_profile', $this->_ids['contribution_page']);
413
414 $submitParams = array(
415 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
416 'id' => (int) $this->_ids['contribution_page'],
417 'amount' => 10,
418 'billing_first_name' => 'Billy',
419 'billing_middle_name' => 'Goat',
420 'billing_last_name' => 'Gruff',
421 'selectMembership' => $this->_ids['membership_type'],
422 'email-Primary' => 'billy-goat@the-bridge.net',
423 'payment_processor_id' => $this->_paymentProcessor['id'],
424 'credit_card_number' => '4111111111111111',
425 'credit_card_type' => 'Visa',
426 'credit_card_exp_date' => array('M' => 9, 'Y' => 2040),
427 'cvv2' => 123,
428 );
429
430 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
431 $contributions = $this->callAPISuccess('contribution', 'get', array('contribution_page_id' => $this->_ids['contribution_page']));
432 $this->assertCount(2, $contributions['values']);
433 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', array());
434 $this->assertTrue(in_array($membershipPayment['contribution_id'], array_keys($contributions['values'])));
435 $membership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
436 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
437 $mut->checkAllMailLog(array(
438 '$ 2.00',
439 'Membership Fee',
440 ));
441 $mut->stop();
442 $mut->clearMessages(999);
443 }
444
445 /**
446 * Test submit with a membership block in place.
447 */
448 public function testSubmitMembershipBlockIsSeparatePaymentZeroDollarsPayLaterWithEmail() {
449 $mut = new CiviMailUtils($this, TRUE);
450 $this->_ids['membership_type'] = array($this->membershipTypeCreate(array('minimum_fee' => 0)));
451 $this->setUpMembershipContributionPage(TRUE);
452 $this->addProfile('supporter_profile', $this->_ids['contribution_page']);
453
454 $submitParams = array(
455 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
456 'id' => (int) $this->_ids['contribution_page'],
457 'amount' => 0,
458 'billing_first_name' => 'Billy',
459 'billing_middle_name' => 'Goat',
460 'billing_last_name' => 'Gruffalo',
461 'selectMembership' => $this->_ids['membership_type'],
462 'payment_processor_id' => 0,
463 'email-Primary' => 'gruffalo@the-bridge.net',
464 );
465
466 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
467 $contributions = $this->callAPISuccess('contribution', 'get', array('contribution_page_id' => $this->_ids['contribution_page']));
468 $this->assertCount(2, $contributions['values']);
469 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', array());
470 $this->assertTrue(in_array($membershipPayment['contribution_id'], array_keys($contributions['values'])));
471 $membership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
472 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
473 $mut->checkMailLog(array(
474 'Gruffalo',
475 'General Membership: $ 0.00',
476 'Membership Fee',
477 ));
478 $mut->stop();
479 $mut->clearMessages();
480 }
481
482 /**
483 * Test submit with a membership block in place.
484 */
485 public function testSubmitMembershipBlockTwoTypesIsSeparatePayment() {
486 $this->_ids['membership_type'] = array($this->membershipTypeCreate(array('minimum_fee' => 6)));
487 $this->_ids['membership_type'][] = $this->membershipTypeCreate(array('name' => 'Student', 'minimum_fee' => 50));
488 $this->setUpMembershipContributionPage(TRUE);
489 $submitParams = array(
490 'price_' . $this->_ids['price_field'][0] => $this->_ids['price_field_value'][1],
491 'id' => (int) $this->_ids['contribution_page'],
492 'amount' => 10,
493 'billing_first_name' => 'Billy',
494 'billing_middle_name' => 'Goat',
495 'billing_last_name' => 'Gruff',
496 'selectMembership' => $this->_ids['membership_type'][1],
497 );
498
499 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
500 $contributions = $this->callAPISuccess('contribution', 'get', array('contribution_page_id' => $this->_ids['contribution_page']));
501 $this->assertCount(2, $contributions['values']);
502 $ids = array_keys($contributions['values']);
503 $this->assertEquals('10.00', $contributions['values'][$ids[0]]['total_amount']);
504 $this->assertEquals('50.00', $contributions['values'][$ids[1]]['total_amount']);
505 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', array());
506 $this->assertArrayHasKey($membershipPayment['contribution_id'], $contributions['values']);
507 $membership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
508 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
509 }
510
511 /**
512 * Test submit with a membership block in place.
513 *
514 * We are expecting a separate payment for the membership vs the contribution.
515 */
516 public function testSubmitMembershipBlockIsSeparatePaymentPaymentProcessorNow() {
517 $this->setUpMembershipContributionPage(TRUE);
518 $processor = Civi\Payment\System::singleton()->getById($this->_paymentProcessor['id']);
519 $processor->setDoDirectPaymentResult(array('fee_amount' => .72));
520 $submitParams = array(
521 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
522 'id' => (int) $this->_ids['contribution_page'],
523 'amount' => 10,
524 'billing_first_name' => 'Billy',
525 'billing_middle_name' => 'Goat',
526 'billing_last_name' => 'Gruff',
527 'selectMembership' => $this->_ids['membership_type'],
528 'payment_processor_id' => $this->_paymentProcessor['id'],
529 'credit_card_number' => '4111111111111111',
530 'credit_card_type' => 'Visa',
531 'credit_card_exp_date' => array('M' => 9, 'Y' => 2040),
532 'cvv2' => 123,
533 );
534
535 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
536 $contributions = $this->callAPISuccess('contribution', 'get', array(
537 'contribution_page_id' => $this->_ids['contribution_page'],
538 'contribution_status_id' => 1,
539 ));
540 $this->assertCount(2, $contributions['values']);
541 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', array());
542 $this->assertTrue(in_array($membershipPayment['contribution_id'], array_keys($contributions['values'])));
543 $membership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
544 $this->assertEquals($membership['contact_id'], $contributions['values'][$membershipPayment['contribution_id']]['contact_id']);
545 foreach ($contributions['values'] as $contribution) {
546 $this->assertEquals(.72, $contribution['fee_amount']);
547 $this->assertEquals($contribution['total_amount'] - .72, $contribution['net_amount']);
548 }
549 }
550
551 /**
552 * Test that when a transaction fails the pending contribution remains.
553 *
554 * An activity should also be created. CRM-16417.
555 */
556 public function testSubmitPaymentProcessorFailure() {
557 $this->setUpContributionPage();
558 $this->setupPaymentProcessor();
559 $this->createLoggedInUser();
560 $priceFieldID = reset($this->_ids['price_field']);
561 $priceFieldValueID = reset($this->_ids['price_field_value']);
562 $submitParams = array(
563 'price_' . $priceFieldID => $priceFieldValueID,
564 'id' => (int) $this->_ids['contribution_page'],
565 'amount' => 10,
566 'payment_processor_id' => 1,
567 'credit_card_number' => '4111111111111111',
568 'credit_card_type' => 'Visa',
569 'credit_card_exp_date' => array('M' => 9, 'Y' => 2008),
570 'cvv2' => 123,
571 );
572
573 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
574 $contribution = $this->callAPISuccessGetSingle('contribution', array(
575 'contribution_page_id' => $this->_ids['contribution_page'],
576 'contribution_status_id' => 2,
577 ));
578
579 $this->callAPISuccessGetSingle('activity', array(
580 'source_record_id' => $contribution['id'],
581 'activity_type_id' => 'Failed Payment',
582 ));
583
584 }
585
586 /**
587 * Test submit recurring membership with immediate confirmation (IATS style).
588 *
589 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
590 * processor (IATS style - denoted by returning trxn_id)
591 * - the first creates a new membership, completed contribution, in progress recurring. Check these
592 * - create another - end date should be extended
593 */
594 public function testSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantPayment() {
595 $this->params['is_recur'] = 1;
596 $this->params['recur_frequency_unit'] = 'month';
597 $this->setUpMembershipContributionPage();
598 $dummyPP = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor);
599 $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 1, 'trxn_id' => 'create_first_success'));
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' => 10,
605 'billing_first_name' => 'Billy',
606 'billing_middle_name' => 'Goat',
607 'billing_last_name' => 'Gruff',
608 'email' => 'billy@goat.gruff',
609 'selectMembership' => $this->_ids['membership_type'],
610 'payment_processor_id' => 1,
611 'credit_card_number' => '4111111111111111',
612 'credit_card_type' => 'Visa',
613 'credit_card_exp_date' => array('M' => 9, 'Y' => 2040),
614 'cvv2' => 123,
615 'is_recur' => 1,
616 'frequency_interval' => 1,
617 'frequency_unit' => 'month',
618 );
619
620 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
621 $contribution = $this->callAPISuccess('contribution', 'getsingle', array(
622 'contribution_page_id' => $this->_ids['contribution_page'],
623 'contribution_status_id' => 1,
624 ));
625
626 $this->assertEquals('create_first_success', $contribution['trxn_id']);
627 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', array());
628 $this->assertEquals($membershipPayment['contribution_id'], $contribution['id']);
629 $membership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
630 $this->assertEquals($membership['contact_id'], $contribution['contact_id']);
631 $this->assertEquals(1, $membership['status_id']);
632 $this->callAPISuccess('contribution_recur', 'getsingle', array('id' => $contribution['contribution_recur_id']));
633 //@todo - check with Joe about these not existing
634 //$this->callAPISuccess('line_item', 'getsingle', array('contribution_id' => $contribution['id'], 'entity_id' => $membership['id']));
635 //renew it with processor setting completed - should extend membership
636 $submitParams['contact_id'] = $contribution['contact_id'];
637 $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 1, 'trxn_id' => 'create_second_success'));
638 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
639 $this->callAPISuccess('contribution', 'getsingle', array(
640 'id' => array('NOT IN' => array($contribution['id'])),
641 'contribution_page_id' => $this->_ids['contribution_page'],
642 'contribution_status_id' => 1,
643 ));
644 $renewedMembership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
645 $this->assertEquals(date('Y-m-d', strtotime('+ 1 year', strtotime($membership['end_date']))), $renewedMembership['end_date']);
646 $recurringContribution = $this->callAPISuccess('contribution_recur', 'getsingle', array('id' => $contribution['contribution_recur_id']));
647 $this->assertEquals(5, $recurringContribution['contribution_status_id']);
648 }
649
650 /**
651 * Test submit recurring membership with immediate confirmation (IATS style).
652 *
653 * - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
654 * processor (IATS style - denoted by returning trxn_id)
655 * - the first creates a new membership, completed contribution, in progress recurring. Check these
656 * - create another - end date should be extended
657 */
658 public function testSubmitMembershipPriceSetPaymentPaymentProcessorSeparatePaymentRecurInstantPayment() {
659
660 $this->setUpMembershipContributionPage(TRUE);
661 $dummyPP = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor);
662 $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 1, 'trxn_id' => 'create_first_success'));
663
664 $submitParams = array(
665 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
666 'id' => (int) $this->_ids['contribution_page'],
667 'amount' => 10,
668 'billing_first_name' => 'Billy',
669 'billing_middle_name' => 'Goat',
670 'billing_last_name' => 'Gruff',
671 'email' => 'billy@goat.gruff',
672 'selectMembership' => $this->_ids['membership_type'],
673 'payment_processor_id' => 1,
674 'credit_card_number' => '4111111111111111',
675 'credit_card_type' => 'Visa',
676 'credit_card_exp_date' => array('M' => 9, 'Y' => 2040),
677 'cvv2' => 123,
678 'is_recur' => 1,
679 'auto_renew' => TRUE,
680 'frequency_interval' => 1,
681 'frequency_unit' => 'month',
682 );
683
684 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
685 $contribution = $this->callAPISuccess('contribution', 'get', array(
686 'contribution_page_id' => $this->_ids['contribution_page'],
687 'contribution_status_id' => 1,
688 ));
689
690 $this->assertEquals(2, $contribution['count']);
691 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', array());
692 $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
693 $this->assertNotEmpty($contribution['values'][$membershipPayment['contribution_id']]['contribution_recur_id']);
694 $this->callAPISuccess('contribution_recur', 'getsingle', array());
695 }
696
697 /**
698 * Test submit recurring membership with delayed confirmation (Authorize.net style)
699 * - we process 2 membership transactions against with a recurring contribution against a contribution page with a delayed
700 * processor (Authorize.net style - denoted by NOT returning trxn_id)
701 * - the first creates a pending membership, pending contribution, penging recurring. Check these
702 * - complete the transaction
703 * - create another - end date should NOT be extended
704 */
705 public function testSubmitMembershipPriceSetPaymentPaymentProcessorRecurDelayed() {
706 $this->params['is_recur'] = 1;
707 $this->params['recur_frequency_unit'] = 'month';
708 $this->setUpMembershipContributionPage();
709 $dummyPP = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor);
710 $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 2));
711
712 $submitParams = array(
713 'price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']),
714 'id' => (int) $this->_ids['contribution_page'],
715 'amount' => 10,
716 'billing_first_name' => 'Billy',
717 'billing_middle_name' => 'Goat',
718 'billing_last_name' => 'Gruff',
719 'email' => 'billy@goat.gruff',
720 'selectMembership' => $this->_ids['membership_type'],
721 'payment_processor_id' => 1,
722 'credit_card_number' => '4111111111111111',
723 'credit_card_type' => 'Visa',
724 'credit_card_exp_date' => array('M' => 9, 'Y' => 2040),
725 'cvv2' => 123,
726 'is_recur' => 1,
727 'frequency_interval' => 1,
728 'frequency_unit' => 'month',
729 );
730
731 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
732 $contribution = $this->callAPISuccess('contribution', 'getsingle', array(
733 'contribution_page_id' => $this->_ids['contribution_page'],
734 'contribution_status_id' => 2,
735 ));
736 $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', array());
737 $this->assertEquals($membershipPayment['contribution_id'], $contribution['id']);
738 $membership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
739 $this->assertEquals($membership['contact_id'], $contribution['contact_id']);
740 $this->assertEquals(5, $membership['status_id']);
741 //@todo - check with Joe about these not existing
742 //$this->callAPISuccess('line_item', 'getsingle', array('contribution_id' => $contribution['id'], 'entity_id' => $membership['id']));
743 $this->callAPISuccess('contribution', 'completetransaction', array(
744 'id' => $contribution['id'],
745 'trxn_id' => 'ipn_called',
746 'payment_processor_id' => $this->_paymentProcessor['id'],
747 ));
748 $membership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
749 //renew it with processor setting completed - should extend membership
750 $submitParams = array_merge($submitParams, array(
751 'contact_id' => $contribution['contact_id'],
752 'is_recur' => 1,
753 'frequency_interval' => 1,
754 'frequency_unit' => 'month',
755 )
756 );
757 $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 2));
758 $this->callAPISuccess('contribution_page', 'submit', $submitParams);
759 $newContribution = $this->callAPISuccess('contribution', 'getsingle', array(
760 'id' => array(
761 'NOT IN' => array($contribution['id']),
762 ),
763 'contribution_page_id' => $this->_ids['contribution_page'],
764 'contribution_status_id' => 2,
765 )
766 );
767
768 $renewedMembership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
769 //no renewal as the date hasn't changed
770 $this->assertEquals($membership['end_date'], $renewedMembership['end_date']);
771 $recurringContribution = $this->callAPISuccess('contribution_recur', 'getsingle', array('id' => $newContribution['contribution_recur_id']));
772 $this->assertEquals(2, $recurringContribution['contribution_status_id']);
773 }
774
775 /**
776 * Set up membership contribution page.
777 * @param bool $isSeparatePayment
778 */
779 public function setUpMembershipContributionPage($isSeparatePayment = FALSE) {
780 $this->setUpMembershipBlockPriceSet();
781 $this->setupPaymentProcessor();
782 $this->setUpContributionPage();
783
784 $this->callAPISuccess('membership_block', 'create', array(
785 'entity_id' => $this->_ids['contribution_page'],
786 'entity_table' => 'civicrm_contribution_page',
787 'is_required' => TRUE,
788 'is_active' => TRUE,
789 'is_separate_payment' => $isSeparatePayment,
790 'membership_type_default' => $this->_ids['membership_type'],
791 ));
792 }
793
794 /**
795 * Set up pledge block.
796 */
797 public function setUpPledgeBlock() {
798 $params = array(
799 'entity_table' => 'civicrm_contribution_page',
800 'entity_id' => $this->_ids['contribution_page'],
801 'pledge_frequency_unit' => 'week',
802 'is_pledge_interval' => 0,
803 'pledge_start_date' => json_encode(array('calendar_date' => date('Ymd', strtotime("+1 month")))),
804 );
805 $pledgeBlock = CRM_Pledge_BAO_PledgeBlock::create($params);
806 $this->_ids['pledge_block_id'] = $pledgeBlock->id;
807 }
808
809 /**
810 * The default data set does not include a complete default membership price set - not quite sure why.
811 *
812 * This function ensures it exists & populates $this->_ids with it's data
813 */
814 public function setUpMembershipBlockPriceSet() {
815 $this->_ids['price_set'][] = $this->callAPISuccess('price_set', 'getvalue', array(
816 'name' => 'default_membership_type_amount',
817 'return' => 'id',
818 ));
819 if (empty($this->_ids['membership_type'])) {
820 $this->_ids['membership_type'] = array($this->membershipTypeCreate(array('minimum_fee' => 2)));
821 }
822 $priceField = $this->callAPISuccess('price_field', 'create', array(
823 'price_set_id' => reset($this->_ids['price_set']),
824 'name' => 'membership_amount',
825 'label' => 'Membership Amount',
826 'html_type' => 'Radio',
827 'sequential' => 1,
828 ));
829 $this->_ids['price_field'][] = $priceField['id'];
830
831 foreach ($this->_ids['membership_type'] as $membershipTypeID) {
832 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
833 'name' => 'membership_amount',
834 'label' => 'Membership Amount',
835 'amount' => 1,
836 'financial_type_id' => 'Donation',
837 'format.only_id' => TRUE,
838 'membership_type_id' => $membershipTypeID,
839 'price_field_id' => $priceField['id'],
840 ));
841 $this->_ids['price_field_value'][] = $priceFieldValue;
842 }
843 }
844
845 /**
846 * Add text field other amount to the price set.
847 */
848 public function addOtherAmountFieldToMembershipPriceSet() {
849 $this->_ids['price_field']['other_amount'] = $this->callAPISuccess('price_field', 'create', array(
850 'price_set_id' => reset($this->_ids['price_set']),
851 'name' => 'other_amount',
852 'label' => 'Other Amount',
853 'html_type' => 'Text',
854 'format.only_id' => TRUE,
855 'sequential' => 1,
856 ));
857 $this->_ids['price_field_value']['other_amount'] = $this->callAPISuccess('price_field_value', 'create', array(
858 'financial_type_id' => 'Donation',
859 'format.only_id' => TRUE,
860 'label' => 'Other Amount',
861 'amount' => 1,
862 'price_field_id' => $this->_ids['price_field']['other_amount'],
863 ));
864 }
865
866 /**
867 * Help function to set up contribution page with some defaults.
868 */
869 public function setUpContributionPage() {
870 $contributionPageResult = $this->callAPISuccess($this->_entity, 'create', $this->params);
871 if (empty($this->_ids['price_set'])) {
872 $priceSet = $this->callAPISuccess('price_set', 'create', $this->_priceSetParams);
873 $this->_ids['price_set'][] = $priceSet['id'];
874 }
875 $priceSetID = reset($this->_ids['price_set']);
876 CRM_Price_BAO_PriceSet::addTo('civicrm_contribution_page', $contributionPageResult['id'], $priceSetID);
877
878 if (empty($this->_ids['price_field'])) {
879 $priceField = $this->callAPISuccess('price_field', 'create', array(
880 'price_set_id' => $priceSetID,
881 'label' => 'Goat Breed',
882 'html_type' => 'Radio',
883 ));
884 $this->_ids['price_field'] = array($priceField['id']);
885 }
886 if (empty($this->_ids['price_field_value'])) {
887 $this->callAPISuccess('price_field_value', 'create', array(
888 'price_set_id' => $priceSetID,
889 'price_field_id' => $priceField['id'],
890 'label' => 'Long Haired Goat',
891 'financial_type_id' => 'Donation',
892 'amount' => 20,
893 'financial_type_id' => 'Donation',
894 )
895 );
896 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
897 'price_set_id' => $priceSetID,
898 'price_field_id' => $priceField['id'],
899 'label' => 'Shoe-eating Goat',
900 'financial_type_id' => 'Donation',
901 'amount' => 10,
902 'financial_type_id' => 'Donation',
903 )
904 );
905 $this->_ids['price_field_value'] = array($priceFieldValue['id']);
906 }
907 $this->_ids['contribution_page'] = $contributionPageResult['id'];
908 }
909
910 public static function setUpBeforeClass() {
911 // put stuff here that should happen before all tests in this unit
912 }
913
914 public static function tearDownAfterClass() {
915 $tablesToTruncate = array(
916 'civicrm_contact',
917 'civicrm_financial_type',
918 'civicrm_contribution',
919 'civicrm_contribution_page',
920 );
921 $unitTest = new CiviUnitTestCase();
922 $unitTest->quickCleanup($tablesToTruncate);
923 }
924
925 /**
926 * Create a payment processor instance.
927 */
928 protected function setupPaymentProcessor() {
929 $this->params['payment_processor_id'] = $this->_ids['payment_processor'] = $this->paymentProcessorCreate(array(
930 'payment_processor_type_id' => 'Dummy',
931 'class_name' => 'Payment_Dummy',
932 'billing_mode' => 1,
933 ));
934 $this->_paymentProcessor = $this->callAPISuccess('payment_processor', 'getsingle', array('id' => $this->params['payment_processor_id']));
935 }
936
937 /**
938 * Test submit recurring pledge.
939 *
940 * - 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.
941 */
942 public function testSubmitPledgePaymentPaymentProcessorRecurFuturePayment() {
943 $this->params['adjust_recur_start_date'] = TRUE;
944 $this->params['is_pay_later'] = FALSE;
945 $this->setUpContributionPage();
946 $this->setUpPledgeBlock();
947 $this->setupPaymentProcessor();
948 $dummyPP = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor);
949 $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 1, 'trxn_id' => 'create_first_success'));
950
951 $submitParams = array(
952 'id' => (int) $this->_ids['contribution_page'],
953 'amount' => 100,
954 'billing_first_name' => 'Billy',
955 'billing_middle_name' => 'Goat',
956 'billing_last_name' => 'Gruff',
957 'email' => 'billy@goat.gruff',
958 'payment_processor_id' => 1,
959 'credit_card_number' => '4111111111111111',
960 'credit_card_type' => 'Visa',
961 'credit_card_exp_date' => array('M' => 9, 'Y' => 2040),
962 'cvv2' => 123,
963 'pledge_frequency_interval' => 1,
964 'pledge_frequency_unit' => 'week',
965 'pledge_installments' => 3,
966 'is_pledge' => TRUE,
967 'pledge_block_id' => (int) $this->_ids['pledge_block_id'],
968 );
969
970 $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
971
972 // Check if contribution created.
973 $contribution = $this->callAPISuccess('contribution', 'getsingle', array(
974 'contribution_page_id' => $this->_ids['contribution_page'],
975 'contribution_status_id' => 'Completed', // Will be pending when actual payment processor is used (dummy processor does not support future payments).
976 ));
977
978 $this->assertEquals('create_first_success', $contribution['trxn_id']);
979
980 // Check if pledge created.
981 $pledge = $this->callAPISuccess('pledge', 'getsingle', array());
982 $this->assertEquals(date('Ymd', strtotime($pledge['pledge_start_date'])), date('Ymd', strtotime("+1 month")));
983 $this->assertEquals($pledge['pledge_amount'], 300.00);
984
985 // Check if pledge payments created.
986 $params = array(
987 'pledge_id' => $pledge['id'],
988 );
989 $pledgePayment = $this->callAPISuccess('pledge_payment', 'get', $params);
990 $this->assertEquals($pledgePayment['count'], 3);
991 $this->assertEquals(date('Ymd', strtotime($pledgePayment['values'][1]['scheduled_date'])), date('Ymd', strtotime("+1 month")));
992 $this->assertEquals($pledgePayment['values'][1]['scheduled_amount'], 100.00);
993 $this->assertEquals($pledgePayment['values'][1]['status_id'], 1); // Will be pending when actual payment processor is used (dummy processor does not support future payments).
994
995 // Check contribution recur record.
996 $recur = $this->callAPISuccess('contribution_recur', 'getsingle', array('id' => $contribution['contribution_recur_id']));
997 $this->assertEquals(date('Ymd', strtotime($recur['start_date'])), date('Ymd', strtotime("+1 month")));
998 $this->assertEquals($recur['amount'], 100.00);
999 $this->assertEquals($recur['contribution_status_id'], 5); // In progress status.
1000 }
1001
1002 }