Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat (tax-integrations): handle current usage with tax provider #2468

Merged
merged 4 commits into from
Aug 29, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ def initialize(integration:, customer:, invoice:, integration_customer:, fees: [
@customer = customer
@integration_customer = integration_customer
@invoice = invoice
@fees = fees.is_a?(Array) ? fees : fees.order(created_at: :asc)
@fees = (fees.is_a?(Array) || !fees&.first&.persisted?) ? fees : fees.order(created_at: :asc)
end

def body
49 changes: 48 additions & 1 deletion app/services/invoices/customer_usage_service.rb
Original file line number Diff line number Diff line change
@@ -55,7 +55,12 @@ def compute_usage
)

add_charge_fees
compute_amounts

if customer_provider_taxation?
compute_amounts_with_provider_taxes
else
compute_amounts
end

format_usage
end
@@ -136,10 +141,48 @@ def compute_amounts
invoice.total_amount_cents = invoice.fees_amount_cents + invoice.taxes_amount_cents
end

def compute_amounts_with_provider_taxes
invoice.fees_amount_cents = invoice.fees.sum(&:amount_cents)

taxes_result = Rails.cache.read(provider_taxes_cache_key)

unless taxes_result
# Call the service if the cache is empty
taxes_result = Integrations::Aggregator::Taxes::Invoices::CreateDraftService.call(invoice:, fees: invoice.fees)

# Cache the result only if it's successful
Rails.cache.write(provider_taxes_cache_key, taxes_result, expires_in: 24.hours) if taxes_result.success?
end

return result.validation_failure!(errors: {tax_error: [taxes_result.error.code]}) unless taxes_result.success?

result.fees_taxes = taxes_result.fees

invoice.fees.each do |fee|
fee_taxes = result.fees_taxes.find { |item| item.item_id == fee.item_id }

res = Fees::ApplyProviderTaxesService.call(fee:, fee_taxes:)
res.raise_if_error!
end

res = Invoices::ApplyProviderTaxesService.call(invoice:, provider_taxes: result.fees_taxes)
res.raise_if_error!

invoice.total_amount_cents = invoice.fees_amount_cents + invoice.taxes_amount_cents
end

def charge_cache_key(charge)
Subscriptions::ChargeCacheService.new(subscription:, charge:).cache_key
end

def provider_taxes_cache_key
[
'provider-taxes',
subscription.id,
plan.updated_at.iso8601
].join('/')
end

def charge_cache_expiration
(boundaries[:charges_to_datetime] - Time.current).to_i.seconds
end
@@ -156,5 +199,9 @@ def format_usage
fees: invoice.fees
)
end

def customer_provider_taxation?
@customer_provider_taxation ||= invoice.customer.anrok_customer
end
end
end
85 changes: 85 additions & 0 deletions spec/services/invoices/customer_usage_service_spec.rb
Original file line number Diff line number Diff line change
@@ -93,6 +93,91 @@
end
end

context 'when there is tax provider integration' do
let(:integration) { create(:anrok_integration, organization:) }
let(:integration_customer) { create(:anrok_customer, integration:, customer:) }
let(:response) { instance_double(Net::HTTPOK) }
let(:lago_client) { instance_double(LagoHttpClient::Client) }
let(:endpoint) { 'https://api.nango.dev/v1/anrok/draft_invoices' }
let(:body) do
p = Rails.root.join('spec/fixtures/integration_aggregator/taxes/invoices/success_response.json')
json = File.read(p)

# setting item_id based on the test example
response = JSON.parse(json)
response['succeededInvoices'].first['fees'].last['item_id'] = charge.billable_metric.id

response.to_json
end
let(:integration_collection_mapping) do
create(
:netsuite_collection_mapping,
integration:,
mapping_type: :fallback_item,
settings: {external_id: '1', external_account_code: '11', external_name: ''}
)
end

before do
integration_collection_mapping
integration_customer

allow(LagoHttpClient::Client).to receive(:new).with(endpoint).and_return(lago_client)
allow(lago_client).to receive(:post_with_response).and_return(response)
allow(response).to receive(:body).and_return(body)
end

it 'initializes an invoice' do
result = usage_service.call

aggregate_failures do
expect(result).to be_success
expect(result.invoice).to be_a(Invoice)

expect(result.usage).to have_attributes(
from_datetime: Time.current.beginning_of_month.iso8601,
to_datetime: Time.current.end_of_month.iso8601,
issuing_date: Time.zone.today.end_of_month.iso8601,
currency: 'EUR',
amount_cents: 2532, # 1266 * 2,
taxes_amount_cents: 253, # 2532 * 0.1
total_amount_cents: 2785
)
expect(result.usage.fees.size).to eq(1)
expect(result.usage.fees.first.charge.invoice_display_name).to eq(charge.invoice_display_name)
end
end

it 'uses the Rails cache' do
key = [
'provider-taxes',
subscription.id,
plan.updated_at.iso8601
].join('/')

expect do
usage_service.call
end.to change { Rails.cache.exist?(key) }.from(false).to(true)
end

context 'when there is error received from the provider' do
let(:body) do
p = Rails.root.join('spec/fixtures/integration_aggregator/taxes/invoices/failure_response.json')
File.read(p)
end

it 'returns tax error' do
result = usage_service.call

aggregate_failures do
expect(result).not_to be_success
expect(result.error).to be_a(BaseService::ValidationFailure)
expect(result.error.messages[:tax_error]).to eq(['taxDateTooFarInFuture'])
end
end
end
end

context 'with subscription started in current billing period' do
before { subscription.update!(started_at: Time.zone.today) }