Skip to content

Commit

Permalink
Add PaymentRequest support
Browse files Browse the repository at this point in the history
  • Loading branch information
vincent-pochet committed Dec 20, 2024
1 parent 0047ca5 commit 606ad89
Show file tree
Hide file tree
Showing 20 changed files with 1,316 additions and 139 deletions.
10 changes: 6 additions & 4 deletions app/jobs/payment_providers/cashfree/handle_event_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
module PaymentProviders
module Cashfree
class HandleEventJob < ApplicationJob
queue_as 'providers'
queue_as "providers"

def perform(event_json:)
result = PaymentProviders::CashfreeService.new.handle_event(event_json:)
result.raise_if_error!
def perform(organization:, event:)
PaymentProviders::Cashfree::HandleEventService.call!(
organization:,
event_json: event
)
end
end
end
Expand Down
6 changes: 4 additions & 2 deletions app/models/payment_providers/cashfree_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

module PaymentProviders
class CashfreeProvider < BaseProvider
SUCCESS_REDIRECT_URL = 'https://cashfree.com/'
CashfreePayment = Data.define(:id, :status, :metadata)

SUCCESS_REDIRECT_URL = "https://cashfree.com/"
API_VERSION = "2023-08-01"
BASE_URL = (Rails.env.production? ? 'https://api.cashfree.com/pg/links' : 'https://sandbox.cashfree.com/pg/links')
BASE_URL = (Rails.env.production? ? "https://api.cashfree.com/pg/links" : "https://sandbox.cashfree.com/pg/links")

validates :client_id, presence: true
validates :client_secret, presence: true
Expand Down
46 changes: 31 additions & 15 deletions app/services/invoices/payments/cashfree_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,23 @@ class CashfreeService < BaseService
def initialize(invoice = nil)
@invoice = invoice

super(nil)
super
end

def update_payment_status(provider_payment_id:, status:)
payment = Payment.find_by(provider_payment_id:)
return result.not_found_failure!(resource: 'cashfree_payment') unless payment
def update_payment_status(organization_id:, status:, cashfree_payment:)
payment = if cashfree_payment.metadata[:payment_type] == "one-time"
create_payment(cashfree_payment)
else
Payment.find_by(provider_payment_id: cashfree_payment.id)
end
return result.not_found_failure!(resource: "cashfree_payment") unless payment

result.payment = payment
result.invoice = payment.payable
return result if payment.payable.payment_succeeded?

invoice_payment_status = invoice_payment_status(status)

payment.update!(status: invoice_payment_status)
update_invoice_payment_status(payment_status: invoice_payment_status)
payment.update!(status:)
update_invoice_payment_status(payment_status: invoice_payment_status(status))

result
rescue BaseService::FailedResult => e
Expand All @@ -36,9 +38,8 @@ def update_payment_status(provider_payment_id:, status:)
def generate_payment_url
return result unless should_process_payment?

res = create_post_request(payment_url_params)

result.payment_url = JSON.parse(res.body)["link_url"]
payment_link_response = create_payment_link(payment_url_params)
result.payment_url = JSON.parse(payment_link_response.body)["link_url"]

result
rescue LagoHttpClient::HttpError => e
Expand All @@ -52,21 +53,36 @@ def generate_payment_url

delegate :organization, :customer, to: :invoice

def create_payment(cashfree_payment)
@invoice = Invoice.find_by(id: cashfree_payment.metadata[:lago_invoice_id])

increment_payment_attempts

Payment.new(
payable: @invoice,
payment_provider_id: cashfree_payment_provider.id,
payment_provider_customer_id: customer.cashfree_customer.id,
amount_cents: @invoice.total_amount_cents,
amount_currency: @invoice.currency,
provider_payment_id: cashfree_payment.id
)
end

def should_process_payment?
return false if invoice.payment_succeeded? || invoice.voided?
return false if cashfree_payment_provider.blank?

customer&.cashfree_customer&.id
!!customer&.cashfree_customer&.id
end

def client
@client ||= LagoHttpClient::Client.new(::PaymentProviders::CashfreeProvider::BASE_URL)
end

def create_post_request(body)
def create_payment_link(body)
client.post_with_response(body, {
"accept" => 'application/json',
"content-type" => 'application/json',
"accept" => "application/json",
"content-type" => "application/json",
"x-client-id" => cashfree_payment_provider.client_id,
"x-client-secret" => cashfree_payment_provider.client_secret,
"x-api-version" => ::PaymentProviders::CashfreeProvider::API_VERSION
Expand Down
32 changes: 32 additions & 0 deletions app/services/payment_providers/cashfree/handle_event_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

module PaymentProviders
module Cashfree
class HandleEventService < BaseService
EVENT_MAPPING = {
"PAYMENT_LINK_EVENT" => PaymentProviders::Cashfree::Webhooks::PaymentLinkEventService
}.freeze

def initialize(organization:, event_json:)
@organization = organization
@event_json = event_json

super
end

def call
EVENT_MAPPING[event["type"]].call!(organization_id: organization.id, event_json:)

result
end

private

attr_reader :organization, :event_json

def event
@event ||= JSON.parse(event_json)
end
end
end
end
24 changes: 24 additions & 0 deletions app/services/payment_providers/cashfree/webhooks/base_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

module PaymentProviders
module Cashfree
module Webhooks
class BaseService < BaseService
def initialize(organization_id:, event_json:)
@organization = Organization.find(organization_id)
@event_json = event_json

super
end

private

attr_reader :organization, :event_json

def event
@event ||= JSON.parse(event_json)
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# frozen_string_literal: true

module PaymentProviders
module Cashfree
module Webhooks
class PaymentLinkEventService < BaseService
LINK_STATUS_ACTIONS = %w[PAID].freeze

PAYMENT_SERVICE_CLASS_MAP = {
"Invoice" => Invoices::Payments::CashfreeService,
"PaymentRequest" => PaymentRequests::Payments::CashfreeService
}.freeze

def call
return result unless LINK_STATUS_ACTIONS.include?(link_status)
return result if provider_payment_id.nil?

payment_service_class.new.update_payment_status(
organization_id: organization.id,
status: link_status,
cashfree_payment: PaymentProviders::CashfreeProvider::CashfreePayment.new(
id: provider_payment_id,
status: link_status,
metadata: event.dig("data", "link_notes").to_h.symbolize_keys || {}
)
).raise_if_error!
end

private

def payment_service_class
PAYMENT_SERVICE_CLASS_MAP.fetch(payable_type || "Invoice") do
raise NameError, "Invalid lago_payable_type: #{payable_type}"
end
end

def link_status
@link_status ||= event.dig("data", "link_status")
end

def provider_payment_id
@provider_payment_id ||= event.dig("data", "link_notes", "lago_invoice_id") || event.dig("data", "link_notes", "lago_payable_id")
end

def payable_type
@payable_type ||= event.dig("data", "link_notes", "lago_payable_type")
end
end
end
end
end
27 changes: 3 additions & 24 deletions app/services/payment_providers/cashfree_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ def create_or_update(**args)
end

def handle_incoming_webhook(organization_id:, body:, timestamp:, signature:, code: nil)
organization = Organization.find_by(id: organization_id)

payment_provider_result = PaymentProviders::FindService.call(
organization_id:,
code:,
Expand All @@ -59,33 +61,10 @@ def handle_incoming_webhook(organization_id:, body:, timestamp:, signature:, cod
return result.service_failure!(code: "webhook_error", message: "Invalid signature")
end

PaymentProviders::Cashfree::HandleEventJob.perform_later(event_json: body)
PaymentProviders::Cashfree::HandleEventJob.perform_later(organization:, event: body)

result.event = body
result
end

def handle_event(event_json:)
event = JSON.parse(event_json)
event_type = event["type"]

case event_type
when "PAYMENT_LINK_EVENT"
link_status = event.dig("data", "link_status")
provider_payment_id = event.dig("data", "link_notes", "lago_invoice_id")

if LINK_STATUS_ACTIONS.include?(link_status) && !provider_payment_id.nil?
update_payment_status_result = Invoices::Payments::CashfreeService
.new.update_payment_status(
provider_payment_id: provider_payment_id,
status: link_status
)

return update_payment_status_result unless update_payment_status_result.success?
end
end

result.raise_if_error!
end
end
end
Loading

0 comments on commit 606ad89

Please sign in to comment.