Order api updates to fix participant handling & deprecate creating 'completed'
[civicrm-core.git] / tests / phpunit / api / v3 / OrderTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 * Test APIv3 civicrm_contribute_* functions
30 *
31 * @package CiviCRM_APIv3
32 * @subpackage API_Contribution
33 * @group headless
34 */
35 class api_v3_OrderTest extends CiviUnitTestCase {
36
37 protected $_individualId;
38 protected $_financialTypeId = 1;
39 public $debug = 0;
40
41 /**
42 * Setup function.
43 *
44 * @throws \CRM_Core_Exception
45 */
46 public function setUp() {
47 parent::setUp();
48
49 $this->_apiversion = 3;
50 $this->_individualId = $this->individualCreate();
51 }
52
53 /**
54 * Clean up after each test.
55 *
56 * @throws \CRM_Core_Exception
57 */
58 public function tearDown() {
59 $this->quickCleanUpFinancialEntities();
60 $this->quickCleanup(['civicrm_uf_match']);
61 }
62
63 /**
64 * Test Get order api.
65 */
66 public function testGetOrder() {
67 $contribution = $this->addOrder(FALSE, 100);
68
69 $params = [
70 'contribution_id' => $contribution['id'],
71 ];
72
73 $order = $this->callAPIAndDocument('Order', 'get', $params, __FUNCTION__, __FILE__);
74
75 $this->assertEquals(1, $order['count']);
76 $expectedResult = [
77 $contribution['id'] => [
78 'total_amount' => 100,
79 'contribution_id' => $contribution['id'],
80 'contribution_status' => 'Completed',
81 'net_amount' => 100,
82 ],
83 ];
84 $lineItems[] = [
85 'entity_table' => 'civicrm_contribution',
86 'entity_id' => $contribution['id'],
87 'contribution_id' => $contribution['id'],
88 'unit_price' => 100,
89 'line_total' => 100,
90 'financial_type_id' => 1,
91 ];
92 $this->checkPaymentResult($order, $expectedResult, $lineItems);
93 $this->callAPISuccess('Contribution', 'Delete', [
94 'id' => $contribution['id'],
95 ]);
96 }
97
98 /**
99 * Test Get Order api for participant contribution.
100 */
101 public function testGetOrderParticipant() {
102 $this->addOrder(FALSE, 100);
103 list($items, $contribution) = $this->createParticipantWithContribution();
104
105 $params = [
106 'contribution_id' => $contribution['id'],
107 ];
108
109 $order = $this->callAPISuccess('Order', 'get', $params);
110
111 $this->assertEquals(2, count($order['values'][$contribution['id']]['line_items']));
112 $this->callAPISuccess('Contribution', 'Delete', [
113 'id' => $contribution['id'],
114 ]);
115 }
116
117 /**
118 * Function to assert db values.
119 */
120 public function checkPaymentResult($results, $expectedResult, $lineItems = NULL) {
121 foreach ($expectedResult[$results['id']] as $key => $value) {
122 $this->assertEquals($results['values'][$results['id']][$key], $value);
123 }
124
125 if ($lineItems) {
126 foreach ($lineItems as $key => $items) {
127 foreach ($items as $k => $item) {
128 $this->assertEquals($results['values'][$results['id']]['line_items'][$key][$k], $item);
129 }
130 }
131 }
132 }
133
134 /**
135 * Add order.
136 *
137 * @param bool $isPriceSet
138 * @param float $amount
139 * @param array $extraParams
140 *
141 * @return array
142 */
143 public function addOrder($isPriceSet, $amount = 300.00, $extraParams = []) {
144 $p = [
145 'contact_id' => $this->_individualId,
146 'receive_date' => '2010-01-20',
147 'total_amount' => $amount,
148 'financial_type_id' => $this->_financialTypeId,
149 'contribution_status_id' => 1,
150 ];
151
152 if ($isPriceSet) {
153 $priceFields = $this->createPriceSet();
154 foreach ($priceFields['values'] as $key => $priceField) {
155 $lineItems[1][$key] = [
156 'price_field_id' => $priceField['price_field_id'],
157 'price_field_value_id' => $priceField['id'],
158 'label' => $priceField['label'],
159 'field_title' => $priceField['label'],
160 'qty' => 1,
161 'unit_price' => $priceField['amount'],
162 'line_total' => $priceField['amount'],
163 'financial_type_id' => $priceField['financial_type_id'],
164 ];
165 }
166 $p['line_item'] = $lineItems;
167 }
168 $p = array_merge($extraParams, $p);
169 return $this->callAPISuccess('Contribution', 'create', $p);
170 }
171
172 /**
173 * Test create order api
174 */
175 public function testAddOrder() {
176 $order = $this->addOrder(FALSE, 100);
177 $params = [
178 'contribution_id' => $order['id'],
179 ];
180 $order = $this->callAPISuccess('order', 'get', $params);
181 $expectedResult = [
182 $order['id'] => [
183 'total_amount' => 100,
184 'contribution_id' => $order['id'],
185 'contribution_status' => 'Completed',
186 'net_amount' => 100,
187 ],
188 ];
189 $lineItems[] = [
190 'entity_table' => 'civicrm_contribution',
191 'entity_id' => $order['id'],
192 'contribution_id' => $order['id'],
193 'unit_price' => 100,
194 'line_total' => 100,
195 'financial_type_id' => 1,
196 ];
197 $this->checkPaymentResult($order, $expectedResult, $lineItems);
198 $this->callAPISuccess('Contribution', 'Delete', [
199 'id' => $order['id'],
200 ]);
201 }
202
203 /**
204 * Test create order api for membership
205 *
206 * @throws \CRM_Core_Exception
207 */
208 public function testAddOrderForMembership() {
209 $membershipType = $this->membershipTypeCreate();
210 $membershipType1 = $this->membershipTypeCreate();
211 $membershipType = $membershipTypes = [$membershipType, $membershipType1];
212 $p = [
213 'contact_id' => $this->_individualId,
214 'receive_date' => '2010-01-20',
215 'financial_type_id' => 'Event Fee',
216 'contribution_status_id' => 'Pending',
217 ];
218 $priceFields = $this->createPriceSet();
219 foreach ($priceFields['values'] as $key => $priceField) {
220 $lineItems[$key] = [
221 'price_field_id' => $priceField['price_field_id'],
222 'price_field_value_id' => $priceField['id'],
223 'label' => $priceField['label'],
224 'field_title' => $priceField['label'],
225 'qty' => 1,
226 'unit_price' => $priceField['amount'],
227 'line_total' => $priceField['amount'],
228 'financial_type_id' => $priceField['financial_type_id'],
229 'entity_table' => 'civicrm_membership',
230 'membership_type_id' => array_pop($membershipType),
231 ];
232 }
233 $p['line_items'][] = [
234 'line_item' => [array_pop($lineItems)],
235 'params' => [
236 'contact_id' => $this->_individualId,
237 'membership_type_id' => array_pop($membershipTypes),
238 'join_date' => '2006-01-21',
239 'start_date' => '2006-01-21',
240 'end_date' => '2006-12-21',
241 'source' => 'Payment',
242 'is_override' => 1,
243 ],
244 ];
245 $order = $this->callAPIAndDocument('order', 'create', $p, __FUNCTION__, __FILE__);
246 $params = [
247 'contribution_id' => $order['id'],
248 ];
249 $order = $this->callAPISuccess('order', 'get', $params);
250 $expectedResult = [
251 $order['id'] => [
252 'total_amount' => 200,
253 'contribution_id' => $order['id'],
254 'contribution_status' => 'Pending Label**',
255 'net_amount' => 200,
256 ],
257 ];
258 $this->checkPaymentResult($order, $expectedResult);
259 $this->callAPISuccessGetCount('MembershipPayment', $params, 1);
260 $this->callAPISuccess('Contribution', 'Delete', [
261 'id' => $order['id'],
262 ]);
263 $p['line_items'][] = [
264 'line_item' => [array_pop($lineItems)],
265 'params' => [
266 'contact_id' => $this->_individualId,
267 'membership_type_id' => array_pop($membershipTypes),
268 'join_date' => '2006-01-21',
269 'start_date' => '2006-01-21',
270 'end_date' => '2006-12-21',
271 'source' => 'Payment',
272 'is_override' => 1,
273 'status_id' => 'Pending',
274 ],
275 ];
276 $p['total_amount'] = 300;
277 $order = $this->callAPISuccess('order', 'create', $p);
278 $expectedResult = [
279 $order['id'] => [
280 'total_amount' => 300,
281 'contribution_status' => 'Pending Label**',
282 'net_amount' => 300,
283 ],
284 ];
285 $paymentMembership = [
286 'contribution_id' => $order['id'],
287 ];
288 $order = $this->callAPISuccess('order', 'get', $paymentMembership);
289 $this->checkPaymentResult($order, $expectedResult);
290 $this->callAPISuccessGetCount('MembershipPayment', $paymentMembership, 2);
291 $this->callAPISuccess('Contribution', 'Delete', [
292 'id' => $order['id'],
293 ]);
294 }
295
296 /**
297 * Test create order api for participant
298 *
299 * @throws \CRM_Core_Exception
300 */
301 public function testAddOrderForParticipant() {
302 $event = $this->eventCreate();
303 $this->_eventId = $event['id'];
304 $p = [
305 'contact_id' => $this->_individualId,
306 'receive_date' => '2010-01-20',
307 'financial_type_id' => $this->_financialTypeId,
308 'contribution_status_id' => 'Pending',
309 ];
310 $priceFields = $this->createPriceSet();
311 foreach ($priceFields['values'] as $key => $priceField) {
312 $lineItems[$key] = [
313 'price_field_id' => $priceField['price_field_id'],
314 'price_field_value_id' => $priceField['id'],
315 'label' => $priceField['label'],
316 'field_title' => $priceField['label'],
317 'qty' => 1,
318 'unit_price' => $priceField['amount'],
319 'line_total' => $priceField['amount'],
320 'financial_type_id' => $priceField['financial_type_id'],
321 'entity_table' => 'civicrm_participant',
322 ];
323 }
324 $p['line_items'][] = [
325 'line_item' => $lineItems,
326 'params' => [
327 'contact_id' => $this->_individualId,
328 'event_id' => $this->_eventId,
329 'role_id' => 1,
330 'register_date' => '2007-07-21 00:00:00',
331 'source' => 'Online Event Registration: API Testing',
332 ],
333 ];
334
335 $order = $this->callAPIAndDocument('order', 'create', $p, __FUNCTION__, __FILE__, 'Create order for participant', 'CreateOrderParticipant');
336 $params = ['contribution_id' => $order['id']];
337 $order = $this->callAPISuccess('order', 'get', $params);
338 $expectedResult = [
339 $order['id'] => [
340 'total_amount' => 300,
341 'contribution_id' => $order['id'],
342 'contribution_status' => 'Pending Label**',
343 'net_amount' => 300,
344 ],
345 ];
346 $this->checkPaymentResult($order, $expectedResult);
347 $paymentParticipant = $this->callAPISuccessGetSingle('ParticipantPayment', ['contribution_id' => $order['id']]);
348 $participant = $this->callAPISuccessGetSingle('Participant', ['participant_id' => $paymentParticipant['participant_id']]);
349 $this->assertEquals('Pending (incomplete transaction)', $participant['participant_status']);
350 $this->callAPISuccess('Contribution', 'Delete', [
351 'id' => $order['id'],
352 ]);
353
354 $p['line_items'][] = [
355 'line_item' => $lineItems,
356 'params' => [
357 'contact_id' => $this->individualCreate(),
358 'event_id' => $this->_eventId,
359 'role_id' => 1,
360 'register_date' => '2007-07-21 00:00:00',
361 'source' => 'Online Event Registration: API Testing',
362 ],
363 ];
364
365 $order = $this->callAPISuccess('order', 'create', $p);
366 $expectedResult = [
367 $order['id'] => [
368 'total_amount' => 600,
369 'contribution_status' => 'Pending Label**',
370 'net_amount' => 600,
371 ],
372 ];
373 $paymentParticipant = [
374 'contribution_id' => $order['id'],
375 ];
376 $order = $this->callAPISuccess('order', 'get', $paymentParticipant);
377 $this->checkPaymentResult($order, $expectedResult);
378 $this->callAPISuccessGetCount('ParticipantPayment', $paymentParticipant, 2);
379 $this->callAPISuccess('Contribution', 'Delete', [
380 'id' => $order['id'],
381 ]);
382 }
383
384 /**
385 * Test create order api with line items
386 */
387 public function testAddOrderWithLineItems() {
388 $order = $this->addOrder(TRUE);
389 $params = [
390 'contribution_id' => $order['id'],
391 ];
392 $order = $this->callAPISuccess('order', 'get', $params);
393 $expectedResult = [
394 $order['id'] => [
395 'total_amount' => 300,
396 'contribution_id' => $order['id'],
397 'contribution_status' => 'Completed',
398 'net_amount' => 300,
399 ],
400 ];
401 $items[] = [
402 'entity_table' => 'civicrm_contribution',
403 'entity_id' => $order['id'],
404 'contribution_id' => $order['id'],
405 'unit_price' => 100,
406 'line_total' => 100,
407 ];
408 $items[] = [
409 'entity_table' => 'civicrm_contribution',
410 'entity_id' => $order['id'],
411 'contribution_id' => $order['id'],
412 'unit_price' => 200,
413 'line_total' => 200,
414 ];
415 $this->checkPaymentResult($order, $expectedResult, $items);
416 $params = [
417 'entity_table' => 'civicrm_contribution',
418 'entity_id' => $order['id'],
419 ];
420 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
421 $this->assertEquals($eft['values'][$eft['id']]['amount'], 300);
422 $params = [
423 'entity_table' => 'civicrm_financial_item',
424 'financial_trxn_id' => $eft['values'][$eft['id']]['financial_trxn_id'],
425 ];
426 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
427 $amounts = [200, 100];
428 foreach ($eft['values'] as $value) {
429 $this->assertEquals($value['amount'], array_pop($amounts));
430 }
431 $this->callAPISuccess('Contribution', 'Delete', [
432 'id' => $order['id'],
433 ]);
434 }
435
436 /**
437 * Test delete order api
438 */
439 public function testDeleteOrder() {
440 $order = $this->addOrder(FALSE, 100);
441 $params = [
442 'contribution_id' => $order['id'],
443 ];
444 try {
445 $this->callAPISuccess('order', 'delete', $params);
446 $this->fail("Missed expected exception");
447 }
448 catch (Exception $expected) {
449 $this->callAPISuccess('Contribution', 'create', [
450 'contribution_id' => $order['id'],
451 'is_test' => TRUE,
452 ]);
453 $this->callAPIAndDocument('order', 'delete', $params, __FUNCTION__, __FILE__);
454 $order = $this->callAPISuccess('order', 'get', $params);
455 $this->assertEquals(0, $order['count']);
456 }
457 }
458
459 /**
460 * Test cancel order api
461 */
462 public function testCancelOrder() {
463 $contribution = $this->addOrder(FALSE, 100);
464 $params = [
465 'contribution_id' => $contribution['id'],
466 ];
467 $this->callAPIAndDocument('order', 'cancel', $params, __FUNCTION__, __FILE__);
468 $order = $this->callAPISuccess('Order', 'get', $params);
469 $expectedResult = [
470 $contribution['id'] => [
471 'total_amount' => 100,
472 'contribution_id' => $contribution['id'],
473 'contribution_status' => 'Cancelled',
474 'net_amount' => 100,
475 ],
476 ];
477 $this->checkPaymentResult($order, $expectedResult);
478 $this->callAPISuccess('Contribution', 'Delete', [
479 'id' => $contribution['id'],
480 ]);
481 }
482
483 /**
484 * Test cancel order api
485 */
486 public function testCancelWithParticipant() {
487 $event = $this->eventCreate();
488 $this->_eventId = $event['id'];
489 $eventParams = [
490 'id' => $this->_eventId,
491 'financial_type_id' => 4,
492 'is_monetary' => 1,
493 ];
494 $this->callAPISuccess('event', 'create', $eventParams);
495 $participantParams = [
496 'financial_type_id' => 4,
497 'event_id' => $this->_eventId,
498 'role_id' => 1,
499 'status_id' => 1,
500 'fee_currency' => 'USD',
501 'contact_id' => $this->_individualId,
502 ];
503 $participant = $this->callAPISuccess('Participant', 'create', $participantParams);
504 $extraParams = [
505 'contribution_mode' => 'participant',
506 'participant_id' => $participant['id'],
507 ];
508 $contribution = $this->addOrder(TRUE, 100, $extraParams);
509 $paymentParticipant = [
510 'participant_id' => $participant['id'],
511 'contribution_id' => $contribution['id'],
512 ];
513 $this->callAPISuccess('ParticipantPayment', 'create', $paymentParticipant);
514 $params = [
515 'contribution_id' => $contribution['id'],
516 ];
517 $this->callAPISuccess('order', 'cancel', $params);
518 $order = $this->callAPISuccess('Order', 'get', $params);
519 $expectedResult = [
520 $contribution['id'] => [
521 'total_amount' => 100,
522 'contribution_id' => $contribution['id'],
523 'contribution_status' => 'Cancelled',
524 'net_amount' => 100,
525 ],
526 ];
527 $this->checkPaymentResult($order, $expectedResult);
528 $participantPayment = $this->callAPISuccess('ParticipantPayment', 'getsingle', $params);
529 $participant = $this->callAPISuccess('participant', 'get', ['id' => $participantPayment['participant_id']]);
530 $this->assertEquals($participant['values'][$participant['id']]['participant_status'], 'Cancelled');
531 $this->callAPISuccess('Contribution', 'Delete', [
532 'id' => $contribution['id'],
533 ]);
534 }
535
536 /**
537 * Test an exception is thrown if line items do not add up to total_amount, no tax.
538 */
539 public function testCreateOrderIfTotalAmountDoesNotMatchLineItemsAmountsIfNoTaxSupplied() {
540 $params = [
541 'contact_id' => $this->_individualId,
542 'receive_date' => '2018-01-01',
543 'total_amount' => 50,
544 'financial_type_id' => $this->_financialTypeId,
545 'contribution_status_id' => 'Pending',
546 'line_items' => [
547 0 => [
548 'line_item' => [
549 '0' => [
550 'price_field_id' => 1,
551 'price_field_value_id' => 1,
552 'label' => 'Test 1',
553 'field_title' => 'Test 1',
554 'qty' => 1,
555 'unit_price' => 40,
556 'line_total' => 40,
557 'financial_type_id' => 1,
558 'entity_table' => 'civicrm_contribution',
559 ],
560 ],
561 ],
562 ],
563 ];
564
565 $this->callAPIFailure('Order', 'create', $params, 'Line item total doesn\'t match with total amount');
566 }
567
568 /**
569 * Test an exception is thrown if line items do not add up to total_amount, with tax.
570 */
571 public function testCreateOrderIfTotalAmountDoesNotMatchLineItemsAmountsIfTaxSupplied() {
572 $params = [
573 'contact_id' => $this->_individualId,
574 'receive_date' => '2018-01-01',
575 'total_amount' => 50,
576 'financial_type_id' => $this->_financialTypeId,
577 'contribution_status_id' => 'Pending',
578 'tax_amount' => 15,
579 'line_items' => [
580 0 => [
581 'line_item' => [
582 '0' => [
583 'price_field_id' => 1,
584 'price_field_value_id' => 1,
585 'label' => 'Test 1',
586 'field_title' => 'Test 1',
587 'qty' => 1,
588 'unit_price' => 30,
589 'line_total' => 30,
590 'financial_type_id' => 1,
591 'entity_table' => 'civicrm_contribution',
592 'tax_amount' => 15,
593 ],
594 ],
595 ],
596 ],
597 ];
598
599 $this->callAPIFailure('Order', 'create', $params, 'Line item total doesn\'t match with total amount.');
600 }
601
602 public function testCreateOrderIfTotalAmountDoesMatchLineItemsAmountsAndTaxSupplied() {
603 $params = [
604 'contact_id' => $this->_individualId,
605 'receive_date' => '2018-01-01',
606 'total_amount' => 50,
607 'financial_type_id' => $this->_financialTypeId,
608 'contribution_status_id' => 'Pending',
609 'tax_amount' => 15,
610 'line_items' => [
611 0 => [
612 'line_item' => [
613 '0' => [
614 'price_field_id' => 1,
615 'price_field_value_id' => 1,
616 'label' => 'Test 1',
617 'field_title' => 'Test 1',
618 'qty' => 1,
619 'unit_price' => 35,
620 'line_total' => 35,
621 'financial_type_id' => 1,
622 'entity_table' => 'civicrm_contribution',
623 'tax_amount' => 15,
624 ],
625 ],
626 ],
627 ],
628 ];
629
630 $order = $this->callAPISuccess('Order', 'create', $params);
631 $this->assertEquals(1, $order['count']);
632 }
633
634 }