Skip to content
Merged
Show file tree
Hide file tree
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
1 change: 0 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ gem "lograge"
gem "logstash-event"

# HTTP and Multipart support
gem "httparty"
gem "multipart-post"
gem "mutex_m"

Expand Down
5 changes: 0 additions & 5 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -337,10 +337,6 @@ GEM
hashie (5.0.0)
highline (3.1.2)
reline
httparty (0.23.1)
csv
mini_mime (>= 1.0.0)
multi_xml (>= 0.5.2)
httpclient (2.8.3)
i18n (1.14.7)
concurrent-ruby (~> 1.0)
Expand Down Expand Up @@ -1004,7 +1000,6 @@ DEPENDENCIES
graphql
graphql-pagination
guard-rspec
httparty
i18n-tasks!
jwt
kaminari-activerecord
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/webhooks_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def flutterwave
organization_id: params[:organization_id],
code: params[:code].presence,
body: request.body.read,
signature: request.headers["verif-hash"]
secret: request.headers["verif-hash"]
)

unless result.success?
Expand Down
5 changes: 2 additions & 3 deletions app/services/integrations/aggregator/base_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,8 @@ def provider_key
def throttle!(*providers)
providers.each do |provider_name|
if provider == provider_name.to_s
unless Throttling.for(provider_name.to_sym).check(:client, throttle_key)
raise BaseService::ThrottlingError.new(provider_name:)
end
raise BaseService::ThrottlingError.new(provider_name:) \
unless Throttling.for(provider_name.to_sym).check(:client, throttle_key)
end
end
end
Expand Down
21 changes: 11 additions & 10 deletions app/services/invoices/payments/flutterwave_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,17 @@ def payment_url
parsed_response["data"]["link"]
end

def create_checkout_session body = {
amount: invoice.total_amount_cents / 100.0,
tx_ref: invoice.id,
currency: invoice.currency.upcase,
redirect_url: success_redirect_url,
customer: customer_params,
customizations: customizations_params,
configuration: configuration_params,
meta: meta_params
}
def create_checkout_session
body = {
amount: Money.from_cents(invoice.total_amount_cents, invoice.currency).to_f,
tx_ref: invoice.id,
currency: invoice.currency.upcase,
redirect_url: success_redirect_url,
customer: customer_params,
customizations: customizations_params,
configuration: configuration_params,
meta: meta_params
}
http_client.post_with_response(body, headers)
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ def call

return result unless service_class

service_class.call!(organization_id: organization.id, event_json:)
begin
service_class.call!(organization_id: organization.id, event_json:)
rescue => e
Rails.logger.error("Flutterwave event processing error: #{e.message}")
end

result
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,42 @@ module PaymentProviders
module Flutterwave
class HandleIncomingWebhookService < BaseService
Result = BaseResult[:event]
def initialize(organization_id:, body:, signature:, code: nil)
def initialize(organization_id:, body:, secret:, code: nil)
@organization_id = organization_id
@body = body
@signature = signature
@secret = secret
@code = code

super
end

def call
organization = Organization.find_by(id: organization_id)

payment_provider_result = PaymentProviders::FindService.call(
organization_id:,
code:,
payment_provider_type: "flutterwave"
)
return payment_provider_result unless payment_provider_result.success?

webhook_secret = payment_provider_result.payment_provider.webhook_secret
return result.service_failure!(code: "webhook_error", message: "Missing webhook secret") if webhook_secret.blank?
return result.service_failure!(code: "webhook_error", message: "Webhook secret is missing") if webhook_secret.blank?

unless webhook_secret == signature
return result.service_failure!(code: "webhook_error", message: "Invalid signature")
unless webhook_secret == secret
return result.service_failure!(code: "webhook_error", message: "Invalid webhook secret")
end

PaymentProviders::Flutterwave::HandleEventJob.perform_later(organization:, event: body)
PaymentProviders::Flutterwave::HandleEventJob.perform_later(
organization: payment_provider_result.payment_provider.organization,
event: body
)

result.event = body
result
end

private

attr_reader :organization_id, :body, :signature, :code
attr_reader :organization_id, :body, :secret, :code
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,23 @@ class ChargeCompletedService < BaseService
def initialize(organization_id:, event_json:)
@organization_id = organization_id
@event_json = event_json

super
end

def call
return result unless SUCCESS_STATUSES.include?(transaction_status)
return result if provider_payment_id.nil?

# Validate payable_type first to raise NameError for invalid types
payment_service_class

verified_transaction = verify_transaction
return result unless verified_transaction
payment_service_class.new(nil).update_payment_status(

payable = find_payable
return result unless payable

payment_service_class.new(payable:).update_payment_status(
organization_id:,
status: verified_transaction[:status],
flutterwave_payment: PaymentProviders::FlutterwaveProvider::FlutterwavePayment.new(
Expand Down Expand Up @@ -69,6 +75,15 @@ def payment_service_class
end
end

def find_payable
case payable_type
when "Invoice"
Invoice.find_by(id: provider_payment_id)
when "PaymentRequest"
PaymentRequest.find_by(id: provider_payment_id)
end
end

def verify_transaction
Organization.find(organization_id)
payment_provider_result = PaymentProviders::FindService.call(
Expand Down Expand Up @@ -112,6 +127,7 @@ def build_metadata(verified_transaction)
lago_invoice_id: provider_payment_id,
lago_payable_type: payable_type,
flutterwave_transaction_id: verified_transaction[:id],
flw_ref: verified_transaction[:reference],
reference: verified_transaction[:reference],
amount: verified_transaction[:amount],
currency: verified_transaction[:currency],
Expand Down
7 changes: 0 additions & 7 deletions app/services/payment_providers/flutterwave_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ def create_or_update(**args)
flutterwave_provider.code = args[:code] if args.key?(:code)
flutterwave_provider.name = args[:name] if args.key?(:name)
flutterwave_provider.save!

if payment_provider_code_changed?(flutterwave_provider, old_code, args)
flutterwave_provider.customers.update_all(payment_provider_code: args[:code]) # rubocop:disable Rails/SkipsModelValidations
end
Expand All @@ -36,11 +35,5 @@ def create_or_update(**args)
rescue ActiveRecord::RecordInvalid => e
result.record_validation_failure!(record: e.record)
end

private

def payment_provider_code_changed?(provider, old_code, args)
old_code != args[:code]
end
end
end
5 changes: 2 additions & 3 deletions app/services/payment_requests/payments/flutterwave_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def payment_url

def create_checkout_session
body = {
amount: payable.total_amount_cents / 100.0,
amount: Money.from_cents(payable.total_amount_cents, payable.currency).to_f,
tx_ref: "lago_payment_request_#{payable.id}",
currency: payable.currency.upcase,
redirect_url: success_redirect_url,
Expand Down Expand Up @@ -137,14 +137,13 @@ def http_client

def deliver_error_webhook(http_error)
return unless payable.organization.webhook_endpoints.any?

SendWebhookJob.perform_later(
"payment_request.payment_failure",
payable,
provider_customer_id: flutterwave_customer&.provider_customer_id,
provider_error: {
message: http_error.message,
error_code: http_error.code
error_code: http_error.error_code
}
)
end
Expand Down
24 changes: 16 additions & 8 deletions config/database.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,25 @@ default: &default
development:
primary:
<<: *default
url: <%= ENV['DATABASE_URL'] %>
host: db
username: lago
password: changeme
database: lago
port: 5432
events:
<<: *default
url: <%= ENV['DATABASE_URL'] %>
host: db
username: lago
password: changeme
database: lago
port: 5432
clickhouse:
adapter: clickhouse
database: <%= ENV.fetch('LAGO_CLICKHOUSE_DATABASE', 'default') %>
host: <%= ENV.fetch('LAGO_CLICKHOUSE_HOST', 'clickhouse') %>
port: <%= ENV.fetch('LAGO_CLICKHOUSE_PORT', 8123) %>
username: <%= ENV.fetch('LAGO_CLICKHOUSE_USERNAME', 'default') %>
password: <%= ENV.fetch('LAGO_CLICKHOUSE_PASSWORD', 'default') %>
database: default
host: clickhouse
port: 8123
username: default
password: default
migrations_paths: db/clickhouse_migrate
debug: true
database_tasks: <% if ENV['LAGO_CLICKHOUSE_MIGRATIONS_ENABLED'].present? %> true <% else %> false <% end %>
Expand Down Expand Up @@ -86,4 +94,4 @@ production:
ssl: <%= ENV.fetch('LAGO_CLICKHOUSE_SSL', false) %>
migrations_paths: db/clickhouse_migrate
debug: false
database_tasks: <% if ENV['LAGO_CLICKHOUSE_MIGRATIONS_ENABLED'].present? %> true <% else %> false <% end %>
database_tasks: <% if ENV['LAGO_CLICKHOUSE_MIGRATIONS_ENABLED'].present? %> true <% else %> false <% end %>
7 changes: 7 additions & 0 deletions spec/factories/payment_provider_customers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@
customer
organization { customer.organization }

provider_customer_id { SecureRandom.uuid }
end
factory :flutterwave_customer, class: "PaymentProviderCustomers::FlutterwaveCustomer" do
customer
organization { customer.organization }
payment_provider { association(:flutterwave_provider, organization: organization) }

provider_customer_id { SecureRandom.uuid }
end
end
42 changes: 42 additions & 0 deletions spec/jobs/payment_providers/flutterwave/handle_event_job_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# frozen_string_literal: true

require "rails_helper"

RSpec.describe PaymentProviders::Flutterwave::HandleEventJob do
subject(:handle_event_job) { described_class.new }

let(:organization) { create(:organization) }
let(:event_json) { {event: "charge.completed", data: {}}.to_json }

describe "#perform" do
it "calls the HandleEventService" do
allow(PaymentProviders::Flutterwave::HandleEventService)
.to receive(:call!)

handle_event_job.perform(organization:, event: event_json)

expect(PaymentProviders::Flutterwave::HandleEventService)
.to have_received(:call!)
.with(organization:, event_json: event_json)
end
end

describe "queue configuration" do
context "when SIDEKIQ_PAYMENTS is true" do
before { ENV["SIDEKIQ_PAYMENTS"] = "true" }
after { ENV.delete("SIDEKIQ_PAYMENTS") }

it "uses the payments queue" do
expect(described_class.queue_name).to eq("payments")
end
end

context "when SIDEKIQ_PAYMENTS is false or not set" do
before { ENV.delete("SIDEKIQ_PAYMENTS") }

it "uses the providers queue" do
expect(described_class.queue_name).to eq("providers")
end
end
end
end
Loading
Loading