Merge pull request #15784 from civicrm/5.20
[civicrm-core.git] / CRM / Financial / BAO / Payment.php
CommitLineData
0c9b306a 1<?php
2/*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
f299f7db 6 | Copyright CiviCRM LLC (c) 2004-2020 |
0c9b306a 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 *
30 * @package CRM
f299f7db 31 * @copyright CiviCRM LLC (c) 2004-2020
0c9b306a 32 */
33
34/**
35 * This class contains payment related functions.
36 */
37class CRM_Financial_BAO_Payment {
38
39 /**
40 * Function to process additional payment for partial and refund contributions.
41 *
42 * This function is called via API payment.create function. All forms that add payments
43 * should use this.
44 *
45 * @param array $params
46 * - contribution_id
47 * - total_amount
48 * - line_item
49 *
50 * @return \CRM_Financial_DAO_FinancialTrxn
51 *
0c9b306a 52 * @throws \CRM_Core_Exception
6c434a1d 53 * @throws \CiviCRM_API3_Exception
0c9b306a 54 */
55 public static function create($params) {
56 $contribution = civicrm_api3('Contribution', 'getsingle', ['id' => $params['contribution_id']]);
57 $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus($contribution['contribution_status_id'], 'name');
7585038a 58 $isPaymentCompletesContribution = self::isPaymentCompletesContribution($params['contribution_id'], $params['total_amount']);
f5a468db 59 $lineItems = self::getPayableLineItems($params);
7585038a 60
4c68d684 61 $whiteList = ['check_number', 'payment_processor_id', 'fee_amount', 'total_amount', 'contribution_id', 'net_amount', 'card_type_id', 'pan_truncation', 'trxn_result_code', 'payment_instrument_id', 'trxn_id', 'trxn_date'];
a494d7a3 62 $paymentTrxnParams = array_intersect_key($params, array_fill_keys($whiteList, 1));
63 $paymentTrxnParams['is_payment'] = 1;
6cc6cb8c 64
a494d7a3 65 if (isset($paymentTrxnParams['payment_processor_id']) && empty($paymentTrxnParams['payment_processor_id'])) {
66 // Don't pass 0 - ie the Pay Later processor as it is a pseudo-processor.
67 unset($paymentTrxnParams['payment_processor_id']);
68 }
69 if (empty($paymentTrxnParams['payment_instrument_id'])) {
70 if (!empty($params['payment_processor_id'])) {
71 $paymentTrxnParams['payment_instrument_id'] = civicrm_api3('PaymentProcessor', 'getvalue', ['return' => 'payment_instrument_id', 'id' => $paymentTrxnParams['payment_processor_id']]);
72 }
73 else {
74 // Fall back on the payment instrument already used - should we deprecate this?
75 $paymentTrxnParams['payment_instrument_id'] = $contribution['payment_instrument_id'];
76 }
77 }
78 if (empty($paymentTrxnParams['trxn_id']) && !empty($paymentTrxnParams['contribution_trxn_id'])) {
79 CRM_Core_Error::deprecatedFunctionWarning('contribution_trxn_id is deprecated - use trxn_id');
80 $paymentTrxnParams['trxn_id'] = $paymentTrxnParams['contribution_trxn_id'];
81 }
82
6ac768e7 83 $paymentTrxnParams['currency'] = $contribution['currency'];
84
85 $accountsReceivableAccount = CRM_Financial_BAO_FinancialAccount::getFinancialAccountForFinancialTypeByRelationship($contribution['financial_type_id'], 'Accounts Receivable Account is');
86 $paymentTrxnParams['to_financial_account_id'] = CRM_Contribute_BAO_Contribution::getToFinancialAccount($contribution, $params);
87 $paymentTrxnParams['from_financial_account_id'] = $accountsReceivableAccount;
88
362f37fc 89 if ($params['total_amount'] > 0) {
f55dedff 90 $paymentTrxnParams['status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_FinancialTrxn', 'status_id', 'Completed');
6ac768e7 91 }
92 elseif ($params['total_amount'] < 0) {
93 $paymentTrxnParams['status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Refunded');
94 }
74e4c3c2 95
6ac768e7 96 $trxn = CRM_Core_BAO_FinancialTrxn::create($paymentTrxnParams);
b40da328 97
6ac768e7 98 if ($params['total_amount'] < 0 && !empty($params['cancelled_payment_id'])) {
99 self::reverseAllocationsFromPreviousPayment($params, $trxn->id);
100 return $trxn;
0c9b306a 101 }
6ac768e7 102 list($ftIds, $taxItems) = CRM_Contribute_BAO_Contribution::getLastFinancialItemIds($params['contribution_id']);
103
104 foreach ($lineItems as $key => $value) {
32ecba8e 105 if ($value['allocation'] === (float) 0) {
6ac768e7 106 continue;
107 }
cc9f2882 108
109 if (!empty($ftIds[$value['price_field_value_id']])) {
110 $financialItemID = $ftIds[$value['price_field_value_id']];
111 }
112 else {
113 $financialItemID = self::getNewFinancialItemID($value, $params['trxn_date'], $contribution['contact_id'], $paymentTrxnParams['currency']);
114 }
115
6ac768e7 116 $eftParams = [
117 'entity_table' => 'civicrm_financial_item',
118 'financial_trxn_id' => $trxn->id,
cc9f2882 119 'entity_id' => $financialItemID,
6ac768e7 120 'amount' => $value['allocation'],
121 ];
122
123 civicrm_api3('EntityFinancialTrxn', 'create', $eftParams);
124
125 if (array_key_exists($value['price_field_value_id'], $taxItems)) {
126 // @todo - this is expected to be broken - it should be fixed to
127 // a) have the getPayableLineItems add the amount to allocate for tax
128 // b) call EntityFinancialTrxn directly - per above.
129 // - see https://github.com/civicrm/civicrm-core/pull/14763
130 $entityParams = [
131 'contribution_total_amount' => $contribution['total_amount'],
132 'trxn_total_amount' => $params['total_amount'],
133 'trxn_id' => $trxn->id,
134 'line_item_amount' => $taxItems[$value['price_field_value_id']]['amount'],
135 ];
136 $eftParams['entity_id'] = $taxItems[$value['price_field_value_id']]['financial_item_id'];
137 CRM_Contribute_BAO_Contribution::createProportionalEntry($entityParams, $eftParams);
df29ccff 138 }
2561fc11 139 }
db77f7d3 140
5f5ed53a 141 if ($isPaymentCompletesContribution) {
6ac768e7 142 if ($contributionStatus === 'Pending refund') {
6c434a1d 143 // Ideally we could still call completetransaction as non-payment related actions should
144 // be outside this class. However, for now we just update the contribution here.
145 // Unit test cover in CRM_Event_BAO_AdditionalPaymentTest::testTransactionInfo.
146 civicrm_api3('Contribution', 'create',
147 [
148 'id' => $contribution['id'],
149 'contribution_status_id' => 'Completed',
150 ]
151 );
152 }
153 else {
362f37fc 154 civicrm_api3('Contribution', 'completetransaction', [
155 'id' => $contribution['id'],
156 'is_post_payment_create' => TRUE,
bd981689 157 'is_email_receipt' => $params['is_send_contribution_notification'],
362f37fc 158 ]);
6c434a1d 159 // Get the trxn
160 $trxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($contribution['id'], 'DESC');
161 $ftParams = ['id' => $trxnId['financialTrxnId']];
f0da41da 162 $trxn = CRM_Core_BAO_FinancialTrxn::retrieve($ftParams);
6c434a1d 163 }
5f5ed53a 164 }
6ac768e7 165 elseif ($contributionStatus === 'Pending' && $params['total_amount'] > 0) {
16b0233c 166 self::updateContributionStatus($contribution['id'], 'Partially Paid');
5f5ed53a 167 }
8d54448e 168 CRM_Contribute_BAO_Contribution::recordPaymentActivity($params['contribution_id'], CRM_Utils_Array::value('participant_id', $params), $params['total_amount'], $trxn->currency, $trxn->trxn_date);
0c9b306a 169 return $trxn;
170 }
171
a79d2ec2 172 /**
173 * Send an email confirming a payment that has been received.
174 *
175 * @param array $params
176 *
177 * @return array
f3e6da5e 178 *
179 * @throws \CiviCRM_API3_Exception
a79d2ec2 180 */
181 public static function sendConfirmation($params) {
182
183 $entities = self::loadRelatedEntities($params['id']);
be2fb01f 184 $sendTemplateParams = [
a79d2ec2 185 'groupName' => 'msg_tpl_workflow_contribution',
186 'valueName' => 'payment_or_refund_notification',
187 'PDFFilename' => ts('notification') . '.pdf',
188 'contactId' => $entities['contact']['id'],
189 'toName' => $entities['contact']['display_name'],
190 'toEmail' => $entities['contact']['email'],
191 'tplParams' => self::getConfirmationTemplateParameters($entities),
be2fb01f 192 ];
44a2f017 193 if (!empty($params['from']) && !empty($params['check_permissions'])) {
194 // Filter from against permitted emails.
195 $validEmails = self::getValidFromEmailsForPayment($entities['event']['id'] ?? NULL);
196 if (!isset($validEmails[$params['from']])) {
197 // Ignore unpermitted parameter.
198 unset($params['from']);
199 }
200 }
201 $sendTemplateParams['from'] = $params['from'] ?? key(CRM_Core_BAO_Email::domainEmails());
a79d2ec2 202 return CRM_Core_BAO_MessageTemplate::sendTemplate($sendTemplateParams);
203 }
204
44a2f017 205 /**
206 * Get valid from emails for payment.
207 *
208 * @param int $eventID
209 *
210 * @return array
211 */
212 public static function getValidFromEmailsForPayment($eventID = NULL) {
213 if ($eventID) {
214 $emails = CRM_Event_BAO_Event::getFromEmailIds($eventID);
215 }
216 else {
217 $emails['from_email_id'] = CRM_Core_BAO_Email::getFromEmail();
218 }
219 return $emails['from_email_id'];
220 }
221
a79d2ec2 222 /**
223 * Load entities related to the current payment id.
224 *
225 * This gives us all the data we need to send an email confirmation but avoiding
226 * getting anything not tested for the confirmations. We retrieve the 'full' event as
227 * it has been traditionally assigned in full.
228 *
229 * @param int $id
230 *
231 * @return array
232 * - contact = ['id' => x, 'display_name' => y, 'email' => z]
233 * - event = [.... full event details......]
234 * - contribution = ['id' => x],
235 * - payment = [payment info + payment summary info]
fdbe8ee1 236 * @throws \CiviCRM_API3_Exception
a79d2ec2 237 */
238 protected static function loadRelatedEntities($id) {
239 $entities = [];
240 $contributionID = (int) civicrm_api3('EntityFinancialTrxn', 'getvalue', [
241 'financial_trxn_id' => $id,
242 'entity_table' => 'civicrm_contribution',
243 'return' => 'entity_id',
244 ]);
245 $entities['contribution'] = ['id' => $contributionID];
246 $entities['payment'] = array_merge(civicrm_api3('FinancialTrxn', 'getsingle', ['id' => $id]),
247 CRM_Contribute_BAO_Contribution::getPaymentInfo($contributionID)
248 );
249
250 $contactID = self::getPaymentContactID($contributionID);
251 list($displayName, $email) = CRM_Contact_BAO_Contact_Location::getEmailDetails($contactID);
252 $entities['contact'] = ['id' => $contactID, 'display_name' => $displayName, 'email' => $email];
1e477c5b 253 $contact = civicrm_api3('Contact', 'getsingle', ['id' => $contactID, 'return' => 'email_greeting']);
254 $entities['contact']['email_greeting'] = $contact['email_greeting_display'];
a79d2ec2 255
256 $participantRecords = civicrm_api3('ParticipantPayment', 'get', [
257 'contribution_id' => $contributionID,
258 'api.Participant.get' => ['return' => 'event_id'],
259 'sequential' => 1,
260 ])['values'];
261 if (!empty($participantRecords)) {
262 $entities['event'] = civicrm_api3('Event', 'getsingle', ['id' => $participantRecords[0]['api.Participant.get']['values'][0]['event_id']]);
0fad34a0 263 if (!empty($entities['event']['is_show_location'])) {
264 $locationParams = [
265 'entity_id' => $entities['event']['id'],
266 'entity_table' => 'civicrm_event',
267 ];
268 $entities['location'] = CRM_Core_BAO_Location::getValues($locationParams, TRUE);
269 }
a79d2ec2 270 }
271
272 return $entities;
273 }
274
275 /**
276 * @param int $contributionID
277 *
278 * @return int
6ac768e7 279 * @throws \CiviCRM_API3_Exception
280 * @throws \CiviCRM_API3_Exception
a79d2ec2 281 */
282 public static function getPaymentContactID($contributionID) {
283 $contribution = civicrm_api3('Contribution', 'getsingle', [
284 'id' => $contributionID ,
285 'return' => ['contact_id'],
286 ]);
287 return (int) $contribution['contact_id'];
288 }
7b966967 289
a79d2ec2 290 /**
291 * @param array $entities
292 * Related entities as an array keyed by the various entities.
293 *
294 * @return array
295 * Values required for the notification
296 * - contact_id
297 * - template_variables
298 * - event (DAO of event if relevant)
299 */
300 public static function getConfirmationTemplateParameters($entities) {
301 $templateVariables = [
302 'contactDisplayName' => $entities['contact']['display_name'],
1e477c5b 303 'emailGreeting' => $entities['contact']['email_greeting'],
a79d2ec2 304 'totalAmount' => $entities['payment']['total'],
305 'amountOwed' => $entities['payment']['balance'],
b5a442ed 306 'totalPaid' => $entities['payment']['paid'],
a79d2ec2 307 'paymentAmount' => $entities['payment']['total_amount'],
434546ac 308 'checkNumber' => CRM_Utils_Array::value('check_number', $entities['payment']),
309 'receive_date' => $entities['payment']['trxn_date'],
310 'paidBy' => CRM_Core_PseudoConstant::getLabel('CRM_Core_BAO_FinancialTrxn', 'payment_instrument_id', $entities['payment']['payment_instrument_id']),
0fad34a0 311 'isShowLocation' => (!empty($entities['event']) ? $entities['event']['is_show_location'] : FALSE),
312 'location' => CRM_Utils_Array::value('location', $entities),
313 'event' => CRM_Utils_Array::value('event', $entities),
314 'component' => (!empty($entities['event']) ? 'event' : 'contribution'),
b5a442ed 315 'isRefund' => $entities['payment']['total_amount'] < 0,
316 'isAmountzero' => $entities['payment']['total_amount'] === 0,
317 'refundAmount' => ($entities['payment']['total_amount'] < 0 ? $entities['payment']['total_amount'] : NULL),
00ef9b01 318 'paymentsComplete' => ($entities['payment']['balance'] == 0),
a79d2ec2 319 ];
a79d2ec2 320
321 return self::filterUntestedTemplateVariables($templateVariables);
322 }
323
324 /**
325 * Filter out any untested variables.
326 *
327 * This just serves to highlight if any variables are added without a unit test also being added.
328 *
329 * (if hit then add a unit test for the param & add to this array).
330 *
331 * @param array $params
332 *
333 * @return array
334 */
335 public static function filterUntestedTemplateVariables($params) {
336 $testedTemplateVariables = [
337 'contactDisplayName',
338 'totalAmount',
339 'amountOwed',
340 'paymentAmount',
341 'event',
342 'component',
434546ac 343 'checkNumber',
344 'receive_date',
345 'paidBy',
0fad34a0 346 'isShowLocation',
347 'location',
b5a442ed 348 'isRefund',
349 'isAmountzero',
350 'refundAmount',
351 'totalPaid',
00ef9b01 352 'paymentsComplete',
7b966967 353 'emailGreeting',
a79d2ec2 354 ];
565920da 355 // These are assigned by the payment form - they still 'get through' from the
356 // form for now without being in here but we should ideally load
357 // and assign. Note we should update the tpl to use {if $billingName}
358 // and ditch contributeMode - although it might need to be deprecated rather than removed.
a79d2ec2 359 $todoParams = [
a79d2ec2 360 'contributeMode',
a79d2ec2 361 'billingName',
362 'address',
363 'credit_card_type',
364 'credit_card_number',
365 'credit_card_exp_date',
a79d2ec2 366 ];
367 $filteredParams = [];
368 foreach ($testedTemplateVariables as $templateVariable) {
369 // This will cause an a-notice if any are NOT set - by design. Ensuring
370 // they are set prevents leakage.
371 $filteredParams[$templateVariable] = $params[$templateVariable];
372 }
373 return $filteredParams;
374 }
375
7585038a 376 /**
377 * Does this payment complete the contribution
378 *
379 * @param int $contributionID
380 * @param float $paymentAmount
381 *
382 * @return bool
383 */
384 protected static function isPaymentCompletesContribution($contributionID, $paymentAmount) {
385 $outstandingBalance = CRM_Contribute_BAO_Contribution::getContributionBalance($contributionID);
386 $cmp = bccomp($paymentAmount, $outstandingBalance, 5);
387 return ($cmp == 0 || $cmp == 1);
388 }
389
16b0233c 390 /**
391 * Update the status of the contribution.
392 *
393 * We pass the is_post_payment_create as we have already created the line items
394 *
395 * @param int $contributionID
396 * @param string $status
397 *
398 * @throws \CiviCRM_API3_Exception
399 */
400 private static function updateContributionStatus(int $contributionID, string $status) {
401 civicrm_api3('Contribution', 'create',
402 [
403 'id' => $contributionID,
404 'is_post_payment_create' => TRUE,
405 'contribution_status_id' => $status,
406 ]
407 );
408 }
409
f5a468db 410 /**
411 * Get the line items for the contribution.
412 *
413 * Retrieve the line items and wrangle the following
414 *
415 * - get the outstanding balance on a line item basis.
416 * - determine what amount is being paid on this line item - we get the total being paid
417 * for the whole contribution and determine the ratio of the balance that is being paid
418 * and then assign apply that ratio to each line item.
419 * - if overrides have been passed in we use those amounts instead.
420 *
421 * @param $params
422 *
423 * @return array
cc9f2882 424 * @throws \CiviCRM_API3_Exception
f5a468db 425 */
426 protected static function getPayableLineItems($params): array {
427 $lineItems = CRM_Price_BAO_LineItem::getLineItemsByContributionID($params['contribution_id']);
b40da328 428 $lineItemOverrides = [];
429 if (!empty($params['line_item'])) {
430 // The format is a bit weird here - $params['line_item'] => [[1 => 10], [2 => 40]]
431 // Squash to [1 => 10, 2 => 40]
432 foreach ($params['line_item'] as $lineItem) {
433 $lineItemOverrides += $lineItem;
434 }
435 }
f5a468db 436 $outstandingBalance = CRM_Contribute_BAO_Contribution::getContributionBalance($params['contribution_id']);
437 if ($outstandingBalance !== 0.0) {
438 $ratio = $params['total_amount'] / $outstandingBalance;
439 }
440 else {
441 // Help we are making a payment but no money is owed. We won't allocate the overpayment to any line item.
442 $ratio = 0;
443 }
444 foreach ($lineItems as $lineItemID => $lineItem) {
cc9f2882 445 // Ideally id would be set deeper but for now just add in here.
446 $lineItems[$lineItemID]['id'] = $lineItemID;
4f6e08d0 447 $lineItems[$lineItemID]['paid'] = self::getAmountOfLineItemPaid($lineItemID);
f5a468db 448 $lineItems[$lineItemID]['balance'] = $lineItem['subTotal'] - $lineItems[$lineItemID]['paid'];
449
450 if (!empty($lineItemOverrides)) {
451 $lineItems[$lineItemID]['allocation'] = CRM_Utils_Array::value($lineItemID, $lineItemOverrides);
452 }
453 else {
454 $lineItems[$lineItemID]['allocation'] = $lineItems[$lineItemID]['balance'] * $ratio;
455 }
456 }
457 return $lineItems;
458 }
459
4f6e08d0 460 /**
461 * Get the amount paid so far against this line item.
462 *
463 * @param int $lineItemID
464 *
465 * @return float
466 *
467 * @throws \CiviCRM_API3_Exception
468 */
469 protected static function getAmountOfLineItemPaid($lineItemID) {
470 $paid = 0;
471 $financialItems = civicrm_api3('FinancialItem', 'get', [
472 'entity_id' => $lineItemID,
473 'entity_table' => 'civicrm_line_item',
474 'options' => ['sort' => 'id DESC', 'limit' => 0],
475 ])['values'];
476 if (!empty($financialItems)) {
477 $entityFinancialTrxns = civicrm_api3('EntityFinancialTrxn', 'get', [
478 'entity_table' => 'civicrm_financial_item',
479 'entity_id' => ['IN' => array_keys($financialItems)],
480 'options' => ['limit' => 0],
481 'financial_trxn_id.is_payment' => 1,
482 ])['values'];
483 foreach ($entityFinancialTrxns as $entityFinancialTrxn) {
484 $paid += $entityFinancialTrxn['amount'];
485 }
486 }
487 return (float) $paid;
488 }
489
6ac768e7 490 /**
491 * Reverse the entity financial transactions associated with the cancelled payment.
492 *
493 * The reversals are linked to the new payemnt.
494 *
495 * @param array $params
496 * @param int $trxnID
497 *
498 * @throws \CiviCRM_API3_Exception
499 */
500 protected static function reverseAllocationsFromPreviousPayment($params, $trxnID) {
501 // Do a direct reversal of any entity_financial_trxn records being cancelled.
502 $entityFinancialTrxns = civicrm_api3('EntityFinancialTrxn', 'get', [
503 'entity_table' => 'civicrm_financial_item',
504 'options' => ['limit' => 0],
505 'financial_trxn_id.id' => $params['cancelled_payment_id'],
506 ])['values'];
507 foreach ($entityFinancialTrxns as $entityFinancialTrxn) {
508 civicrm_api3('EntityFinancialTrxn', 'create', [
509 'entity_table' => 'civicrm_financial_item',
510 'entity_id' => $entityFinancialTrxn['entity_id'],
511 'amount' => -$entityFinancialTrxn['amount'],
512 'financial_trxn_id' => $trxnID,
513 ]);
514 }
515 }
516
cc9f2882 517 /**
518 * Create a financial items & return the ID.
519 *
520 * Ideally this will never be called.
521 *
522 * However, I hit a scenario in testing where 'something' had created a pending payment with
523 * no financial items and that would result in a fatal error without handling here. I failed
524 * to replicate & am not investigating via a new test methodology
525 * https://github.com/civicrm/civicrm-core/pull/15706
526 *
527 * After this is in I will do more digging & once I feel confident new instances are not being
528 * created I will add deprecation notices into this function with a view to removing.
529 *
530 * However, I think we want to add it in 5.20 as there is a risk of users experiencing an error
531 * if there is incorrect data & we need time to ensure that what I hit was not a 'thing.
532 * (it might be the demo site data is a bit flawed & that was the issue).
533 *
534 * @param array $lineItem
535 * @param string $trxn_date
536 * @param int $contactID
537 * @param string $currency
538 *
539 * @return int
540 *
541 * @throws \CiviCRM_API3_Exception
542 */
543 protected static function getNewFinancialItemID($lineItem, $trxn_date, $contactID, $currency): int {
544 $financialAccount = CRM_Financial_BAO_FinancialAccount::getFinancialAccountForFinancialTypeByRelationship(
545 $lineItem['financial_type_id'],
546 'Income Account Is'
547 );
548 $itemParams = [
549 'transaction_date' => $trxn_date,
550 'contact_id' => $contactID,
551 'currency' => $currency,
552 'amount' => $lineItem['line_total'],
553 'description' => $lineItem['label'],
554 'status_id' => 'Unpaid',
555 'financial_account_id' => $financialAccount,
556 'entity_table' => 'civicrm_line_item',
557 'entity_id' => $lineItem['id'],
558 ];
559 return (int) civicrm_api3('FinancialItem', 'create', $itemParams)['id'];
560 }
561
0c9b306a 562}