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: centralize determine payment status #2990

Merged
merged 14 commits into from
Dec 20, 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
4 changes: 4 additions & 0 deletions app/models/payment_providers/adyen_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ module PaymentProviders
class AdyenProvider < BaseProvider
SUCCESS_REDIRECT_URL = 'https://www.adyen.com/'

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

validates :api_key, :merchant_account, presence: true
validates :success_redirect_url, adyen_url: true, allow_nil: true, length: {maximum: 1024}

Expand Down
8 changes: 8 additions & 0 deletions app/models/payment_providers/base_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ class BaseProvider < ApplicationRecord
validates :name, presence: true

settings_accessors :webhook_secret, :success_redirect_url

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

payment_status
brunomiguelpinto marked this conversation as resolved.
Show resolved Hide resolved
end
end
end

Expand Down
4 changes: 4 additions & 0 deletions app/models/payment_providers/gocardless_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ module PaymentProviders
class GocardlessProvider < BaseProvider
SUCCESS_REDIRECT_URL = 'https://gocardless.com/'

PENDING_STATUSES = %w[pending_customer_approval pending_submission submitted confirmed].freeze
SUCCESS_STATUSES = %w[paid_out].freeze
FAILED_STATUSES = %w[cancelled customer_approval_denied failed charged_back].freeze

validates :access_token, presence: true
validates :success_redirect_url, url: true, allow_nil: true, length: {maximum: 1024}

Expand Down
28 changes: 19 additions & 9 deletions app/models/payment_providers/stripe_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,27 @@ class StripeProvider < BaseProvider
SUCCESS_REDIRECT_URL = 'https://stripe.com/'

# NOTE: find the complete list of event types at https://stripe.com/docs/api/events/types
WEBHOOKS_EVENTS = [
'setup_intent.succeeded',
'payment_intent.payment_failed',
'payment_intent.succeeded',
'payment_method.detached',
'charge.refund.updated',
'customer.updated',
'charge.succeeded',
'charge.dispute.closed'
WEBHOOKS_EVENTS = %w[
setup_intent.succeeded
payment_intent.payment_failed
payment_intent.succeeded
payment_method.detached
charge.refund.updated
customer.updated
charge.succeeded
charge.dispute.closed
].freeze

PENDING_STATUSES = %w[
processing
requires_capture
requires_action
requires_confirmation
requires_payment_method
].freeze
SUCCESS_STATUSES = %w[succeeded].freeze
FAILED_STATUSES = %w[canceled].freeze

validates :secret_key, presence: true
validates :success_redirect_url, url: true, allow_nil: true, length: {maximum: 1024}

Expand Down
16 changes: 2 additions & 14 deletions app/services/payment_requests/payments/adyen_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ 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

Expand Down Expand Up @@ -48,7 +44,7 @@ def create

payment.save!

payable_payment_status = payable_payment_status(payment.status)
payable_payment_status = adyen_payment_provider.determine_payment_status(payment.status)
update_payable_payment_status(payment_status: payable_payment_status)
update_invoices_payment_status(payment_status: payable_payment_status)

Expand Down Expand Up @@ -91,7 +87,7 @@ def update_payment_status(provider_payment_id:, status:, metadata: {})

payment.update!(status:)

payable_payment_status = payable_payment_status(status)
payable_payment_status = payment.payment_provider&.determine_payment_status(status)
update_payable_payment_status(payment_status: payable_payment_status)
update_invoices_payment_status(payment_status: payable_payment_status)
reset_customer_dunning_campaign_status(payable_payment_status)
Expand Down Expand Up @@ -207,14 +203,6 @@ def success_redirect_url
adyen_payment_provider.success_redirect_url.presence || ::PaymentProviders::AdyenProvider::SUCCESS_REDIRECT_URL
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)
UpdateService.call(
payable: result.payable,
Expand Down
17 changes: 2 additions & 15 deletions app/services/payment_requests/payments/gocardless_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,6 @@ def code
end
end

PENDING_STATUSES = %w[pending_customer_approval pending_submission submitted confirmed]
.freeze
SUCCESS_STATUSES = %w[paid_out].freeze
FAILED_STATUSES = %w[cancelled customer_approval_denied failed charged_back].freeze

def initialize(payable = nil)
@payable = payable

Expand Down Expand Up @@ -55,7 +50,7 @@ def create

payment.save!

payable_payment_status = payable_payment_status(payment.status)
payable_payment_status = gocardless_payment_provider.determine_payment_status(payment.status)
update_payable_payment_status(payment_status: payable_payment_status)
update_invoices_payment_status(payment_status: payable_payment_status)

Expand All @@ -81,7 +76,7 @@ def update_payment_status(provider_payment_id:, status:)

payment.update!(status:)

payable_payment_status = payable_payment_status(status)
payable_payment_status = payment.payment_provider.determine_payment_status(status)
update_payable_payment_status(payment_status: payable_payment_status)
update_invoices_payment_status(payment_status: payable_payment_status)
reset_customer_dunning_campaign_status(payable_payment_status)
Expand Down Expand Up @@ -162,14 +157,6 @@ def create_gocardless_payment
nil
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)
UpdateService.call(
payable: result.payable,
Expand Down
17 changes: 2 additions & 15 deletions app/services/payment_requests/payments/stripe_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@ module Payments
class StripeService < BaseService
include Customers::PaymentProviderFinder

PENDING_STATUSES = %w[processing requires_capture requires_action requires_confirmation requires_payment_method]
.freeze
SUCCESS_STATUSES = %w[succeeded].freeze
FAILED_STATUSES = %w[canceled].freeze

def initialize(payable = nil)
@payable = payable

Expand Down Expand Up @@ -43,7 +38,7 @@ def create

payment.save!

payable_payment_status = payable_payment_status(payment.status)
payable_payment_status = stripe_payment_provider.determine_payment_status(payment.status)
update_payable_payment_status(
payment_status: payable_payment_status,
processing: payment.status == "processing"
Expand Down Expand Up @@ -111,7 +106,7 @@ def update_payment_status(organization_id:, status:, stripe_payment:)
payment.update!(status:)

processing = status == "processing"
payment_status = payable_payment_status(status)
payment_status = payment.payment_provider.determine_payment_status(status)
update_payable_payment_status(payment_status:, processing:)
update_invoices_payment_status(payment_status:, processing:)
reset_customer_dunning_campaign_status(payment_status)
Expand Down Expand Up @@ -220,14 +215,6 @@ def description
"#{organization.name} - Overdue invoices"
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&.to_sym
end

def update_payable_payment_status(payment_status:, deliver_webhook: true, processing: false)
UpdateService.call(
payable: result.payable,
Expand Down
22 changes: 22 additions & 0 deletions db/migrate/20241220095049_backfill_payable_payment_status.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

class BackfillPayablePaymentStatus < ActiveRecord::Migration[7.1]
def change
provider_types = PaymentProviders::BaseProvider.distinct.pluck(:type)
provider_types.each do |provider_type|
provider_class = provider_type.constantize

payments = Payment.joins(:payment_provider)
.where(payment_providers: {type: provider_type}, status: provider_class::PENDING_STATUSES)
payments.update_all(payable_payment_status: :pending) # rubocop:disable Rails/SkipsModelValidations

payments = Payment.joins(:payment_provider)
.where(payment_providers: {type: provider_type}, status: provider_class::SUCCESS_STATUSES)
payments.update_all(payable_payment_status: :succeeded) # rubocop:disable Rails/SkipsModelValidations

payments = Payment.joins(:payment_provider)
.where(payment_providers: {type: provider_type}, status: provider_class::FAILED_STATUSES)
payments.update_all(payable_payment_status: :failed) # rubocop:disable Rails/SkipsModelValidations
end
end
end
2 changes: 1 addition & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions spec/factories/payments.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@
provider_payment_id { SecureRandom.uuid }
status { 'pending' }

trait :adyen_payment do
association :payment_provider, factory: :adyen_provider
association :payment_provider_customer, factory: :adyen_customer
end

trait :gocardless_payment do
association :payment_provider, factory: :gocardless_provider
association :payment_provider_customer, factory: :gocardless_customer
end

trait :requires_action do
status { 'requires_action' }
provider_payment_data do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,7 @@
let(:payment) do
create(
:payment,
:adyen_payment,
payable: payment_request,
provider_payment_id:,
status: "Pending"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@
let(:payment) do
create(
:payment,
:gocardless_payment,
payable: payment_request,
provider_payment_id: provider_payment_id,
status: "pending_submission"
Expand Down
Loading