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