Skip to content

Commit

Permalink
feat (tax-integrations): auto retry failed invoice due to api limit (#…
Browse files Browse the repository at this point in the history
…2884)

## Context

Failed invoices due to api limit should be auto retried

## Description

This PR uses error detail extension
(#2878) to catch all failed
invoices due to API limit error and auto-retry them.

This is first phase for covering api limit errors. Later, we will turn
tax integration to be async and implement throttling mechanism for it.
  • Loading branch information
lovrocolic authored Nov 28, 2024
1 parent db25970 commit 4ee1e09
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 1 deletion.
19 changes: 19 additions & 0 deletions app/jobs/clock/retry_failed_invoices_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

module Clock
class RetryFailedInvoicesJob < ApplicationJob
include SentryCronConcern

queue_as 'clock'

def perform
Invoice
.failed
.joins(:error_details)
.where("error_details.details ? 'tax_error_message'")
.where("error_details.details ->> 'tax_error_message' ILIKE ?", "%API limit%").find_each do |i|
Invoices::RetryService.call(invoice: i)
end
end
end
end
5 changes: 4 additions & 1 deletion app/services/integrations/aggregator/taxes/base_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ def process_response(body)
result.succeeded_id = body['succeededInvoices'].first['id']
else
code, message = retrieve_error_details(body['failedInvoices'].first['validation_errors'])
deliver_tax_error_webhook(customer:, code:, message:)

unless message.include?('API limit')
deliver_tax_error_webhook(customer:, code:, message:)
end

result.service_failure!(code:, message:)
end
Expand Down
6 changes: 6 additions & 0 deletions clock.rb
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,10 @@ module Clockwork
.set(sentry: {"slug" => "lago_process_dunning_campaigns", "cron" => "45 */1 * * *"})
.perform_later
end

every(15.minutes, "schedule:retry_failed_invoices") do
Clock::RetryFailedInvoicesJob
.set(sentry: {"slug" => "lago_retry_failed_invoices", "cron" => '*/15 * * * *'})
.perform_later
end
end
86 changes: 86 additions & 0 deletions spec/jobs/clock/retry_failed_invoices_job_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# frozen_string_literal: true

require 'rails_helper'

describe Clock::RetryFailedInvoicesJob, job: true do
subject { described_class }

describe '.perform' do
let(:customer) { create(:customer) }
let(:failed_invoice) do
create(
:invoice,
status: :failed,
created_at: DateTime.parse('20 Jun 2022'),
customer:,
organization: customer.organization
)
end
let(:error_detail) do
create(
:error_detail,
owner: failed_invoice,
organization: customer.organization,
error_code: :tax_error,
details: {
tax_error: 'validationError',
tax_error_message: "You've exceeded your API limit of 10 per second"
}
)
end
let(:finalized_invoice) do
create(
:invoice,
status: :finalized,
created_at: DateTime.parse('20 Jun 2022'),
customer:,
organization: customer.organization
)
end

before do
failed_invoice
finalized_invoice
error_detail
allow(Invoices::RetryService).to receive(:call)
end

context 'with invalid product error' do
let(:error_detail) do
create(
:error_detail,
owner: failed_invoice,
organization: customer.organization,
error_code: :tax_error,
details: {
tax_error: 'productExternalIdUnknown'
}
)
end

it 'does not call the retry service' do
current_date = DateTime.parse('22 Jun 2022')

travel_to(current_date) do
described_class.perform_now

expect(Invoices::RetryService).not_to have_received(:call).with(invoice: failed_invoice)
expect(Invoices::RetryService).not_to have_received(:call).with(invoice: finalized_invoice)
end
end
end

context 'with api limit error' do
it 'calls the retry service' do
current_date = DateTime.parse('22 Jun 2022')

travel_to(current_date) do
described_class.perform_now

expect(Invoices::RetryService).to have_received(:call).with(invoice: failed_invoice)
expect(Invoices::RetryService).not_to have_received(:call).with(invoice: finalized_invoice)
end
end
end
end
end

0 comments on commit 4ee1e09

Please sign in to comment.