diff --git a/app/jobs/super_good/solidus_taxjar/replace_transaction_job.rb b/app/jobs/super_good/solidus_taxjar/replace_transaction_job.rb index 2967e9bd..2fc1024d 100644 --- a/app/jobs/super_good/solidus_taxjar/replace_transaction_job.rb +++ b/app/jobs/super_good/solidus_taxjar/replace_transaction_job.rb @@ -6,20 +6,35 @@ class ReplaceTransactionJob < ApplicationJob queue_as { SuperGood::SolidusTaxjar.job_queue } def perform(order) - order_transaction = SuperGood::SolidusTaxjar.reporting.refund_and_create_new_transaction(order) + order.shipments.group_by(&:address).each do |address, shipments| + begin + latest_order_transactions = OrderTransaction.latest_for(order) - SuperGood::SolidusTaxjar::TransactionSyncLog.create!( - order: order, - order_transaction: order_transaction, - status: :success - ) + latest_order_transactions.each do |transaction| + transaction_response = @api.create_refund_transaction_for(order) + transaction.create_refund_transaction!( + transaction_id: transaction_response.transaction_id, + transaction_date: transaction_response.transaction_date + ) + end - rescue Taxjar::Error => exception - SuperGood::SolidusTaxjar::TransactionSyncLog.create!( - order: order, - status: :error, - error_message: exception.message - ) + return if order.total.zero? + + order_transaction = SuperGood::SolidusTaxjar.reporting.refund_and_create_new_transaction(order, address, shipments) + + SuperGood::SolidusTaxjar::TransactionSyncLog.create!( + order: order, + order_transaction: order_transaction, + status: :success + ) + rescue Taxjar::Error => exception + SuperGood::SolidusTaxjar::TransactionSyncLog.create!( + order: order, + status: :error, + error_message: exception.message + ) + end + end end end end diff --git a/app/jobs/super_good/solidus_taxjar/report_transaction_job.rb b/app/jobs/super_good/solidus_taxjar/report_transaction_job.rb index 823aa294..2cb1cc1b 100644 --- a/app/jobs/super_good/solidus_taxjar/report_transaction_job.rb +++ b/app/jobs/super_good/solidus_taxjar/report_transaction_job.rb @@ -10,11 +10,13 @@ def perform(order, transaction_sync_batch = nil) transaction_sync_batch: transaction_sync_batch, order: order ) - begin - order_transaction = SuperGood::SolidusTaxjar.reporting.show_or_create_transaction(order) - transaction_sync_log.update!(order_transaction: order_transaction, status: :success) - rescue StandardError => exception - transaction_sync_log.update!(status: :error, error_message: exception.message) + order.shipments.group_by(&:address).each do |address, shipments| + begin + order_transaction = SuperGood::SolidusTaxjar.reporting.show_or_create_transaction(order, address, shipments) + transaction_sync_log.update!(order_transaction: order_transaction, status: :success) + rescue StandardError => exception + transaction_sync_log.update!(status: :error, error_message: exception.message) + end end end end diff --git a/app/models/super_good/solidus_taxjar/order_transaction.rb b/app/models/super_good/solidus_taxjar/order_transaction.rb index f82a4047..fd0f746c 100644 --- a/app/models/super_good/solidus_taxjar/order_transaction.rb +++ b/app/models/super_good/solidus_taxjar/order_transaction.rb @@ -9,7 +9,7 @@ class OrderTransaction < ActiveRecord::Base validates_presence_of :transaction_date def self.latest_for(order) - where(order: order).order(transaction_date: :desc, created_at: :desc).limit(1).first + where(order: order).where.missing(:refund_transaction).order(transaction_date: :desc, created_at: :desc) end end end diff --git a/app/models/super_good/solidus_taxjar/transaction_sync_log.rb b/app/models/super_good/solidus_taxjar/transaction_sync_log.rb index d307054c..3bd1e774 100644 --- a/app/models/super_good/solidus_taxjar/transaction_sync_log.rb +++ b/app/models/super_good/solidus_taxjar/transaction_sync_log.rb @@ -4,5 +4,5 @@ class SuperGood::SolidusTaxjar::TransactionSyncLog < ApplicationRecord belongs_to :order_transaction, optional: true delegate :refund_transaction, to: :order_transaction, :allow_nil => true - enum status: [:processing, :success, :error] + enum :status, [:processing, :success, :error] end diff --git a/lib/super_good/solidus_taxjar.rb b/lib/super_good/solidus_taxjar.rb index 4578e001..228a016d 100644 --- a/lib/super_good/solidus_taxjar.rb +++ b/lib/super_good/solidus_taxjar.rb @@ -13,6 +13,7 @@ require "super_good/solidus_taxjar/tax_calculator" require "super_good/solidus_taxjar/tax_rate_calculator" require "super_good/solidus_taxjar/discount_calculator" +require "super_good/solidus_taxjar/proportional_discount_calculator" require "super_good/solidus_taxjar/addresses" require "super_good/solidus_taxjar/reporting" require "super_good/solidus_taxjar/reportable" @@ -79,8 +80,8 @@ def logger self.reportable_order_check = ->(order) { true } - self.shipping_calculator = ->(order) { order.shipments.sum(&:total_before_tax) } - self.shipping_tax_label_maker = ->(shipment, shipping_tax) { "Sales Tax" } + self.shipping_calculator = ->(shipments) { shipments.sum(&:cost) } + self.shipping_tax_label_maker = ->(taxjar_shipment, shipment) { "Sales Tax" } self.tax_exemption_mailer_from_address = "admin@example.com" self.tax_exemption_mailer_to_address = "admin@example.com" self.taxable_address_check = ->(address) { true } diff --git a/lib/super_good/solidus_taxjar/api.rb b/lib/super_good/solidus_taxjar/api.rb index 699768b9..82aa3c40 100644 --- a/lib/super_good/solidus_taxjar/api.rb +++ b/lib/super_good/solidus_taxjar/api.rb @@ -21,8 +21,8 @@ def tax_categories taxjar_client.categories end - def tax_for(order) - taxjar_client.tax_for_order(ApiParams.order_params(order)) + def tax_for(order, address, shipments) + taxjar_client.tax_for_order(ApiParams.order_params(order, address, shipments)) end def tax_rate_for(address) @@ -33,9 +33,9 @@ def tax_rates_for(address) taxjar_client.rates_for_location(*ApiParams.address_params(address)) end - def create_transaction_for(order) + def create_transaction_for(order, address, shipments) latest_transaction_id = - OrderTransaction.latest_for(order)&.transaction_id + OrderTransaction.latest_for(order)&.first&.transaction_id transaction_id = TransactionIdGenerator.next_transaction_id( order: order, @@ -43,12 +43,12 @@ def create_transaction_for(order) ) taxjar_client.create_order( - ApiParams.transaction_params(order, transaction_id) + ApiParams.transaction_params(order, address, shipments, transaction_id) ) end - def update_transaction_for(order) - taxjar_client.update_order ApiParams.transaction_params(order) + def update_transaction_for(order, address, shipments) + taxjar_client.update_order ApiParams.transaction_params(order, address, shipments) end def delete_transaction_for(order) diff --git a/lib/super_good/solidus_taxjar/api_params.rb b/lib/super_good/solidus_taxjar/api_params.rb index 2153aad9..5572ae56 100644 --- a/lib/super_good/solidus_taxjar/api_params.rb +++ b/lib/super_good/solidus_taxjar/api_params.rb @@ -4,20 +4,15 @@ module ApiParams UNTAXABLE_INVENTORY_UNIT_STATES = ["returned", "canceled"] class << self - def order_params(order) + def order_params(order, address, shipments) {} .merge(customer_id(order)) - .merge(order_address_params(order.tax_address)) - .merge(line_items_params(order.line_items)) - .merge(shipping: shipping(order)) + .merge(order_address_params(address)) + .merge(line_items_params(shipments.map(&:inventory_units).flatten.compact)) + .merge(shipping: shipping(shipments)) .merge(SuperGood::SolidusTaxjar.custom_order_params.call(order)) end - #ADNAN: temp fix until we have a solution - def reorder_params(order) - order_params(order) - end - def address_params(address) [ address.zipcode, @@ -37,19 +32,27 @@ def tax_rate_address_params(address) }.merge(order_address_params(address)) end - def transaction_params(order, transaction_id = order.number) + def transaction_params(order, address, shipments, transaction_id = order.number) + {}.merge(customer_id(order)) + .merge(order_address_params(address)) + .merge(line_items_params(shipments.map(&:inventory_units).flatten.compact)) + .merge(shipping: shipping(shipments)) + .merge(SuperGood::SolidusTaxjar.custom_order_params.call(order)) + + # Calculate discount adjustments for proper rounding across all shipments + calculator = ProportionalDiscountCalculator.new(order) + discount_adjustments = calculator.calculate + {} .merge(customer_id(order)) - .merge(order_address_params(order.tax_address)) - .merge(transaction_line_items_params(order.line_items)) + .merge(order_address_params(address)) + .merge(transaction_line_items_params(address, shipments.map(&:inventory_units).flatten.compact, discount_adjustments)) .merge( transaction_id: transaction_id, transaction_date: order.completed_at.to_formatted_s(:iso8601), - # We use `payment_total` to reflect the total liablity - # transferred. - amount: [order.payments.completed.sum(&:amount) - refund_total_without_tax(order) - order.additional_tax_total, 0].max, - shipping: shipping(order), - sales_tax: sales_tax(order) + amount: order_total_for_shipments(address, shipments, discount_adjustments) - reimbursement_total_without_tax(shipments), + shipping: shipping(shipments), + sales_tax: sales_tax(order, address, shipments) ) end @@ -149,20 +152,24 @@ def order_address_params(address) # @param line_items [Spree::LineItem::ActiveRecord_Relation] All of the # order's line items. # @return [Hash] A TaxJar API-friendly line item collection. - def line_items_params(line_items) - { - line_items: line_items.filter_map { |line_item| - next unless line_item.quantity.positive? + def line_items_params(_inventory_units) + grouped_inventory_units = _inventory_units.group_by(&:line_item) - { - id: line_item.id, - quantity: 1, - unit_price: line_item.total, - discount: discount(line_item), - product_tax_code: line_item.tax_category&.tax_code - } + line_items = grouped_inventory_units.filter_map { |line_item, inventory_units| + quantity = inventory_units.sum(&:quantity) + + next unless quantity.positive? + + { + id: line_item.id, + quantity:, + unit_price: line_item.total / line_item.quantity, + discount: discount(line_item) * (quantity / line_item.quantity.to_f), + product_tax_code: line_item.tax_category&.tax_code } } + + { line_items: } end # @private @@ -173,79 +180,119 @@ def line_items_params(line_items) # # @param line_items [Spree::LineItem::ActiveRecord_Relation] All of the # order's line items. + # @param discount_adjustments [Hash] Pre-calculated discount amounts per line_item per address # @return [Hash] A TaxJar API-friendly line item collection. - def transaction_line_items_params(line_items) - { - line_items: line_items.filter_map { |line_item| - quantity = taxable_quantity line_item - next unless quantity.positive? + def transaction_line_items_params(address, _inventory_units, discount_adjustments = {}) + grouped_inventory_units = _inventory_units.group_by(&:line_item) - { - id: line_item.id, - quantity: quantity, - product_identifier: line_item.sku, - description: line_item.variant.descriptive_name, - product_tax_code: line_item.tax_category&.tax_code, - unit_price: SuperGood::SolidusTaxjar.line_item_unit_price_calculator.call(line_item), - discount: discount(line_item), - sales_tax: line_item_sales_tax(line_item) - } + line_items = grouped_inventory_units.filter_map { |line_item, inventory_units| + quantity = taxable_quantity(inventory_units) + + next unless quantity.positive? + + # Use pre-calculated discount adjustment if available, otherwise calculate proportionally + discount_amount = if discount_adjustments.dig(line_item.id, address.id) + discount_adjustments[line_item.id][address.id] + else + proportional_discount_calculator.proportional_discount(line_item, quantity) + end + + { + id: line_item.id, + quantity:, + product_identifier: line_item.sku, + unit_price: line_item.total / line_item.quantity, + discount: discount_amount, + product_tax_code: line_item.tax_category&.tax_code, + description: line_item.variant.descriptive_name, + sales_tax: line_item_sales_tax(line_item, address, inventory_units) } } + + { line_items: } end def discount(line_item) ::SuperGood::SolidusTaxjar.discount_calculator.new(line_item).discount end - def shipping(order) - SuperGood::SolidusTaxjar.shipping_calculator.call(order) + def proportional_discount_calculator + @proportional_discount_calculator ||= ProportionalDiscountCalculator.new(nil) end - def sales_tax(order) + def shipping(shipments) + SuperGood::SolidusTaxjar.shipping_calculator.call(shipments) + end + + def sales_tax(order, address, shipments) return 0 if order.total.zero? - order.additional_tax_total - order_reimbursement_tax_total(order) + tax_total = order.all_adjustments.tax. + select { |adjustment| adjustment.label.include?(address.address1) }.sum(&:amount) + + round_to_two_places(tax_total - reimbursement_tax_total(shipments)) end - def line_item_sales_tax(line_item) + def line_item_sales_tax(line_item, address, inventory_units) return 0 if line_item.order.total.zero? - line_item.additional_tax_total - line_item_reimbursement_tax_total(line_item) + tax_total = line_item.adjustments.tax. + select { |adjustment| adjustment.label.include?(address.address1) }.sum(&:amount) + + round_to_two_places(tax_total - line_item_reimbursement_tax_total(inventory_units)) end - def taxable_quantity(line_item) - line_item.inventory_units - .where.not(state: UNTAXABLE_INVENTORY_UNIT_STATES) - .count + def round_to_two_places(amount) + BigDecimal(amount.to_s).round(2, BigDecimal::ROUND_HALF_UP) end - def line_item_reimbursement_tax_total(line_item) - line_item - .inventory_units + def taxable_inventory(inventory_units) + inventory_units.reject {|i| UNTAXABLE_INVENTORY_UNIT_STATES.include?(i.state)} + end + + def taxable_quantity(inventory_units) + taxable_inventory(inventory_units).sum(&:quantity) + end + + def line_item_reimbursement_tax_total(inventory_units) + inventory_units .flat_map(&:return_items) .filter { |return_item| return_item.reimbursement.present? } .sum(&:additional_tax_total) end - def order_reimbursement_tax_total(order) - order.reimbursements.sum { |reimbursement| reimbursement_tax_total(reimbursement) } + def reimbursement_tax_total(shipments) + inventory_units = shipments.map(&:inventory_units).flatten.compact + inventory_units.flat_map(&:return_items) + .filter { |return_item| return_item.reimbursement.present? } + .sum(&:additional_tax_total) end - def reimbursement_tax_total(reimbursement) - reimbursement.return_items.sum(&:additional_tax_total) + def reimbursement_total_without_tax(shipments) + inventory_units = shipments.map(&:inventory_units).flatten.compact + inventory_units.flat_map(&:return_items) + .filter { |return_item| return_item.reimbursement.present? } + .sum(&:amount) end - def refund_total_without_tax(order) - order.refunds.sum do |refund| - if refund.reimbursement.present? - refund.reimbursement.total - reimbursement_tax_total(refund.reimbursement) + def order_total_for_shipments(address, shipments, discount_adjustments = {}) + grouped_inventory_units = shipments.map(&:inventory_units).flatten.compact.group_by(&:line_item) + + line_items_total = grouped_inventory_units.filter_map { |line_item, inventory_units| + quantity = inventory_units.sum(&:quantity) + next unless quantity.positive? + + # Use pre-calculated discount adjustment if available + if discount_adjustments.dig(line_item.id, address.id) + discount_amount = discount_adjustments[line_item.id][address.id] + line_item.total * (quantity / line_item.quantity.to_f) - discount_amount.abs else - # This use case represents making a line item level adjustment, and then refunding - # that amount. - refund.amount + # Use the original calculation method to maintain backward compatibility + (line_item.total - discount(line_item)) * (quantity / line_item.quantity.to_f) end - end + }.sum + + line_items_total + shipping(shipments) end end end diff --git a/lib/super_good/solidus_taxjar/proportional_discount_calculator.rb b/lib/super_good/solidus_taxjar/proportional_discount_calculator.rb new file mode 100644 index 00000000..c4374d75 --- /dev/null +++ b/lib/super_good/solidus_taxjar/proportional_discount_calculator.rb @@ -0,0 +1,128 @@ +module SuperGood + module SolidusTaxjar + # Calculates proportional discounts across multiple shipments with proper rounding. + # + # When an order has discounts and multiple shipments, discounts need to be + # distributed proportionally. This calculator ensures that rounding errors + # don't accumulate by adjusting the last shipment to absorb any difference. + # + # @example Basic usage + # calculator = ProportionalDiscountCalculator.new(order, discount_calculator) + # adjustments = calculator.calculate + # # => { line_item_id => { address_id => discount_amount } } + class ProportionalDiscountCalculator + UNTAXABLE_INVENTORY_UNIT_STATES = ["returned", "canceled"] + + # @param order [Spree::Order] The order to calculate discounts for + # @param discount_calculator [#call] Callable that returns discount for a line item + def initialize(order, discount_calculator = nil) + @order = order + @discount_calculator = discount_calculator || default_discount_calculator + end + + # Calculate discount adjustments for all line items across all shipments. + # + # This method ensures that when discounts are split proportionally across shipments, + # the rounding errors don't accumulate. The last shipment for each line item absorbs + # any rounding difference. + # + # @return [Hash] Nested hash: { line_item_id => { address_id => discount_amount } } + def calculate + @order.line_items.each_with_object({}) do |line_item, adjustments| + adjustment = calculate_line_item_adjustments(line_item) + adjustments[line_item.id] = adjustment if adjustment + end + end + + # Calculate simple proportional discount for a line item in a shipment. + # + # This is used as a fallback when pre-calculated adjustments are not available. + # + # @param line_item [Spree::LineItem] The line item + # @param quantity [Integer] The quantity in the shipment + # @return [BigDecimal] The proportional discount amount + def proportional_discount(line_item, quantity) + total_discount = line_item_discount(line_item) + return BigDecimal("0") if total_discount.zero? || line_item.quantity.zero? + + proportion = quantity / line_item.quantity.to_f + proportional_amount = total_discount * proportion + + round_to_two_places(proportional_amount) + end + + private + + def calculate_line_item_adjustments(line_item) + total_discount = line_item_discount(line_item) + return nil if total_discount.zero? + + shipment_quantities = gather_shipment_quantities(line_item) + return nil if shipment_quantities.empty? + + proportional_discounts = calculate_proportional_discounts(line_item, total_discount, shipment_quantities) + adjust_for_rounding_error!(proportional_discounts, total_discount) + build_adjustments_hash(proportional_discounts) + end + + def line_item_discount(line_item) + @discount_calculator.call(line_item) + end + + def gather_shipment_quantities(line_item) + @order.shipments.filter_map do |shipment| + inventory_units = shipment.inventory_units.select { |iu| iu.line_item_id == line_item.id } + quantity = taxable_quantity(inventory_units) + next unless quantity.positive? + + { address_id: shipment.address.id, quantity: quantity } + end + end + + def calculate_proportional_discounts(line_item, total_discount, shipment_quantities) + shipment_quantities.map do |sq| + proportion = sq[:quantity] / line_item.quantity.to_f + rounded_discount = round_to_two_places(total_discount * proportion) + sq.merge(discount: rounded_discount) + end + end + + def adjust_for_rounding_error!(proportional_discounts, total_discount) + return if proportional_discounts.empty? + + sum_of_discounts = proportional_discounts.sum { |pd| pd[:discount] } + rounding_error = round_to_two_places(total_discount - sum_of_discounts) + + if rounding_error != 0 + # Add the rounding error to the last shipment + proportional_discounts.last[:discount] = round_to_two_places( + proportional_discounts.last[:discount] + rounding_error + ) + end + end + + def build_adjustments_hash(proportional_discounts) + proportional_discounts.each_with_object({}) do |pd, hash| + hash[pd[:address_id]] = pd[:discount] + end + end + + def taxable_quantity(inventory_units) + taxable_inventory(inventory_units).sum(&:quantity) + end + + def taxable_inventory(inventory_units) + inventory_units.reject { |iu| UNTAXABLE_INVENTORY_UNIT_STATES.include?(iu.state) } + end + + def round_to_two_places(amount) + BigDecimal(amount.to_s).round(2, BigDecimal::ROUND_HALF_UP) + end + + def default_discount_calculator + ->(line_item) { ::SuperGood::SolidusTaxjar.discount_calculator.new(line_item).discount } + end + end + end +end + diff --git a/lib/super_good/solidus_taxjar/reporting.rb b/lib/super_good/solidus_taxjar/reporting.rb index e2f000d9..8dc33b14 100644 --- a/lib/super_good/solidus_taxjar/reporting.rb +++ b/lib/super_good/solidus_taxjar/reporting.rb @@ -9,20 +9,8 @@ def create_refund(reimbursement) @api.create_refund_for(reimbursement) end - def refund_and_create_new_transaction(order) - latest_order_transaction = OrderTransaction.latest_for(order) - - unless latest_order_transaction.refund_transaction - transaction_response = @api.create_refund_transaction_for(order) - latest_order_transaction.create_refund_transaction!( - transaction_id: transaction_response.transaction_id, - transaction_date: transaction_response.transaction_date - ) - end - - return if order.total.zero? - - if transaction_response = @api.create_transaction_for(order) + def refund_and_create_new_transaction(order, address, shipments) + if transaction_response = @api.create_transaction_for(order, address, shipments) order.taxjar_order_transactions.create!( transaction_id: transaction_response.transaction_id, transaction_date: transaction_response.transaction_date @@ -30,13 +18,13 @@ def refund_and_create_new_transaction(order) end end - def show_or_create_transaction(order) + def show_or_create_transaction(order, address, shipments) if transaction_response = @api.show_latest_transaction_for(order) SuperGood::SolidusTaxjar::OrderTransaction.find_by!( transaction_id: transaction_response.transaction_id ) else - transaction_response = @api.create_transaction_for(order) + transaction_response = @api.create_transaction_for(order, address, shipments) order.taxjar_order_transactions.create!( transaction_id: transaction_response.transaction_id, transaction_date: transaction_response.transaction_date diff --git a/lib/super_good/solidus_taxjar/tax_calculator.rb b/lib/super_good/solidus_taxjar/tax_calculator.rb index 79f76de5..cadccc64 100644 --- a/lib/super_good/solidus_taxjar/tax_calculator.rb +++ b/lib/super_good/solidus_taxjar/tax_calculator.rb @@ -9,20 +9,29 @@ def initialize(order, api: SuperGood::SolidusTaxjar.api) end def calculate - return no_tax if SuperGood::SolidusTaxjar.test_mode - return no_tax if incomplete_address?(order.tax_address) || order.line_items.none? - return no_tax unless taxable_order? order - return no_tax unless taxable_address? order.tax_address - - cache do - next no_tax unless taxjar_breakdown - - ::Spree::Tax::OrderTax.new( - order_id: order.id, - line_item_taxes: line_item_taxes, - shipment_taxes: shipment_taxes - ) - end + # return no_tax if SuperGood::SolidusTaxjar.test_mode + # return no_tax if incomplete_address?(order.tax_address) || order.line_items.none? + # return no_tax unless taxable_order? order + # return no_tax unless taxable_address? order.tax_address + + line_item_taxes = [] + shipment_taxes = [] + + order.shipments.group_by(&:address).each do |address, shipments| + @address = address + @shipments = shipments + + @taxjar_breakdown = cache do + api.tax_for(order, address, shipments).breakdown + end + + next unless @taxjar_breakdown + + line_item_taxes.concat(calculate_line_item_taxes) + shipment_taxes.concat(calculate_shipment_taxes) + end + + ::Spree::Tax::OrderTax.new(order_id: order.id, line_item_taxes:, shipment_taxes:) rescue => e exception_handler.call(e) no_tax @@ -32,18 +41,13 @@ def calculate attr_reader :order, :api - def line_item_taxes - @line_item_taxes ||= - taxjar_breakdown.line_items.map { |taxjar_line_item| + def calculate_line_item_taxes + @taxjar_breakdown.line_items.map { |taxjar_line_item| spree_line_item_id = taxjar_line_item.id.to_i - # Searching in memory because this association is loaded and most - # orders aren't going to have a huge number of line items. - spree_line_item = order.line_items.find { |li| li.id == spree_line_item_id } - ::Spree::Tax::ItemTax.new( item_id: spree_line_item_id, - label: line_item_tax_label(taxjar_line_item, spree_line_item), + label: line_item_tax_label(taxjar_line_item, @address), tax_rate: tax_rate, amount: taxjar_line_item.tax_collectable, included_in_price: false @@ -51,10 +55,9 @@ def line_item_taxes } end - def shipment_taxes - @shipment_taxes ||= - if taxjar_breakdown.shipping? && - (total_shipping_tax = taxjar_breakdown.shipping.tax_collectable) != 0 + def calculate_shipment_taxes + if @taxjar_breakdown.shipping? && + (total_shipping_tax = @taxjar_breakdown.shipping.tax_collectable) != 0 # Distribute shipping tax across shipments: # TaxJar does not provide a breakdown of shipping taxes, so we have @@ -62,17 +65,16 @@ def shipment_taxes # accounting for rounding errors. tax_items = [] remaining_tax = total_shipping_tax - shipments = order.shipments.to_a - total_shipping_cost = shipments.sum(&:total_before_tax) + total_shipping_cost = @shipments.sum(&:total_before_tax) - shipments[0...-1].each do |shipment| + @shipments[0...-1].each do |shipment| percentage_of_tax = shipment.total_before_tax / total_shipping_cost shipping_tax = (percentage_of_tax * total_shipping_tax).round(2) remaining_tax -= shipping_tax tax_items << ::Spree::Tax::ItemTax.new( item_id: shipment.id, - label: shipping_tax_label(shipment, shipping_tax), + label: shipping_tax_label(@taxjar_breakdown.shipping, shipment), tax_rate: tax_rate, amount: shipping_tax, included_in_price: false @@ -80,8 +82,8 @@ def shipment_taxes end tax_items << ::Spree::Tax::ItemTax.new( - item_id: shipments.last.id, - label: shipping_tax_label(shipments.last, remaining_tax), + item_id: @shipments.last.id, + label: shipping_tax_label(@taxjar_breakdown.shipping, @shipments.last), tax_rate: tax_rate, amount: remaining_tax, included_in_price: false @@ -93,14 +95,6 @@ def shipment_taxes end end - def taxjar_breakdown - @taxjar_breakdown ||= taxjar_tax.breakdown - end - - def taxjar_tax - @taxjar_taxes ||= api.tax_for(order) - end - def no_tax ::Spree::Tax::OrderTax.new( order_id: order.id, @@ -120,7 +114,7 @@ def tax_rate end def cache_key - SuperGood::SolidusTaxjar.cache_key.call(order) + SuperGood::SolidusTaxjar.cache_key.call(order, [order, @address, @shipments]) end def taxable_order?(order)