Merge pull request #15790 from civicrm/5.20
[civicrm-core.git] / tests / phpunit / api / v3 / OrderTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2020 |
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 * @throws \CRM_Core_Exception
102 */
103 public function testGetOrderParticipant() {
104 $this->addOrder(FALSE, 100);
105 $contribution = $this->createPartiallyPaidParticipantOrder();
106
107 $params = [
108 'contribution_id' => $contribution['id'],
109 ];
110
111 $order = $this->callAPISuccess('Order', 'get', $params);
112
113 $this->assertCount(2, $order['values'][$contribution['id']]['line_items']);
114 $this->callAPISuccess('Contribution', 'Delete', [
115 'id' => $contribution['id'],
116 ]);
117 }
118
119 /**
120 * Function to assert db values.
121 */
122 public function checkPaymentResult($results, $expectedResult, $lineItems = NULL) {
123 foreach ($expectedResult[$results['id']] as $key => $value) {
124 $this->assertEquals($results['values'][$results['id']][$key], $value);
125 }
126
127 if ($lineItems) {
128 foreach ($lineItems as $key => $items) {
129 foreach ($items as $k => $item) {
130 $this->assertEquals($results['values'][$results['id']]['line_items'][$key][$k], $item);
131 }
132 }
133 }
134 }
135
136 /**
137 * Add order.
138 *
139 * @param bool $isPriceSet
140 * @param float $amount
141 * @param array $extraParams
142 *
143 * @return array
144 */
145 public function addOrder($isPriceSet, $amount = 300.00, $extraParams = []) {
146 $p = [
147 'contact_id' => $this->_individualId,
148 'receive_date' => '2010-01-20',
149 'total_amount' => $amount,
150 'financial_type_id' => $this->_financialTypeId,
151 'contribution_status_id' => 1,
152 ];
153
154 if ($isPriceSet) {
155 $priceFields = $this->createPriceSet();
156 foreach ($priceFields['values'] as $key => $priceField) {
157 $lineItems[1][$key] = [
158 'price_field_id' => $priceField['price_field_id'],
159 'price_field_value_id' => $priceField['id'],
160 'label' => $priceField['label'],
161 'field_title' => $priceField['label'],
162 'qty' => 1,
163 'unit_price' => $priceField['amount'],
164 'line_total' => $priceField['amount'],
165 'financial_type_id' => $priceField['financial_type_id'],
166 ];
167 }
168 $p['line_item'] = $lineItems;
169 }
170 $p = array_merge($extraParams, $p);
171 return $this->callAPISuccess('Contribution', 'create', $p);
172 }
173
174 /**
175 * Test create order api
176 */
177 public function testAddOrder() {
178 $order = $this->addOrder(FALSE, 100);
179 $params = [
180 'contribution_id' => $order['id'],
181 ];
182 $order = $this->callAPISuccess('order', 'get', $params);
183 $expectedResult = [
184 $order['id'] => [
185 'total_amount' => 100,
186 'contribution_id' => $order['id'],
187 'contribution_status' => 'Completed',
188 'net_amount' => 100,
189 ],
190 ];
191 $lineItems[] = [
192 'entity_table' => 'civicrm_contribution',
193 'entity_id' => $order['id'],
194 'contribution_id' => $order['id'],
195 'unit_price' => 100,
196 'line_total' => 100,
197 'financial_type_id' => 1,
198 ];
199 $this->checkPaymentResult($order, $expectedResult, $lineItems);
200 $this->callAPISuccess('Contribution', 'Delete', [
201 'id' => $order['id'],
202 ]);
203 }
204
205 /**
206 * Test create order api for membership
207 *
208 * @throws \CRM_Core_Exception
209 */
210 public function testAddOrderForMembership() {
211 $membershipType = $this->membershipTypeCreate();
212 $membershipType1 = $this->membershipTypeCreate();
213 $membershipType = $membershipTypes = [$membershipType, $membershipType1];
214 $p = [
215 'contact_id' => $this->_individualId,
216 'receive_date' => '2010-01-20',
217 'financial_type_id' => 'Event Fee',
218 'contribution_status_id' => 'Pending',
219 ];
220 $priceFields = $this->createPriceSet();
221 foreach ($priceFields['values'] as $key => $priceField) {
222 $lineItems[$key] = [
223 'price_field_id' => $priceField['price_field_id'],
224 'price_field_value_id' => $priceField['id'],
225 'label' => $priceField['label'],
226 'field_title' => $priceField['label'],
227 'qty' => 1,
228 'unit_price' => $priceField['amount'],
229 'line_total' => $priceField['amount'],
230 'financial_type_id' => $priceField['financial_type_id'],
231 'entity_table' => 'civicrm_membership',
232 'membership_type_id' => array_pop($membershipType),
233 ];
234 }
235 $p['line_items'][] = [
236 'line_item' => [array_pop($lineItems)],
237 'params' => [
238 'contact_id' => $this->_individualId,
239 'membership_type_id' => array_pop($membershipTypes),
240 'join_date' => '2006-01-21',
241 'start_date' => '2006-01-21',
242 'end_date' => '2006-12-21',
243 'source' => 'Payment',
244 'is_override' => 1,
245 ],
246 ];
247 $order = $this->callAPIAndDocument('order', 'create', $p, __FUNCTION__, __FILE__);
248 $params = [
249 'contribution_id' => $order['id'],
250 ];
251 $order = $this->callAPISuccess('order', 'get', $params);
252 $expectedResult = [
253 $order['id'] => [
254 'total_amount' => 200,
255 'contribution_id' => $order['id'],
256 'contribution_status' => 'Pending Label**',
257 'net_amount' => 200,
258 ],
259 ];
260 $this->checkPaymentResult($order, $expectedResult);
261 $this->callAPISuccessGetCount('MembershipPayment', $params, 1);
262 $this->callAPISuccess('Contribution', 'Delete', [
263 'id' => $order['id'],
264 ]);
265 $p['line_items'][] = [
266 'line_item' => [array_pop($lineItems)],
267 'params' => [
268 'contact_id' => $this->_individualId,
269 'membership_type_id' => array_pop($membershipTypes),
270 'join_date' => '2006-01-21',
271 'start_date' => '2006-01-21',
272 'end_date' => '2006-12-21',
273 'source' => 'Payment',
274 'is_override' => 1,
275 'status_id' => 'Pending',
276 ],
277 ];
278 $p['total_amount'] = 300;
279 $order = $this->callAPISuccess('order', 'create', $p);
280 $expectedResult = [
281 $order['id'] => [
282 'total_amount' => 300,
283 'contribution_status' => 'Pending Label**',
284 'net_amount' => 300,
285 ],
286 ];
287 $paymentMembership = [
288 'contribution_id' => $order['id'],
289 ];
290 $order = $this->callAPISuccess('order', 'get', $paymentMembership);
291 $this->checkPaymentResult($order, $expectedResult);
292 $this->callAPISuccessGetCount('MembershipPayment', $paymentMembership, 2);
293 $this->callAPISuccess('Contribution', 'Delete', [
294 'id' => $order['id'],
295 ]);
296 }
297
298 /**
299 * Test create order api for participant
300 *
301 * @throws \CRM_Core_Exception
302 */
303 public function testAddOrderForParticipant() {
304 $event = $this->eventCreate();
305 $this->_eventId = $event['id'];
306 $p = [
307 'contact_id' => $this->_individualId,
308 'receive_date' => '2010-01-20',
309 'financial_type_id' => $this->_financialTypeId,
310 'contribution_status_id' => 'Pending',
311 ];
312 $priceFields = $this->createPriceSet();
313 foreach ($priceFields['values'] as $key => $priceField) {
314 $lineItems[$key] = [
315 'price_field_id' => $priceField['price_field_id'],
316 'price_field_value_id' => $priceField['id'],
317 'label' => $priceField['label'],
318 'field_title' => $priceField['label'],
319 'qty' => 1,
320 'unit_price' => $priceField['amount'],
321 'line_total' => $priceField['amount'],
322 'financial_type_id' => $priceField['financial_type_id'],
323 'entity_table' => 'civicrm_participant',
324 ];
325 }
326 $p['line_items'][] = [
327 'line_item' => $lineItems,
328 'params' => [
329 'contact_id' => $this->_individualId,
330 'event_id' => $this->_eventId,
331 'role_id' => 1,
332 'register_date' => '2007-07-21 00:00:00',
333 'source' => 'Online Event Registration: API Testing',
334 ],
335 ];
336
337 $order = $this->callAPIAndDocument('order', 'create', $p, __FUNCTION__, __FILE__, 'Create order for participant', 'CreateOrderParticipant');
338 $params = ['contribution_id' => $order['id']];
339 $order = $this->callAPISuccess('order', 'get', $params);
340 $expectedResult = [
341 $order['id'] => [
342 'total_amount' => 300,
343 'contribution_id' => $order['id'],
344 'contribution_status' => 'Pending Label**',
345 'net_amount' => 300,
346 ],
347 ];
348 $this->checkPaymentResult($order, $expectedResult);
349 $paymentParticipant = $this->callAPISuccessGetSingle('ParticipantPayment', ['contribution_id' => $order['id']]);
350 $participant = $this->callAPISuccessGetSingle('Participant', ['participant_id' => $paymentParticipant['participant_id']]);
351 $this->assertEquals('Pending (incomplete transaction)', $participant['participant_status']);
352 $this->callAPISuccess('Contribution', 'Delete', [
353 'id' => $order['id'],
354 ]);
355
356 $p['line_items'][] = [
357 'line_item' => $lineItems,
358 'params' => [
359 'contact_id' => $this->individualCreate(),
360 'event_id' => $this->_eventId,
361 'role_id' => 1,
362 'register_date' => '2007-07-21 00:00:00',
363 'source' => 'Online Event Registration: API Testing',
364 ],
365 ];
366
367 $order = $this->callAPISuccess('order', 'create', $p);
368 $expectedResult = [
369 $order['id'] => [
370 'total_amount' => 600,
371 'contribution_status' => 'Pending Label**',
372 'net_amount' => 600,
373 ],
374 ];
375 $paymentParticipant = [
376 'contribution_id' => $order['id'],
377 ];
378 $order = $this->callAPISuccess('order', 'get', $paymentParticipant);
379 $this->checkPaymentResult($order, $expectedResult);
380 $this->callAPISuccessGetCount('ParticipantPayment', $paymentParticipant, 2);
381 $this->callAPISuccess('Contribution', 'Delete', [
382 'id' => $order['id'],
383 ]);
384 }
385
386 /**
387 * Test create order api with line items
388 */
389 public function testAddOrderWithLineItems() {
390 $order = $this->addOrder(TRUE);
391 $params = [
392 'contribution_id' => $order['id'],
393 ];
394 $order = $this->callAPISuccess('order', 'get', $params);
395 $expectedResult = [
396 $order['id'] => [
397 'total_amount' => 300,
398 'contribution_id' => $order['id'],
399 'contribution_status' => 'Completed',
400 'net_amount' => 300,
401 ],
402 ];
403 $items[] = [
404 'entity_table' => 'civicrm_contribution',
405 'entity_id' => $order['id'],
406 'contribution_id' => $order['id'],
407 'unit_price' => 100,
408 'line_total' => 100,
409 ];
410 $items[] = [
411 'entity_table' => 'civicrm_contribution',
412 'entity_id' => $order['id'],
413 'contribution_id' => $order['id'],
414 'unit_price' => 200,
415 'line_total' => 200,
416 ];
417 $this->checkPaymentResult($order, $expectedResult, $items);
418 $params = [
419 'entity_table' => 'civicrm_contribution',
420 'entity_id' => $order['id'],
421 ];
422 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
423 $this->assertEquals($eft['values'][$eft['id']]['amount'], 300);
424 $params = [
425 'entity_table' => 'civicrm_financial_item',
426 'financial_trxn_id' => $eft['values'][$eft['id']]['financial_trxn_id'],
427 ];
428 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
429 $amounts = [200, 100];
430 foreach ($eft['values'] as $value) {
431 $this->assertEquals($value['amount'], array_pop($amounts));
432 }
433 $this->callAPISuccess('Contribution', 'Delete', [
434 'id' => $order['id'],
435 ]);
436 }
437
438 /**
439 * Test delete order api
440 */
441 public function testDeleteOrder() {
442 $order = $this->addOrder(FALSE, 100);
443 $params = [
444 'contribution_id' => $order['id'],
445 ];
446 try {
447 $this->callAPISuccess('order', 'delete', $params);
448 $this->fail("Missed expected exception");
449 }
450 catch (Exception $expected) {
451 $this->callAPISuccess('Contribution', 'create', [
452 'contribution_id' => $order['id'],
453 'is_test' => TRUE,
454 ]);
455 $this->callAPIAndDocument('order', 'delete', $params, __FUNCTION__, __FILE__);
456 $order = $this->callAPISuccess('order', 'get', $params);
457 $this->assertEquals(0, $order['count']);
458 }
459 }
460
461 /**
462 * Test cancel order api
463 */
464 public function testCancelOrder() {
465 $contribution = $this->addOrder(FALSE, 100);
466 $params = [
467 'contribution_id' => $contribution['id'],
468 ];
469 $this->callAPIAndDocument('order', 'cancel', $params, __FUNCTION__, __FILE__);
470 $order = $this->callAPISuccess('Order', 'get', $params);
471 $expectedResult = [
472 $contribution['id'] => [
473 'total_amount' => 100,
474 'contribution_id' => $contribution['id'],
475 'contribution_status' => 'Cancelled',
476 'net_amount' => 100,
477 ],
478 ];
479 $this->checkPaymentResult($order, $expectedResult);
480 $this->callAPISuccess('Contribution', 'Delete', [
481 'id' => $contribution['id'],
482 ]);
483 }
484
485 /**
486 * Test cancel order api
487 */
488 public function testCancelWithParticipant() {
489 $event = $this->eventCreate();
490 $this->_eventId = $event['id'];
491 $eventParams = [
492 'id' => $this->_eventId,
493 'financial_type_id' => 4,
494 'is_monetary' => 1,
495 ];
496 $this->callAPISuccess('event', 'create', $eventParams);
497 $participantParams = [
498 'financial_type_id' => 4,
499 'event_id' => $this->_eventId,
500 'role_id' => 1,
501 'status_id' => 1,
502 'fee_currency' => 'USD',
503 'contact_id' => $this->_individualId,
504 ];
505 $participant = $this->callAPISuccess('Participant', 'create', $participantParams);
506 $extraParams = [
507 'contribution_mode' => 'participant',
508 'participant_id' => $participant['id'],
509 ];
510 $contribution = $this->addOrder(TRUE, 100, $extraParams);
511 $paymentParticipant = [
512 'participant_id' => $participant['id'],
513 'contribution_id' => $contribution['id'],
514 ];
515 $this->callAPISuccess('ParticipantPayment', 'create', $paymentParticipant);
516 $params = [
517 'contribution_id' => $contribution['id'],
518 ];
519 $this->callAPISuccess('order', 'cancel', $params);
520 $order = $this->callAPISuccess('Order', 'get', $params);
521 $expectedResult = [
522 $contribution['id'] => [
523 'total_amount' => 100,
524 'contribution_id' => $contribution['id'],
525 'contribution_status' => 'Cancelled',
526 'net_amount' => 100,
527 ],
528 ];
529 $this->checkPaymentResult($order, $expectedResult);
530 $participantPayment = $this->callAPISuccess('ParticipantPayment', 'getsingle', $params);
531 $participant = $this->callAPISuccess('participant', 'get', ['id' => $participantPayment['participant_id']]);
532 $this->assertEquals($participant['values'][$participant['id']]['participant_status'], 'Cancelled');
533 $this->callAPISuccess('Contribution', 'Delete', [
534 'id' => $contribution['id'],
535 ]);
536 }
537
538 /**
539 * Test an exception is thrown if line items do not add up to total_amount, no tax.
540 */
541 public function testCreateOrderIfTotalAmountDoesNotMatchLineItemsAmountsIfNoTaxSupplied() {
542 $params = [
543 'contact_id' => $this->_individualId,
544 'receive_date' => '2018-01-01',
545 'total_amount' => 50,
546 'financial_type_id' => $this->_financialTypeId,
547 'contribution_status_id' => 'Pending',
548 'line_items' => [
549 0 => [
550 'line_item' => [
551 '0' => [
552 'price_field_id' => 1,
553 'price_field_value_id' => 1,
554 'label' => 'Test 1',
555 'field_title' => 'Test 1',
556 'qty' => 1,
557 'unit_price' => 40,
558 'line_total' => 40,
559 'financial_type_id' => 1,
560 'entity_table' => 'civicrm_contribution',
561 ],
562 ],
563 ],
564 ],
565 ];
566
567 $this->callAPIFailure('Order', 'create', $params, 'Line item total doesn\'t match with total amount');
568 }
569
570 /**
571 * Test an exception is thrown if line items do not add up to total_amount, with tax.
572 */
573 public function testCreateOrderIfTotalAmountDoesNotMatchLineItemsAmountsIfTaxSupplied() {
574 $params = [
575 'contact_id' => $this->_individualId,
576 'receive_date' => '2018-01-01',
577 'total_amount' => 50,
578 'financial_type_id' => $this->_financialTypeId,
579 'contribution_status_id' => 'Pending',
580 'tax_amount' => 15,
581 'line_items' => [
582 0 => [
583 'line_item' => [
584 '0' => [
585 'price_field_id' => 1,
586 'price_field_value_id' => 1,
587 'label' => 'Test 1',
588 'field_title' => 'Test 1',
589 'qty' => 1,
590 'unit_price' => 30,
591 'line_total' => 30,
592 'financial_type_id' => 1,
593 'entity_table' => 'civicrm_contribution',
594 'tax_amount' => 15,
595 ],
596 ],
597 ],
598 ],
599 ];
600
601 $this->callAPIFailure('Order', 'create', $params, 'Line item total doesn\'t match with total amount.');
602 }
603
604 public function testCreateOrderIfTotalAmountDoesMatchLineItemsAmountsAndTaxSupplied() {
605 $params = [
606 'contact_id' => $this->_individualId,
607 'receive_date' => '2018-01-01',
608 'total_amount' => 50,
609 'financial_type_id' => $this->_financialTypeId,
610 'contribution_status_id' => 'Pending',
611 'tax_amount' => 15,
612 'line_items' => [
613 0 => [
614 'line_item' => [
615 '0' => [
616 'price_field_id' => 1,
617 'price_field_value_id' => 1,
618 'label' => 'Test 1',
619 'field_title' => 'Test 1',
620 'qty' => 1,
621 'unit_price' => 35,
622 'line_total' => 35,
623 'financial_type_id' => 1,
624 'entity_table' => 'civicrm_contribution',
625 'tax_amount' => 15,
626 ],
627 ],
628 ],
629 ],
630 ];
631
632 $order = $this->callAPISuccess('Order', 'create', $params);
633 $this->assertEquals(1, $order['count']);
634 }
635
636 /**
637 * Test that a contribution can be added in pending mode with a chained payment.
638 *
639 * We have just deprecated creating an order with a status other than pending. It makes
640 * sense to support adding a payment straight away by chaining.
641 *
642 * @throws \CRM_Core_Exception
643 */
644 public function testCreateWithChainedPayment() {
645 $contributionID = $this->callAPISuccess('Order', 'create', ['contact_id' => $this->_individualId, 'total_amount' => 5, 'financial_type_id' => 2, 'contribution_status_id' => 'Pending', 'api.Payment.create' => ['total_amount' => 5]])['id'];
646 $this->assertEquals('Completed', $this->callAPISuccessGetValue('Contribution', ['id' => $contributionID, 'return' => 'contribution_status']));
647 }
648
649 }