diff --git a/app/models/payment_providers/adyen_provider.rb b/app/models/payment_providers/adyen_provider.rb index 1514e970968..d45af2703eb 100644 --- a/app/models/payment_providers/adyen_provider.rb +++ b/app/models/payment_providers/adyen_provider.rb @@ -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} diff --git a/app/models/payment_providers/base_provider.rb b/app/models/payment_providers/base_provider.rb index 85f2a821bdd..813bb6435f0 100644 --- a/app/models/payment_providers/base_provider.rb +++ b/app/models/payment_providers/base_provider.rb @@ -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 + end end end diff --git a/app/models/payment_providers/gocardless_provider.rb b/app/models/payment_providers/gocardless_provider.rb index e0387d76dec..8ee648424c3 100644 --- a/app/models/payment_providers/gocardless_provider.rb +++ b/app/models/payment_providers/gocardless_provider.rb @@ -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} diff --git a/app/models/payment_providers/stripe_provider.rb b/app/models/payment_providers/stripe_provider.rb index 426ed50e063..82a9b83778a 100644 --- a/app/models/payment_providers/stripe_provider.rb +++ b/app/models/payment_providers/stripe_provider.rb @@ -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} diff --git a/app/services/payment_requests/payments/adyen_service.rb b/app/services/payment_requests/payments/adyen_service.rb index 0b25168ebfa..dc40208037b 100644 --- a/app/services/payment_requests/payments/adyen_service.rb +++ b/app/services/payment_requests/payments/adyen_service.rb @@ -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 @@ -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) @@ -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) @@ -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, diff --git a/app/services/payment_requests/payments/gocardless_service.rb b/app/services/payment_requests/payments/gocardless_service.rb index 3628fb51cce..8577c58b412 100644 --- a/app/services/payment_requests/payments/gocardless_service.rb +++ b/app/services/payment_requests/payments/gocardless_service.rb @@ -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 @@ -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) @@ -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) @@ -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, diff --git a/app/services/payment_requests/payments/stripe_service.rb b/app/services/payment_requests/payments/stripe_service.rb index b3d7a6a201f..96ecad72bd9 100644 --- a/app/services/payment_requests/payments/stripe_service.rb +++ b/app/services/payment_requests/payments/stripe_service.rb @@ -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 @@ -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" @@ -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) @@ -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, diff --git a/db/migrate/20241220095049_backfill_payable_payment_status.rb b/db/migrate/20241220095049_backfill_payable_payment_status.rb new file mode 100644 index 00000000000..7dc75faf992 --- /dev/null +++ b/db/migrate/20241220095049_backfill_payable_payment_status.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index 184c0a4afa2..85aedac2c1f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_12_17_120924) do +ActiveRecord::Schema[7.1].define(version: 2024_12_20_095049) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" diff --git a/spec/factories/payments.rb b/spec/factories/payments.rb index 0c382fa3574..19de49b1ebb 100644 --- a/spec/factories/payments.rb +++ b/spec/factories/payments.rb @@ -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 diff --git a/spec/services/payment_requests/payments/adyen_service_spec.rb b/spec/services/payment_requests/payments/adyen_service_spec.rb index 1f225b4d5a0..f2738f9d93a 100644 --- a/spec/services/payment_requests/payments/adyen_service_spec.rb +++ b/spec/services/payment_requests/payments/adyen_service_spec.rb @@ -426,6 +426,7 @@ let(:payment) do create( :payment, + :adyen_payment, payable: payment_request, provider_payment_id:, status: "Pending" diff --git a/spec/services/payment_requests/payments/gocardless_service_spec.rb b/spec/services/payment_requests/payments/gocardless_service_spec.rb index aebef0cbf00..38b5a20a2e8 100644 --- a/spec/services/payment_requests/payments/gocardless_service_spec.rb +++ b/spec/services/payment_requests/payments/gocardless_service_spec.rb @@ -285,6 +285,7 @@ let(:payment) do create( :payment, + :gocardless_payment, payable: payment_request, provider_payment_id: provider_payment_id, status: "pending_submission"