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