Merge pull request #9560 from aydun/CRM-19754
[civicrm-core.git] / tests / phpunit / api / v3 / TaxContributionPageTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2017 |
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 * Class api_v3_TaxContributionPageTest
30 * @group headless
31 */
32 class api_v3_TaxContributionPageTest extends CiviUnitTestCase {
33 protected $_apiversion = 3;
34 protected $params;
35 protected $financialtypeID;
36 protected $financialAccountId;
37 protected $_entity = 'contribution_page';
38 protected $_priceSetParams = array();
39 protected $_paymentProcessorType;
40 protected $payParams = array();
41 protected $paymentProceParams = array();
42 protected $settingValue = array();
43 protected $setInvoiceSettings;
44 protected $_ids = array();
45 protected $_individualId;
46 protected $financialAccHalftax;
47 protected $financialtypeHalftax;
48 protected $financialRelationHalftax;
49 protected $halfFinancialAccId;
50 protected $halfFinancialTypeId;
51 public $DBResetRequired = TRUE;
52
53 public function setUp() {
54 parent::setUp();
55 $this->_individualId = $this->individualCreate();
56 $this->_orgId = $this->organizationCreate(NULL);
57
58 $this->params = array(
59 'title' => "Test Contribution Page" . substr(sha1(rand()), 0, 7),
60 'financial_type_id' => 1,
61 'payment_processor' => 1,
62 'currency' => 'NZD',
63 'goal_amount' => 350,
64 'is_pay_later' => 1,
65 'pay_later_text' => 'I will pay later',
66 'pay_later_receipt' => "I will pay later",
67 'is_monetary' => TRUE,
68 'is_billing_required' => TRUE,
69 );
70
71 $this->_priceSetParams = array(
72 'name' => 'tax_contribution' . substr(sha1(rand()), 0, 7),
73 'title' => 'contributiontax' . substr(sha1(rand()), 0, 7),
74 'is_active' => 1,
75 'help_pre' => "Where does your goat sleep",
76 'help_post' => "thank you for your time",
77 'extends' => 2,
78 'financial_type_id' => 3,
79 'is_quick_config' => 0,
80 'is_reserved' => 0,
81 );
82 // Financial Account with 20% tax rate
83 $financialAccountSetparams = array(
84 #[domain_id] =>
85 'name' => 'vat full taxrate account' . substr(sha1(rand()), 0, 7),
86 'contact_id' => $this->_orgId,
87 'financial_account_type_id' => 2,
88 'is_tax' => 1,
89 'tax_rate' => 20.00,
90 'is_reserved' => 0,
91 'is_active' => 1,
92 'is_default' => 0,
93 );
94
95 $financialAccount = $this->callAPISuccess('financial_account', 'create', $financialAccountSetparams);
96 $this->financialAccountId = $financialAccount['id'];
97
98 // Financial type having 'Sales Tax Account is' with liability financail account
99 $financialType = array(
100 'name' => 'grassvariety1' . substr(sha1(rand()), 0, 7),
101 'is_reserved' => 0,
102 'is_active' => 1,
103 );
104 $priceField = $this->callAPISuccess('financial_type', 'create', $financialType);
105 $this->financialtypeID = $priceField['id'];
106 $financialRelationParams = array(
107 'entity_table' => 'civicrm_financial_type',
108 'entity_id' => $this->financialtypeID,
109 'account_relationship' => 10,
110 'financial_account_id' => $this->financialAccountId,
111 );
112 $financialRelation = CRM_Financial_BAO_FinancialTypeAccount::add($financialRelationParams);
113
114 // Financial type with 5% tax rate
115 $financialAccHalftax = array(
116 'name' => 'vat half taxrate account' . substr(sha1(rand()), 0, 7),
117 'contact_id' => $this->_orgId,
118 'financial_account_type_id' => 2,
119 'is_tax' => 1,
120 'tax_rate' => 5.00,
121 'is_reserved' => 0,
122 'is_active' => 1,
123 'is_default' => 0,
124 );
125 $halfFinancialAccount = CRM_Financial_BAO_FinancialAccount::add($financialAccHalftax);
126 $this->halfFinancialAccId = $halfFinancialAccount->id;
127 $halfFinancialtypeHalftax = array(
128 'name' => 'grassvariety2' . substr(sha1(rand()), 0, 7),
129 'is_reserved' => 0,
130 'is_active' => 1,
131 );
132
133 $halfFinancialType = CRM_Financial_BAO_FinancialType::add($halfFinancialtypeHalftax);
134 $this->halfFinancialTypeId = $halfFinancialType->id;
135 $financialRelationHalftax = array(
136 'entity_table' => 'civicrm_financial_type',
137 'entity_id' => $this->halfFinancialTypeId,
138 'account_relationship' => 10,
139 'financial_account_id' => $this->halfFinancialAccId,
140 );
141
142 $halfFinancialRelation = CRM_Financial_BAO_FinancialTypeAccount::add($financialRelationHalftax);
143
144 // Enable component contribute setting
145 $setInvoiceSettings = $this->enableTaxAndInvoicing();
146
147 // Payment Processor
148 $paymentProceParams = array(
149 'domain_id' => 1,
150 'name' => 'dummy' . substr(sha1(rand()), 0, 7),
151 'payment_processor_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Financial_BAO_PaymentProcessor', 'payment_processor_type_id', 'Dummy'),
152 'financial_account_id' => 12,
153 'is_active' => 1,
154 'is_default' => 1,
155 'user_name' => 'dummy',
156 'url_site' => 'http://dummy.com',
157 'url_recur' => 'http://dummyrecur.com',
158 'class_name' => 'Payment_Dummy',
159 'billing_mode' => 1,
160 'is_recur' => 1,
161 'payment_type' => 1,
162 );
163 $result = $this->callAPISuccess('payment_processor', 'create', $paymentProceParams);
164 $this->_ids['paymentProcessID'] = $result['id'];
165 require_once 'api/v3/examples/PaymentProcessor/Create.php';
166 $this->assertAPISuccess($result);
167 }
168
169 public function tearDown() {
170 $this->quickCleanup(array(
171 'civicrm_contribution',
172 'civicrm_contribution_soft',
173 'civicrm_event',
174 'civicrm_contribution_page',
175 'civicrm_participant',
176 'civicrm_participant_payment',
177 'civicrm_line_item',
178 'civicrm_financial_trxn',
179 'civicrm_financial_item',
180 'civicrm_entity_financial_trxn',
181 'civicrm_contact',
182 'civicrm_membership',
183 'civicrm_membership_payment',
184 'civicrm_payment_processor',
185 ));
186 CRM_Core_PseudoConstant::flush('taxRates');
187 }
188
189 public function setUpContributionPage() {
190 $contributionPageResult = $this->callAPISuccess($this->_entity, 'create', $this->params);
191 if (empty($this->_ids['price_set'])) {
192 $priceSet = $this->callAPISuccess('price_set', 'create', $this->_priceSetParams);
193 $this->_ids['price_set'][] = $priceSet['id'];
194 }
195 $priceSetID = $this->_price = reset($this->_ids['price_set']);
196 CRM_Price_BAO_PriceSet::addTo('civicrm_contribution_page', $contributionPageResult['id'], $priceSetID);
197
198 if (empty($this->_ids['price_field'])) {
199 $priceField = $this->callAPISuccess('price_field', 'create', array(
200 'price_set_id' => $priceSetID,
201 'label' => 'Goat Breed',
202 'html_type' => 'Radio',
203 ));
204 $this->_ids['price_field'] = array($priceField['id']);
205 }
206 if (empty($this->_ids['price_field_value'])) {
207 $this->callAPISuccess('price_field_value', 'create', array(
208 'price_set_id' => $priceSetID,
209 'price_field_id' => $priceField['id'],
210 'label' => 'Long Haired Goat',
211 'amount' => 100,
212 'financial_type_id' => $this->financialtypeID,
213 ));
214 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
215 'price_set_id' => $priceSetID,
216 'price_field_id' => $priceField['id'],
217 'label' => 'Shoe-eating Goat',
218 'amount' => 300,
219 'financial_type_id' => $this->halfFinancialTypeId,
220 ));
221 $this->_ids['price_field_value'] = array($priceFieldValue['id']);
222 }
223 $this->_ids['contribution_page'] = $contributionPageResult['id'];
224 }
225
226 /**
227 * Online and offline contrbution from above created contrbution page.
228 */
229 public function testCreateContributionOnline() {
230 $this->setUpContributionPage();
231 $params = array(
232 'contact_id' => $this->_individualId,
233 'receive_date' => '20120511',
234 'total_amount' => 100.00,
235 'financial_type_id' => $this->financialtypeID,
236 'contribution_page_id' => $this->_ids['contribution_page'],
237 'payment_processor' => $this->_ids['paymentProcessID'],
238 'trxn_id' => 12345,
239 'invoice_id' => 67890,
240 'source' => 'SSF',
241 'contribution_status_id' => 1,
242 );
243
244 $contribution = $this->callAPIAndDocument('contribution', 'create', $params, __FUNCTION__, __FILE__);
245 $this->_ids['contributionId'] = $contribution['id'];
246 $this->assertEquals($contribution['values'][$contribution['id']]['contact_id'], $this->_individualId);
247 $this->assertEquals($contribution['values'][$contribution['id']]['total_amount'], 120.00);
248 $this->assertEquals($contribution['values'][$contribution['id']]['financial_type_id'], $this->financialtypeID);
249 $this->assertEquals($contribution['values'][$contribution['id']]['trxn_id'], 12345);
250 $this->assertEquals($contribution['values'][$contribution['id']]['invoice_id'], 67890);
251 $this->assertEquals($contribution['values'][$contribution['id']]['source'], 'SSF');
252 $this->assertEquals($contribution['values'][$contribution['id']]['tax_amount'], 20);
253 $this->assertEquals($contribution['values'][$contribution['id']]['contribution_status_id'], 1);
254 $this->_checkFinancialRecords($contribution, 'online');
255 }
256
257 public function testCreateContributionChainedLineItems() {
258 $this->setUpContributionPage();
259 $params = array(
260 'contact_id' => $this->_individualId,
261 'receive_date' => '20120511',
262 'total_amount' => 400.00,
263 'financial_type_id' => $this->financialtypeID,
264 'trxn_id' => 12345,
265 'invoice_id' => 67890,
266 'source' => 'SSF',
267 'contribution_status_id' => 1,
268 'skipLineItem' => 1,
269 'api.line_item.create' => array(
270 array(
271 'price_field_id' => $this->_ids['price_field'],
272 'qty' => 1,
273 'line_total' => '100',
274 'unit_price' => '100',
275 'financial_type_id' => $this->financialtypeID,
276 ),
277 array(
278 'price_field_id' => $this->_ids['price_field'],
279 'qty' => 1,
280 'line_total' => '300',
281 'unit_price' => '300',
282 'financial_type_id' => $this->halfFinancialTypeId,
283 ),
284 ),
285 );
286
287 $description = "Create Contribution with Nested Line Items.";
288 $subfile = "CreateWithNestedLineItems";
289 $contribution = $this->callAPIAndDocument('contribution', 'create', $params, __FUNCTION__, __FILE__, $description, $subfile);
290
291 $lineItems = $this->callAPISuccess('line_item', 'get', array(
292 'entity_id' => $contribution['id'],
293 'contribution_id' => $contribution['id'],
294 'entity_table' => 'civicrm_contribution',
295 'sequential' => 1,
296 ));
297 $this->assertEquals(2, $lineItems['count']);
298 }
299
300 public function testCreateContributionPayLaterOnline() {
301 $this->setUpContributionPage();
302 $params = array(
303 'contact_id' => $this->_individualId,
304 'receive_date' => '20120511',
305 'total_amount' => 100.00,
306 'financial_type_id' => $this->financialtypeID,
307 'contribution_page_id' => $this->_ids['contribution_page'],
308 'trxn_id' => 12345,
309 'is_pay_later' => 1,
310 'invoice_id' => 67890,
311 'source' => 'SSF',
312 'contribution_status_id' => 2,
313 );
314 $contribution = $this->callAPIAndDocument('contribution', 'create', $params, __FUNCTION__, __FILE__);
315 $this->assertEquals($contribution['values'][$contribution['id']]['contact_id'], $this->_individualId);
316 $this->assertEquals($contribution['values'][$contribution['id']]['total_amount'], 120.00);
317 $this->assertEquals($contribution['values'][$contribution['id']]['financial_type_id'], $this->financialtypeID);
318 $this->assertEquals($contribution['values'][$contribution['id']]['trxn_id'], 12345);
319 $this->assertEquals($contribution['values'][$contribution['id']]['invoice_id'], 67890);
320 $this->assertEquals($contribution['values'][$contribution['id']]['source'], 'SSF');
321 $this->assertEquals($contribution['values'][$contribution['id']]['tax_amount'], 20);
322 $this->assertEquals($contribution['values'][$contribution['id']]['contribution_status_id'], 2);
323 $this->_checkFinancialRecords($contribution, 'payLater');
324 }
325
326 public function testCreateContributionPendingOnline() {
327 $this->setUpContributionPage();
328 $params = array(
329 'contact_id' => $this->_individualId,
330 'receive_date' => '20120511',
331 'total_amount' => 100.00,
332 'financial_type_id' => $this->financialtypeID,
333 'contribution_page_id' => $this->_ids['contribution_page'],
334 'trxn_id' => 12345,
335 'invoice_id' => 67890,
336 'source' => 'SSF',
337 'contribution_status_id' => 2,
338 );
339
340 $contribution = $this->callAPIAndDocument('contribution', 'create', $params, __FUNCTION__, __FILE__);
341 $this->assertEquals($contribution['values'][$contribution['id']]['contact_id'], $this->_individualId);
342 $this->assertEquals($contribution['values'][$contribution['id']]['total_amount'], 120.00);
343 $this->assertEquals($contribution['values'][$contribution['id']]['financial_type_id'], $this->financialtypeID);
344 $this->assertEquals($contribution['values'][$contribution['id']]['trxn_id'], 12345);
345 $this->assertEquals($contribution['values'][$contribution['id']]['invoice_id'], 67890);
346 $this->assertEquals($contribution['values'][$contribution['id']]['source'], 'SSF');
347 $this->assertEquals($contribution['values'][$contribution['id']]['tax_amount'], 20);
348 $this->assertEquals($contribution['values'][$contribution['id']]['contribution_status_id'], 2);
349 $this->_checkFinancialRecords($contribution, 'pending');
350 }
351
352 /**
353 * Update a contribution.
354 *
355 * Function tests that line items, financial records are updated when contribution amount is changed
356 */
357 public function testCreateUpdateContributionChangeTotal() {
358 $this->setUpContributionPage();
359 $this->contributionParams = array(
360 'contact_id' => $this->_individualId,
361 'receive_date' => '20120511',
362 'total_amount' => 100.00,
363 'financial_type_id' => $this->financialtypeID,
364 'source' => 'SSF',
365 'contribution_status_id' => 1,
366 );
367 $contribution = $this->callAPISuccess('contribution', 'create', $this->contributionParams);
368 $lineItems = $this->callAPISuccess('line_item', 'getvalue', array(
369 'entity_id' => $contribution['id'],
370 'entity_table' => 'civicrm_contribution',
371 'sequential' => 1,
372 'return' => 'line_total',
373 ));
374 $this->assertEquals('100.00', $lineItems);
375 $trxnAmount = $this->_getFinancialTrxnAmount($contribution['id']);
376 $this->assertEquals('120.00', $trxnAmount);
377 $newParams = array(
378 'id' => $contribution['id'],
379 'financial_type_id' => 1, // without tax rate i.e Donation
380 'total_amount' => '300',
381 );
382 $contribution = $this->callAPISuccess('contribution', 'create', $newParams);
383
384 $lineItems = $this->callAPISuccess('line_item', 'getvalue', array(
385 'entity_id' => $contribution['id'],
386 'entity_table' => 'civicrm_contribution',
387 'sequential' => 1,
388 'return' => 'line_total',
389 ));
390
391 $this->assertEquals('300.00', $lineItems);
392 $trxnAmount = $this->_getFinancialTrxnAmount($contribution['id']);
393 $fitemAmount = $this->_getFinancialItemAmount($contribution['id']);
394 $this->assertEquals('300.00', $trxnAmount);
395 $this->assertEquals('300.00', $fitemAmount);
396 }
397
398 /**
399 * @param int $contId
400 *
401 * @return null|string
402 */
403 public function _getFinancialTrxnAmount($contId) {
404 $query = "SELECT
405 SUM( ft.total_amount ) AS total
406 FROM civicrm_financial_trxn AS ft
407 LEFT JOIN civicrm_entity_financial_trxn AS ceft ON ft.id = ceft.financial_trxn_id
408 WHERE ceft.entity_table = 'civicrm_contribution'
409 AND ceft.entity_id = {$contId}";
410 $result = CRM_Core_DAO::singleValueQuery($query);
411 return $result;
412 }
413
414 /**
415 * @param int $contId
416 *
417 * @return null|string
418 */
419 public function _getFinancialItemAmount($contId) {
420 $lineItem = key(CRM_Price_BAO_LineItem::getLineItems($contId, 'contribution'));
421 $query = "SELECT
422 SUM(amount)
423 FROM civicrm_financial_item
424 WHERE entity_table = 'civicrm_line_item'
425 AND entity_id = {$lineItem}";
426 $result = CRM_Core_DAO::singleValueQuery($query);
427 return $result;
428 }
429
430 /**
431 * @param array $params
432 * @param $context
433 */
434 public function _checkFinancialRecords($params, $context) {
435 $entityParams = array(
436 'entity_id' => $params['id'],
437 'entity_table' => 'civicrm_contribution',
438 );
439 if ($context == 'pending') {
440 $trxn = CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams);
441 $this->assertNull($trxn, 'No Trxn to be created until IPN callback');
442 return;
443 }
444 $trxn = current(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams));
445 $trxnParams = array(
446 'id' => $trxn['financial_trxn_id'],
447 );
448 if ($context != 'online' && $context != 'payLater') {
449 $compareParams = array(
450 'to_financial_account_id' => 6,
451 'total_amount' => 120,
452 'status_id' => 1,
453 );
454 }
455 if ($context == 'online') {
456 $compareParams = array(
457 'to_financial_account_id' => 12,
458 'total_amount' => 120,
459 'status_id' => 1,
460 );
461 }
462 elseif ($context == 'payLater') {
463 $compareParams = array(
464 'to_financial_account_id' => 7,
465 'total_amount' => 120,
466 'status_id' => 2,
467 );
468 }
469 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
470 $entityParams = array(
471 'financial_trxn_id' => $trxn['financial_trxn_id'],
472 'entity_table' => 'civicrm_financial_item',
473 );
474 $entityTrxn = current(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams));
475 $fitemParams = array(
476 'id' => $entityTrxn['entity_id'],
477 );
478 $compareParams = array(
479 'amount' => 100,
480 'status_id' => 1,
481 'financial_account_id' => $this->_getFinancialAccountId($this->financialtypeID),
482 );
483 if ($context == 'payLater') {
484 $compareParams = array(
485 'amount' => 100,
486 'status_id' => 3,
487 'financial_account_id' => $this->_getFinancialAccountId($this->financialtypeID),
488 );
489 }
490 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
491 }
492
493 /**
494 * @param int $financialTypeId
495 * @return int
496 */
497 public function _getFinancialAccountId($financialTypeId) {
498 $accountRel = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Income Account is' "));
499
500 $searchParams = array(
501 'entity_table' => 'civicrm_financial_type',
502 'entity_id' => $financialTypeId,
503 'account_relationship' => $accountRel,
504 );
505
506 $result = array();
507 CRM_Financial_BAO_FinancialTypeAccount::retrieve($searchParams, $result);
508 return CRM_Utils_Array::value('financial_account_id', $result);
509 }
510
511 /**
512 * Test deleting a contribution.
513 *
514 * (It is unclear why this is in this class - it seems like maybe it doesn't test anything not
515 * on the contribution test class & might be copy and paste....).
516 */
517 public function testDeleteContribution() {
518 $contributionID = $this->contributionCreate(array(
519 'contact_id' => $this->_individualId,
520 'trxn_id' => 12389,
521 'financial_type_id' => $this->financialtypeID,
522 'invoice_id' => 'dfsdf',
523 ));
524 $this->callAPISuccess('contribution', 'delete', array('id' => $contributionID));
525 }
526
527 }