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(dunning): Create Adyen payment #2474

Merged
merged 6 commits into from
Aug 26, 2024
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
5 changes: 5 additions & 0 deletions app/models/payment_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ class PaymentRequest < ApplicationRecord
def invoice_ids
applied_invoices.pluck(:invoice_id)
end

def increment_payment_attempts!
increment(:payment_attempts)
save!
ancorcruz marked this conversation as resolved.
Show resolved Hide resolved
end
end

# == Schema Information
Expand Down
174 changes: 174 additions & 0 deletions app/services/payment_requests/payments/adyen_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# frozen_string_literal: true

module PaymentRequests
module Payments
class AdyenService < BaseService
include Lago::Adyen::ErrorHandlable
include Customers::PaymentProviderFinder

PENDING_STATUSES = %w[AuthorisedPending Received].freeze
SUCCESS_STATUSES = %w[Authorised SentForSettle SettleScheduled Settled Refunded].freeze
FAILED_STATUSES = %w[Cancelled CaptureFailed Error Expired Refused].freeze

def initialize(payable = nil)
@payable = payable

super(nil)
end

def create
result.payable = payable
return result unless should_process_payment?

unless payable.total_amount_cents.positive?
update_payable_payment_status(payment_status: :succeeded)
return result
end

payable.increment_payment_attempts!

res = create_adyen_payment
return result unless res

adyen_success, _adyen_error = handle_adyen_response(res)
return result unless adyen_success

payment = Payment.new(
payable: payable,
payment_provider_id: adyen_payment_provider.id,
payment_provider_customer_id: customer.adyen_customer.id,
amount_cents: payable.total_amount_cents,
amount_currency: payable.currency.upcase,
provider_payment_id: res.response['pspReference'],
status: res.response['resultCode']
)

ActiveRecord::Base.transaction do
payment.save!

payable_payment_status = payable_payment_status(payment.status)
update_payable_payment_status(payment_status: payable_payment_status)
update_invoices_payment_status(payment_status: payable_payment_status)
end

Integrations::Aggregator::Payments::CreateJob.perform_later(payment:) if payment.should_sync_payment?

result.payment = payment
result
end

private

attr_accessor :payable

delegate :organization, :customer, to: :payable

def should_process_payment?
return false if payable.payment_succeeded?
return false if adyen_payment_provider.blank?

!!customer&.adyen_customer&.provider_customer_id
end

def client
@client ||= Adyen::Client.new(
api_key: adyen_payment_provider.api_key,
env: adyen_payment_provider.environment,
live_url_prefix: adyen_payment_provider.live_prefix
)
end

def adyen_payment_provider
@adyen_payment_provider ||= payment_provider(customer)
end

def update_payment_method_id
result = client.checkout.payments_api.payment_methods(
Lago::Adyen::Params.new(payment_method_params).to_h
).response

payment_method_id = result['storedPaymentMethods']&.first&.dig('id')
customer.adyen_customer.update!(payment_method_id:) if payment_method_id
end

def create_adyen_payment
update_payment_method_id

client.checkout.payments_api.payments(Lago::Adyen::Params.new(payment_params).to_h)
rescue Adyen::AuthenticationError, Adyen::ValidationError => e
deliver_error_webhook(e)
update_payable_payment_status(payment_status: :failed, deliver_webhook: false)
nil
rescue Adyen::AdyenError => e
deliver_error_webhook(e)
update_payable_payment_status(payment_status: :failed, deliver_webhook: false)
raise e
end

def payment_method_params
{
merchantAccount: adyen_payment_provider.merchant_account,
shopperReference: customer.adyen_customer.provider_customer_id
}
end

def payment_params
prms = {
amount: {
currency: payable.currency.upcase,
value: payable.total_amount_cents
},
reference: payable.id,
paymentMethod: {
type: 'scheme',
storedPaymentMethodId: customer.adyen_customer.payment_method_id
},
shopperReference: customer.adyen_customer.provider_customer_id,
merchantAccount: adyen_payment_provider.merchant_account,
shopperInteraction: 'ContAuth',
recurringProcessingModel: 'UnscheduledCardOnFile'
}
prms[:shopperEmail] = customer.email if customer.email
prms
end

def payable_payment_status(payment_status)
return :pending if PENDING_STATUSES.include?(payment_status)
return :succeeded if SUCCESS_STATUSES.include?(payment_status)
return :failed if FAILED_STATUSES.include?(payment_status)

payment_status
end

def update_payable_payment_status(payment_status:, deliver_webhook: true)
payable.update!(
payment_status:,
ready_for_payment_processing: payment_status.to_sym != :succeeded
)
end

def update_invoices_payment_status(payment_status:, deliver_webhook: true)
payable.invoices.each do |invoice|
Invoices::UpdateService.call(
invoice:,
params: {
payment_status:,
ready_for_payment_processing: payment_status.to_sym != :succeeded
},
webhook_notification: deliver_webhook
).raise_if_error!
end
end

def deliver_error_webhook(adyen_error)
DeliverErrorWebhookService.call_async(payable, {
provider_customer_id: customer.adyen_customer.provider_customer_id,
provider_error: {
message: adyen_error.msg,
error_code: adyen_error.code
}
})
end
end
end
end
29 changes: 14 additions & 15 deletions app/services/payment_requests/payments/stripe_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def create
return result
end

increment_payment_attempts
payable.increment_payment_attempts!

stripe_result = create_stripe_payment
# NOTE: return if payment was not processed
Expand All @@ -40,16 +40,20 @@ def create
provider_payment_id: stripe_result.id,
status: stripe_result.status
)
payment.save!

update_payable_payment_status(
payment_status: payable_payment_status(payment.status),
processing: payment.status == 'processing'
)
update_invoices_payment_status(
payment_status: payable_payment_status(payment.status),
processing: payment.status == 'processing'
)
ActiveRecord::Base.transaction do
payment.save!

payable_payment_status = payable_payment_status(payment.status)
update_payable_payment_status(
payment_status: payable_payment_status,
processing: payment.status == 'processing'
)
update_invoices_payment_status(
payment_status: payable_payment_status,
processing: payment.status == 'processing'
)
end

result.payment = payment
result
Expand Down Expand Up @@ -201,11 +205,6 @@ def update_invoices_payment_status(payment_status:, deliver_webhook: true, proce
end
end

def increment_payment_attempts
payable.increment(:payment_attempts)
payable.save!
end

def deliver_error_webhook(stripe_error)
DeliverErrorWebhookService.call_async(payable, {
provider_customer_id: customer.stripe_customer.provider_customer_id,
Expand Down
10 changes: 10 additions & 0 deletions spec/models/payment_request_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,14 @@
expect(payment_request.invoice_ids).to eq(invoices.map(&:id))
end
end

describe "#increment_payment_attempts!" do
let(:payment_request) { create :payment_request }

it "updates payment_attempts attribute +1" do
expect { payment_request.increment_payment_attempts! }
.to change { payment_request.reload.payment_attempts }
.by(1)
end
end
end
Loading