Skip to content

Commit

Permalink
fix(cashfree): Improve thirdparty provider error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
vincent-pochet committed Jan 10, 2025
1 parent b027231 commit f5a3239
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 2 deletions.
16 changes: 16 additions & 0 deletions app/controllers/concerns/api_errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,20 @@ def method_not_allowed_error(code:)
)
end

def thirdpary_error(error:)
render(
json: {
status: 422,
error: 'Unprocessable Entity',
code: 'third_party_error',
error_details: {
third_party: error.third_party,
thirdparty_error: error.error_message
}
}
)
end

def render_error_response(error_result)
case error_result.error
when BaseService::NotFoundFailure
Expand All @@ -69,6 +83,8 @@ def render_error_response(error_result)
forbidden_error(code: error_result.error.code)
when BaseService::UnauthorizedFailure
unauthorized_error(message: error_result.error.message)
when BaseService::ThirdPartyFailure
thirdpary_error(error: error_result.error)
else
raise(error_result.error)
end
Expand Down
15 changes: 15 additions & 0 deletions app/services/base_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,17 @@ def initialize(result, message:)
end
end

class ThirdPartyFailure < FailedResult
attr_reader :third_party, :error_message

def initialize(result, third_party:, error_message:)
@third_party = third_party
@error_message = error_message

super(result, "#{third_party}: #{error_message}")
end
end

class Result < OpenStruct
attr_reader :error

Expand Down Expand Up @@ -150,6 +161,10 @@ def unauthorized_failure!(message: "unauthorized")
fail_with_error!(UnauthorizedFailure.new(self, message:))
end

def third_party_failure!(third_party:, error_message:)
fail_with_error!(ThirdPartyFailure.new(self, third_party:, error_message:))
end

def raise_if_error!
return self if success?

Expand Down
3 changes: 2 additions & 1 deletion app/services/invoices/payments/cashfree_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ def generate_payment_url
result
rescue LagoHttpClient::HttpError => e
deliver_error_webhook(e)
result.service_failure!(code: e.error_code, message: e.error_body)

result.third_party_failure!(third_party: "Cashfree", error_message: e.error_body)
end

private
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def call
payment_url_result = Invoices::Payments::PaymentProviders::Factory.new_instance(invoice:).generate_payment_url

return payment_url_result unless payment_url_result.success?
return payment_url_result if payment_url_result.error.is_a?(BaseService::ThirdPartyFailure)

if payment_url_result.payment_url.blank?
return result.single_validation_failure!(error_code: 'payment_provider_error')
Expand Down
33 changes: 32 additions & 1 deletion spec/services/invoices/payments/cashfree_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@

describe ".generate_payment_url" do
let(:payment_links_response) { Net::HTTPResponse.new("1.0", "200", "OK") }
let(:payment_links_body) { {link_url: "https://payments-test.cashfree.com/links//U1mgll3c0e9g"}.to_json }

before do
cashfree_payment_provider
Expand All @@ -178,7 +179,7 @@
allow(cashfree_client).to receive(:post_with_response)
.and_return(payment_links_response)
allow(payment_links_response).to receive(:body)
.and_return({link_url: "https://payments-test.cashfree.com/links//U1mgll3c0e9g"}.to_json)
.and_return(payment_links_body)
end

it "generates payment url" do
Expand Down Expand Up @@ -206,5 +207,35 @@
expect(result.payment_url).to be_nil
end
end

context 'when payment url failed to generate' do
let(:payment_links_response) { Net::HTTPResponse.new("1.0", "400", "Bad Request") }
let(:payment_links_body) do
{
message: "Currency USD is not enabled",
code: "link_post_failed",
type: "invalid_request_error"
}.to_json
end

before do
cashfree_payment_provider
cashfree_customer

allow(LagoHttpClient::Client).to receive(:new)
.and_return(cashfree_client)
allow(cashfree_client).to receive(:post_with_response)
.and_raise(::LagoHttpClient::HttpError.new(payment_links_response.code, payment_links_body, nil))
end

it 'returns a third party error' do
result = cashfree_service.generate_payment_url

expect(result).not_to be_success
expect(result.error).to be_a(BaseService::ThirdPartyFailure)
expect(result.error.third_party).to eq('Cashfree')
expect(result.error.error_message).to eq(payment_links_body)
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,39 @@
end
end
end

context 'when provider service return a third party error' do
let(:payment_provider) { 'cashfree' }
let(:code) { 'cashfree_1' }

let(:payment_provider_service) { instance_double(PaymentRequests::Payments::CashfreeService) }

let(:error_result) do
BaseService::Result.new.tap do |result|
result.fail_with_error!(
BaseService::ThirdPartyFailure.new(
result,
third_party: 'Cashfree',
error_message: '{"code: "link_post_failed", "type": "invalid_request_error"}'
)
)
end
end

before do
allow(PaymentRequests::Payments::CashfreeService)
.to receive(:new)
.and_return(payment_provider_service)

allow(payment_provider_service).to receive(:generate_payment_url)
.and_return(error_result)
end

it 'returns a third party error' do
result = generate_payment_url_service.call

expect(result).to eq(error_result)
end
end
end
end

0 comments on commit f5a3239

Please sign in to comment.