Creating an Order fails with `Line item total doesn't match total amount` due to the mismatch of 2 vs 6 decimal places
Opening this instead of conflating two issues in #188 - specifically referring to this thread. As the subject says, creating an Order can fail with the error Line item total doesn't match total amount
because of the mismatch of 2 vs 6 decimal places when calculating the Order total from the amounts and tax amounts in the Line Items.
Say, for example, I have two Taxable Line Items in an Order both of which have had their Tax Amount pre-calculated - in this case by WooCommerce but I assume this could equally be the result from any external Payment Processor. All the amounts in the following params are correct as far as WooCommerce is concerned. Both CiviCRM and WooCommerce have 19.37910000
configured as the relevant Tax Rate.
[params] => Array
(
[contact_id] => 210
[financial_type_id] => 5
[payment_instrument_id] => 4
[trxn_id] => WooCommerce Order - 2250
[invoice_id] => 2250_woocommerce
[receive_date] => 2021-10-13 19:34:30
[contribution_status_id] => Pending
[is_pay_later] => 1
[total_amount] => 77.59 <-- The pre-calculated Total Amount
[tax_amount] => 12.59 <-- The pre-calculated Total Tax Amount
[source] => Shop
[campaign_id] => 3
[note] => Solstice Ticket (Bass) x 1, Solstice Ticket (Tenor) x 1
[line_items] => Array
(
[17] => Array
(
[params] => Array
(
[event_id] => 2
[contact_id] => 210
[role_id] => 1
[price_set_id] => 8
[fee_level] => Bass
[fee_amount] => 29.84
[source] => Shop: Solstice Ticket (Bass)
[status_id] => Pending from pay later
)
[line_item] => Array
(
[0] => Array
(
[price_field_id] => 9
[unit_price] => 25.00
[qty] => 1
[line_total] => 25.00 <-- The Line Total
[tax_amount] => 4.84 <-- The pre-calculated Tax Amount
[label] => Bass
[entity_table] => civicrm_participant
[financial_type_id] => 5
[price_field_value_id] => 19
)
)
)
[18] => Array
(
[params] => Array
(
[event_id] => 2
[contact_id] => 210
[role_id] => 1
[price_set_id] => 8
[fee_level] => Tenor
[fee_amount] => 47.75
[source] => Shop: Solstice Ticket (Tenor)
[status_id] => Pending from pay later
)
[line_item] => Array
(
[0] => Array
(
[price_field_id] => 9
[unit_price] => 40.00
[qty] => 1
[line_total] => 40.00 <-- The Line Total
[tax_amount] => 7.75 <-- The pre-calculated Tax Amount
[label] => Tenor
[entity_table] => civicrm_participant
[financial_type_id] => 5
[price_field_value_id] => 20
)
)
)
)
)
The Order API recalculates $order->getTotalAmount()
and then compares it with what's passed in via the API. However, the rounding to 6 decimal places (rather than 2) triggers CRM_Contribute_Exception_CheckLineItemsException
because $params['total_amount'] = 77.59
does not equal $order->getTotalAmount() = 77.596415
.
WooCommerce appears to round the Line Items before summing them, so:
- Line Item 1:
1.193791 * 25 = 29.844775 => 29.84
- Line Item 2:
1.193791 * 40 = 47.75164 => 47.75
- Total of rounded Line Items:
77.59
CiviCRM, appears to sum the Line Items before rounding them, so:
- Line Item 1:
1.193791 * 25 = 29.844775
- Line Item 2:
1.193791 * 40 = 47.75164
- Total of summed Line Items:
77.596415
CRM_Utils_Money::equals()
then rounds up not down because of the extra precision and
My suggestion is that CiviCRM should:
- Verify the amount in each Line Item to the number of decimal places for the given currency.
- Keep a running total of those amounts.
- Compare the final running total with the
total_amount
.