3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
12 use Civi\Api4\LineItem
;
13 use Civi\Api4\PriceField
;
14 use Civi\Api4\PriceFieldValue
;
15 use Civi\Api4\PriceSet
;
20 * @copyright CiviCRM LLC https://civicrm.org/licensing
24 * This class is intended to become the object to manage orders, including via Order.api.
26 * As of writing it is in the process of having appropriate functions built up.
27 * It should **NOT** be accessed directly outside of tested core methods as it
32 class CRM_Financial_BAO_Order
{
39 protected $priceSetID;
42 * Selected price items in the format we see in forms.
45 * [price_3 => 4, price_10 => 7]
46 * is equivalent to 'option_value 4 for radio price field 3 and
47 * a quantity of 7 for text price field 10.
51 protected $priceSelection = [];
54 * Override for financial type id.
56 * Used when the financial type id is to be overridden for all line items
57 * (as can happen in backoffice forms)
61 protected $overrideFinancialTypeID;
64 * Overridable financial type id.
66 * When this is set only this financial type will be overridden.
68 * This is relevant to repeat transactions where we want to
69 * override the type on the line items if it a different financial type has
70 * been saved against the recurring contribution. However, it the line item
71 * financial type differs from the contribution financial type then we
72 * treat this as deliberately uncoupled and don't flow through changes
73 * in financial type down to the line items.
75 * This is covered in testRepeatTransactionUpdatedFinancialTypeAndNotEquals.
79 protected $overridableFinancialTypeID;
82 * Get overridable financial type id.
84 * If only one financial type id can be overridden at the line item level
85 * then get it here, otherwise NULL.
89 public function getOverridableFinancialTypeID(): ?
int {
90 return $this->overridableFinancialTypeID
;
94 * Set overridable financial type id.
96 * If only one financial type id can be overridden at the line item level
99 * @param int|null $overridableFinancialTypeID
101 public function setOverridableFinancialTypeID(?
int $overridableFinancialTypeID): void
{
102 $this->overridableFinancialTypeID
= $overridableFinancialTypeID;
106 * Financial type id to use for any lines where is is not provided.
110 protected $defaultFinancialTypeID;
113 * ID of a contribution to be used as a template.
117 protected $templateContributionID;
120 * Should we permit the line item financial type to be overridden when there is more than one line.
122 * Historically the answer is 'yes' for v3 order api and 'no' for repeattransaction
123 * and backoffice forms.
127 protected $isPermitOverrideFinancialTypeForMultipleLines = TRUE;
132 public function isPermitOverrideFinancialTypeForMultipleLines(): bool {
133 return $this->isPermitOverrideFinancialTypeForMultipleLines
;
137 * @param bool $isPermitOverrideFinancialTypeForMultipleLines
139 public function setIsPermitOverrideFinancialTypeForMultipleLines(bool $isPermitOverrideFinancialTypeForMultipleLines): void
{
140 $this->isPermitOverrideFinancialTypeForMultipleLines
= $isPermitOverrideFinancialTypeForMultipleLines;
144 * Number of line items.
148 protected $lineItemCount;
153 public function getLineItemCount(): int {
154 if (!isset($this->lineItemCount
)) {
155 $this->lineItemCount
= count($this->getPriceOptions()) ||
count($this->lineItems
);
157 return $this->lineItemCount
;
161 * @param int $lineItemCount
163 public function setLineItemCount(int $lineItemCount): void
{
164 $this->lineItemCount
= $lineItemCount;
170 public function getTemplateContributionID(): ?
int {
171 return $this->templateContributionID
;
175 * @param int $templateContributionID
177 public function setTemplateContributionID(int $templateContributionID): void
{
178 $this->templateContributionID
= $templateContributionID;
184 public function getDefaultFinancialTypeID(): int {
185 return $this->defaultFinancialTypeID
;
189 * Set the default financial type id to be used when the line has none.
191 * @param int|null $defaultFinancialTypeID
193 public function setDefaultFinancialTypeID(?
int $defaultFinancialTypeID): void
{
194 $this->defaultFinancialTypeID
= $defaultFinancialTypeID;
198 * Override for the total amount of the order.
200 * When there is a single line item the order total may be overriden.
204 protected $overrideTotalAmount;
207 * Line items in the order.
211 protected $lineItems = [];
214 * Array of entities ordered.
218 protected $entityParameters = [];
221 * Default price sets for component.
225 protected $defaultPriceSets = [];
228 * Cache of the default price field.
232 protected $defaultPriceField;
235 * Cache of the default price field value ID.
239 protected $defaultPriceFieldValueID;
242 * Get parameters for the entities bought as part of this order.
246 * @internal core tested code only.
249 public function getEntitiesToCreate(): array {
251 foreach ($this->entityParameters
as $entityToCreate) {
252 if (in_array($entityToCreate['entity'], ['participant', 'membership'], TRUE)) {
253 $entities[] = $entityToCreate;
260 * Set parameters for the entities bought as part of this order.
262 * @param array $entityParameters
263 * @param int|string $key indexing reference
265 * @internal core tested code only.
268 public function setEntityParameters(array $entityParameters, $key): void
{
269 $this->entityParameters
[$key] = $entityParameters;
273 * Add a line item to an entity.
275 * The v3 api supports more than on line item being stored against a given
276 * set of entity parameters. There is some doubt as to whether this is a
277 * good thing that should be supported in v4 or something that 'seemed
278 * like a good idea at the time' - but this allows the lines to be added from the
281 * @param string $lineIndex
282 * @param string $entityKey
284 public function addLineItemToEntityParameters(string $lineIndex, string $entityKey): void
{
285 $this->entityParameters
[$entityKey]['entity'] = $this->getLineItemEntity($lineIndex);
286 $this->entityParameters
[$entityKey]['line_references'][] = $lineIndex;
290 * Metadata for price fields.
294 protected $priceFieldMetadata = [];
297 * Metadata for price sets.
301 protected $priceSetMetadata = [];
306 * @internal use in tested core code only.
308 * @return \CRM_Core_Form|NULL
310 public function getForm(): ?CRM_Core_Form
{
317 * @internal use in tested core code only.
319 * @param \CRM_Core_Form|NULL $form
321 public function setForm(?CRM_Core_Form
$form): void
{
326 * The serialize & unserialize functions are to prevent the form being serialized & stored.
328 * The form could be potentially large & circular.
330 * We simply serialize the values needed to re-serialize the form.
334 public function _serialize(): array {
336 'OverrideTotalAmount' => $this->getOverrideTotalAmount(),
337 'OverrideFinancialType' => $this->getOverrideFinancialTypeID(),
338 'PriceSelection' => $this->getPriceSelection(),
343 * Re-instantiate the the class with non-calculated variables.
347 public function _unserialize(array $data): void
{
348 foreach ($data as $key => $value) {
355 * Form object - if present the buildAmount hook will be called.
357 * @var \CRM_Member_Form_Membership|\CRM_Member_Form_MembershipRenewal
362 * Get Set override for total amount of the order.
364 * @internal use in tested core code only.
366 * @return float|false
368 public function getOverrideTotalAmount() {
369 // The override amount is only valid for quick config price sets where more
370 // than one field has not been selected.
371 if (!$this->overrideTotalAmount ||
$this->getLineItemCount() > 1) {
374 return $this->overrideTotalAmount
;
378 * Is the line item financial type to be overridden.
380 * We have a tested scenario for repeatcontribution where the line item
381 * does not match the top level financial type for the order. In this case
382 * any financial type override has been determined to NOT apply to the line items.
384 * This is locked in via testRepeatTransactionUpdatedFinancialTypeAndNotEquals.
386 * @param int $financialTypeID
390 public function isOverrideLineItemFinancialType(int $financialTypeID) {
391 if (!$this->getOverrideFinancialTypeID()) {
394 if (!$this->getOverridableFinancialTypeID()) {
397 return $this->getOverridableFinancialTypeID() === $financialTypeID;
401 * Set override for total amount.
403 * @internal use in tested core code only.
405 * @param float|null $overrideTotalAmount
407 public function setOverrideTotalAmount(?
float $overrideTotalAmount): void
{
408 $this->overrideTotalAmount
= $overrideTotalAmount;
412 * Get override for total amount.
414 * @internal use in tested core code only.
418 public function getOverrideFinancialTypeID() {
419 // We don't permit overrides if there is more than one line.
420 // The reason for this constraint may be more historical since
421 // the case could be made that if it is set it should be used and
422 // we have built out the tax calculations a lot now.
423 if (!$this->isPermitOverrideFinancialTypeForMultipleLines() && $this->getLineItemCount() > 1) {
426 return $this->overrideFinancialTypeID ??
FALSE;
430 * Set override for financial type ID.
432 * @internal use in tested core code only.
434 * @param int|null $overrideFinancialTypeID
436 public function setOverrideFinancialTypeID(?
int $overrideFinancialTypeID): void
{
437 $this->overrideFinancialTypeID
= $overrideFinancialTypeID;
441 * Getter for price set id.
443 * @internal use in tested core code only.
447 * @throws \API_Exception
449 public function getPriceSetID(): int {
450 if (!$this->priceSetID
) {
451 foreach ($this->getPriceOptions() as $fieldID => $valueID) {
452 $this->setPriceSetIDFromSelectedField($fieldID);
455 return $this->priceSetID
;
459 * Setter for price set id.
461 * @internal use in tested core code only.
463 * @param int $priceSetID
465 public function setPriceSetID(int $priceSetID) {
466 $this->priceSetID
= $priceSetID;
470 * Set price set id to the default.
472 * @param string $component [membership|contribution]
474 * @throws \API_Exception
475 * @internal use in tested core code only.
477 public function setPriceSetToDefault(string $component): void
{
478 $this->priceSetID
= $this->getDefaultPriceSetForComponent($component);
482 * Set price set ID based on the contribution page id.
484 * @internal use in tested core code only.
486 * @param int $contributionPageID
489 public function setPriceSetIDByContributionPageID(int $contributionPageID): void
{
490 $this->setPriceSetIDByEntity('contribution_page', $contributionPageID);
494 * Set price set ID based on the event id.
496 * @internal use in tested core code only.
498 * @param int $eventID
500 * @throws \CiviCRM_API3_Exception
502 public function setPriceSetIDByEventPageID(int $eventID): void
{
503 $this->setPriceSetIDByEntity('event', $eventID);
507 * Set the price set id based on looking up the entity.
509 * @internal use in tested core code only.
511 * @param string $entity
515 protected function setPriceSetIDByEntity(string $entity, int $id): void
{
516 $this->priceSetID
= CRM_Price_BAO_PriceSet
::getFor('civicrm_' . $entity, $id);
520 * Getter for price selection.
522 * @internal use in tested core code only.
526 public function getPriceSelection(): array {
527 return $this->priceSelection
;
531 * Setter for price selection.
533 * @internal use in tested core code only.
535 * @param array $priceSelection
537 public function setPriceSelection(array $priceSelection) {
538 $this->priceSelection
= $priceSelection;
542 * Price options the simplified price fields selections.
544 * ie. the 'price_' is stripped off the key name and the field ID
545 * is cast to an integer.
547 * @internal use in tested core code only.
551 public function getPriceOptions():array {
553 foreach ($this->getPriceSelection() as $fieldName => $value) {
554 $fieldID = substr($fieldName, 6);
555 $priceOptions[(int) $fieldID] = $value;
557 return $priceOptions;
561 * Get the metadata for the given field.
563 * @internal use in tested core code only.
569 public function getPriceFieldSpec(int $id) :array {
570 return $this->getPriceFieldsMetadata()[$id];
574 * Get the metadata for the fields in the price set.
576 * @internal use in tested core code only.
580 public function getPriceFieldsMetadata(): array {
581 if (empty($this->priceFieldMetadata
)) {
582 $this->getPriceSetMetadata();
584 return $this->priceFieldMetadata
;
588 * Set the metadata for the order.
590 * @param array $metadata
592 protected function setPriceFieldMetadata($metadata) {
593 $this->priceFieldMetadata
= $metadata;
594 if ($this->getForm()) {
595 CRM_Utils_Hook
::buildAmount($this->form
->getFormContext(), $this->form
, $this->priceFieldMetadata
);
600 * Get the metadata for the fields in the price set.
602 * @internal use in tested core code only.
606 public function getPriceSetMetadata(): array {
607 if (empty($this->priceSetMetadata
)) {
608 $priceSetMetadata = CRM_Price_BAO_PriceSet
::getCachedPriceSetDetail($this->getPriceSetID());
609 $this->setPriceFieldMetadata($priceSetMetadata['fields']);
610 unset($priceSetMetadata['fields']);
611 $this->priceSetMetadata
= $priceSetMetadata;
613 return $this->priceSetMetadata
;
617 * Get the financial type id for the order.
619 * @internal use in tested core code only.
621 * This may differ to the line items....
625 public function getFinancialTypeID(): int {
626 return (int) $this->getOverrideFinancialTypeID() ?
: $this->getPriceSetMetadata()['financial_type_id'];
630 * Set the price field selection from an array of params containing price
633 * This function takes the sort of 'anything & everything' parameters that
634 * come in from the form layer and filters them before assigning them to the
635 * priceSelection property.
637 * @param array $input
639 * @throws \API_Exception
641 public function setPriceSelectionFromUnfilteredInput(array $input): void
{
642 foreach ($input as $fieldName => $value) {
643 if (strpos($fieldName, 'price_') === 0) {
644 $fieldID = substr($fieldName, 6);
645 if (is_numeric($fieldID)) {
646 $this->priceSelection
[$fieldName] = $value;
650 if (empty($this->priceSelection
) && isset($input['total_amount'])
651 && is_numeric($input['total_amount']) && !empty($input['financial_type_id'])) {
652 $this->priceSelection
['price_' . $this->getDefaultPriceFieldID()] = $input['total_amount'];
653 $this->setOverrideFinancialTypeID($input['financial_type_id']);
658 * Get the id of the price field to use when just an amount is provided.
660 * @throws \API_Exception
664 public function getDefaultPriceFieldID():int {
665 if (!$this->defaultPriceField
) {
666 $this->defaultPriceField
= PriceField
::get(FALSE)
667 ->addWhere('name', '=', 'contribution_amount')
668 ->addWhere('price_set_id.name', '=', 'default_contribution_amount')
669 ->execute()->first();
671 return $this->defaultPriceField
['id'];
675 * Get the id of the price field to use when just an amount is provided.
677 * @throws \API_Exception
681 public function getDefaultPriceFieldValueID():int {
682 if (!$this->defaultPriceFieldValueID
) {
683 $this->defaultPriceFieldValueID
= PriceFieldValue
::get(FALSE)
684 ->addWhere('name', '=', 'contribution_amount')
685 ->addWhere('price_field_id.name', '=', 'contribution_amount')
686 ->execute()->first()['id'];
688 return $this->defaultPriceFieldValueID
;
696 * @throws \CiviCRM_API3_Exception
698 public function getLineItems():array {
699 if (empty($this->lineItems
)) {
700 $this->lineItems
= $this->calculateLineItems();
702 return $this->lineItems
;
706 * Get line items in a 'traditional' indexing format.
708 * This ensures the line items are indexed by
709 * price field id - as required by the contribution BAO.
711 * @throws \CiviCRM_API3_Exception
713 public function getPriceFieldIndexedLineItems(): array {
715 foreach ($this->getLineItems() as $item) {
716 $lines[$item['price_field_id']] = $item;
722 * Get line items that specifically relate to memberships.
726 * @throws \CiviCRM_API3_Exception
728 public function getMembershipLineItems():array {
729 $lines = $this->getLineItems();
730 foreach ($lines as $index => $line) {
731 if (empty($line['membership_type_id'])) {
732 unset($lines[$index]);
735 if (empty($line['membership_num_terms'])) {
736 $lines[$index]['membership_num_terms'] = 1;
743 * Get an array of all membership types included in the order.
747 * @throws \CiviCRM_API3_Exception
749 public function getMembershipTypes(): array {
751 foreach ($this->getMembershipLineItems() as $line) {
752 $types[$line['membership_type_id']] = CRM_Member_BAO_MembershipType
::getMembershipType((int) $line['membership_type_id']);
758 * Get an array of all membership types included in the order.
762 * @throws \CiviCRM_API3_Exception
764 public function getRenewableMembershipTypes(): array {
766 foreach ($this->getMembershipTypes() as $id => $type) {
767 if (!empty($type['auto_renew'])) {
777 * @throws \API_Exception
779 protected function calculateLineItems(): array {
781 $params = $this->getPriceSelection();
782 if ($this->getOverrideTotalAmount() !== FALSE) {
783 // We need to do this to keep getLine from doing weird stuff but the goal
784 // is to ditch getLine next round of refactoring
785 // and make the code more sane.
786 $params['total_amount'] = $this->getOverrideTotalAmount();
789 // Dummy value to prevent e-notice in getLine. We calculate tax in this class.
790 $params['financial_type_id'] = 0;
791 if ($this->getTemplateContributionID()) {
792 $lineItems = $this->getLinesFromTemplateContribution();
795 foreach ($this->getPriceOptions() as $fieldID => $valueID) {
796 $this->setPriceSetIDFromSelectedField($fieldID);
797 $throwAwayArray = [];
798 // @todo - still using getLine for now but better to bring it to this class & do a better job.
799 $newLines = CRM_Price_BAO_PriceSet
::getLine($params, $throwAwayArray, $this->getPriceSetID(), $this->getPriceFieldSpec($fieldID), $fieldID)[1];
800 foreach ($newLines as $newLine) {
801 $lineItems[$newLine['price_field_value_id']] = $newLine;
805 // Set the line item count here because it is needed to determine whether
806 // we can use overrides and would not be set yet if we have loaded them from
807 // a template contribution.
808 $this->setLineItemCount(count($lineItems));
810 foreach ($lineItems as &$lineItem) {
811 // Set the price set id if not set above. Note that the above
812 // requires it for line retrieval but we want to fix that as it
813 // should not be required at that point.
814 $this->setPriceSetIDFromSelectedField($lineItem['price_field_id']);
815 // Set any pre-calculation to zero as we will calculate.
816 $lineItem['tax_amount'] = 0;
817 if ($this->isOverrideLineItemFinancialType($lineItem['financial_type_id']) !== FALSE) {
818 $lineItem['financial_type_id'] = $this->getOverrideFinancialTypeID();
820 $taxRate = $this->getTaxRate((int) $lineItem['financial_type_id']);
821 if ($this->getOverrideTotalAmount() !== FALSE) {
822 $this->addTotalsToLineBasedOnOverrideTotal((int) $lineItem['financial_type_id'], $lineItem);
825 $lineItem['tax_amount'] = ($taxRate / 100) * $lineItem['line_total'];
832 * Get the total amount for the order.
836 * @throws \CiviCRM_API3_Exception
838 public function getTotalTaxAmount() :float {
840 foreach ($this->getLineItems() as $lineItem) {
841 $amount +
= $lineItem['tax_amount'] ??
0.0;
847 * Get the total amount for the order.
851 * @throws \CiviCRM_API3_Exception
853 public function getTotalAmount() :float {
855 foreach ($this->getLineItems() as $lineItem) {
856 $amount +
= ($lineItem['line_total'] ??
0.0) +
($lineItem['tax_amount'] ??
0.0);
862 * Get the total amount relating to memberships for the order.
866 * @throws \CiviCRM_API3_Exception
868 public function getMembershipTotalAmount() :float {
870 foreach ($this->getMembershipLineItems() as $lineItem) {
871 $amount +
= ($lineItem['line_total'] ??
0.0) +
($lineItem['tax_amount'] ??
0.0);
877 * Get the tax rate for the given financial type.
879 * @param int $financialTypeID
883 public function getTaxRate(int $financialTypeID) {
884 $taxRates = CRM_Core_PseudoConstant
::getTaxRates();
885 if (!isset($taxRates[$financialTypeID])) {
888 return $taxRates[$financialTypeID];
894 * @throws \API_Exception
896 protected function setPriceSetIDFromSelectedField($fieldID): void
{
897 if (!isset($this->priceSetID
)) {
898 $this->setPriceSetID(PriceField
::get(FALSE)
899 ->addSelect('price_set_id')
900 ->addWhere('id', '=', $fieldID)
902 ->first()['price_set_id']);
909 * This function augments the line item where possible. The calling code
910 * should not attempt to set taxes. This function allows minimal values
911 * to be passed for the default price sets - ie if only membership_type_id is
912 * specified the price_field_id and price_value_id will be determined.
914 * @param array $lineItem
915 * @param int|string $index
917 * @throws \API_Exception
918 * @internal tested core code usage only.
919 * @internal use in tested core code only.
922 public function setLineItem(array $lineItem, $index): void
{
923 if (!isset($this->priceSetID
)) {
924 if (!empty($lineItem['price_field_id'])) {
925 $this->setPriceSetIDFromSelectedField($lineItem['price_field_id']);
928 // we are using either the default membership or default contribution
929 // If membership type is passed in we use the default price field.
930 $component = !empty($lineItem['membership_type_id']) ?
'membership' : 'contribution';
931 $this->setPriceSetToDefault($component);
934 if (!isset($lineItem['financial_type_id'])) {
935 $lineItem['financial_type_id'] = $this->getDefaultFinancialTypeID();
937 if (!is_numeric($lineItem['financial_type_id'])) {
938 $lineItem['financial_type_id'] = CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'financial_type_id', $lineItem['financial_type_id']);
940 if ($this->getOverrideTotalAmount()) {
941 $this->addTotalsToLineBasedOnOverrideTotal((int) $lineItem['financial_type_id'], $lineItem);
944 $lineItem['tax_amount'] = ($this->getTaxRate($lineItem['financial_type_id']) / 100) * $lineItem['line_total'];
946 if (!empty($lineItem['membership_type_id'])) {
947 $lineItem['entity_table'] = 'civicrm_membership';
948 if (empty($lineItem['price_field_id']) && empty($lineItem['price_field_value_id'])) {
949 $lineItem = $this->fillMembershipLine($lineItem);
952 if ($this->getPriceSetID() === $this->getDefaultPriceSetForComponent('contribution')) {
953 $this->fillDefaultContributionLine($lineItem);
955 $this->lineItems
[$index] = $lineItem;
959 * Set a value on a line item.
961 * @internal only use in core tested code.
963 * @param string $name
964 * @param mixed $value
965 * @param string|int $index
967 public function setLineItemValue(string $name, $value, $index): void
{
968 $this->lineItems
[$index][$name] = $value;
972 * @param int|string $index
976 public function getLineItemEntity($index):string {
977 // @todo - ensure entity_table is set in setLineItem, go back to enotices here.
978 return str_replace('civicrm_', '', ($this->lineItems
[$index]['entity_table'] ??
'contribution'));
982 * Get the ordered line item.
984 * @param string|int $index
988 public function getLineItem($index): array {
989 return $this->lineItems
[$index];
993 * Fills in additional data for the membership line.
995 * The minimum requirement is the membership_type_id and that priceSetID is set.
997 * @param array $lineItem
1001 protected function fillMembershipLine(array $lineItem): array {
1002 $fields = $this->getPriceFieldsMetadata();
1003 foreach ($fields as $field) {
1004 if (!isset($lineItem['price_field_value_id'])) {
1005 foreach ($field['options'] as $option) {
1006 if ((int) $option['membership_type_id'] === (int) $lineItem['membership_type_id']) {
1007 $lineItem['price_field_id'] = $field['id'];
1008 $lineItem['price_field_value_id'] = $option['id'];
1009 $lineItem['qty'] = 1;
1013 if (isset($lineItem['price_field_value_id'], $field['options'][$lineItem['price_field_value_id']])) {
1014 $option = $field['options'][$lineItem['price_field_value_id']];
1017 $lineItem['unit_price'] = $lineItem['line_total'] ??
$option['amount'];
1018 $lineItem['label'] = $lineItem['label'] ??
$option['label'];
1019 $lineItem['field_title'] = $lineItem['field_title'] ??
$option['label'];
1020 $lineItem['financial_type_id'] = $lineItem['financial_type_id'] ?
: ($this->getDefaultFinancialTypeID() ??
$option['financial_type_id']);
1025 * Add total_amount and tax_amount to the line from the override total.
1027 * @param int $financialTypeID
1028 * @param array $lineItem
1032 protected function addTotalsToLineBasedOnOverrideTotal(int $financialTypeID, array &$lineItem): void
{
1033 $taxRate = $this->getTaxRate($financialTypeID);
1035 // Total is tax inclusive.
1036 $lineItem['tax_amount'] = ($taxRate / 100) * $this->getOverrideTotalAmount() / (1 +
($taxRate / 100));
1037 $lineItem['line_total'] = $lineItem['unit_price'] = $this->getOverrideTotalAmount() - $lineItem['tax_amount'];
1040 $lineItem['line_total'] = $lineItem['unit_price'] = $this->getOverrideTotalAmount();
1045 * Get the line items from a template.
1047 * @return \Civi\Api4\Generic\Result
1049 * @throws \API_Exception
1051 protected function getLinesFromTemplateContribution(): array {
1052 $lines = $this->getLinesForContribution();
1053 foreach ($lines as &$line) {
1054 // The apiv4 insists on adding id - so let it get all the details
1055 // and we will filter out those that are not part of a template here.
1056 unset($line['id'], $line['contribution_id']);
1062 * Get the constructed line items formatted for the v3 Order api.
1066 * @internal core tested code only.
1068 * @throws \CiviCRM_API3_Exception
1070 public function getLineItemForV3OrderApi(): array {
1072 foreach ($this->getLineItems() as $key => $line) {
1074 'line_item' => [$line['price_field_value_id'] => $line],
1075 'params' => $this->entityParameters
[$key] ??
[],
1083 * @throws \API_Exception
1084 * @throws \Civi\API\Exception\UnauthorizedException
1086 protected function getLinesForContribution(): array {
1087 return (array) LineItem
::get(FALSE)
1088 ->addWhere('contribution_id', '=', $this->getTemplateContributionID())
1094 'price_field_value_id',
1095 'financial_type_id',
1101 'non_deductible_amount',
1102 'participant_count',
1103 'membership_num_terms',
1109 * Get the default price set id for the given component.
1111 * @param string $component
1114 * @throws \API_Exception
1116 protected function getDefaultPriceSetForComponent(string $component): int {
1117 if (!isset($this->defaultPriceSets
[$component])) {
1118 $this->defaultPriceSets
[$component] = PriceSet
::get(FALSE)
1119 ->addWhere('name', '=', ($component === 'membership' ?
'default_membership_type_amount' : 'default_contribution_amount'))
1123 return $this->defaultPriceSets
[$component];
1127 * Fill in values for a default contribution line item.
1129 * @param array $lineItem
1131 * @throws \API_Exception
1133 protected function fillDefaultContributionLine(array &$lineItem): void
{
1136 'price_field_id' => $this->getDefaultPriceFieldID(),
1137 'price_field_value_id' => $this->getDefaultPriceFieldValueID(),
1138 'entity_table' => 'civicrm_contribution',
1139 'unit_price' => $lineItem['line_total'],
1140 'label' => ts('Contribution Amount'),
1142 $lineItem = array_merge($defaults, $lineItem);