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 | */ |
37 | class 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 | } |