diff --git a/app/controllers/api/payment/braintree_controller.rb b/app/controllers/api/payment/braintree_controller.rb index de3cfe7ed..32b87e6db 100644 --- a/app/controllers/api/payment/braintree_controller.rb +++ b/app/controllers/api/payment/braintree_controller.rb @@ -7,6 +7,8 @@ class Api::Payment::BraintreeController < PaymentController # rubocop:disable Me before_action :check_api_key, only: [:refund] before_action :verify_bot, only: [:transaction], if: -> { authenticate_cypress_http_token == false } + EXPIRED_CARD_ERROR_CODE = '2004' + def token @merchant_account_id = unsafe_params[:merchantAccountId] render json: { token: ::Braintree::ClientToken.generate(merchant_account_id: @merchant_account_id) } @@ -31,7 +33,8 @@ def express_payment cookied_payment_methods: params.to_unsafe_hash['payment_method_ids'] ).process rescue PaymentProcessor::Exceptions::BraintreePaymentError => e - render json: { error: e.message, success: false }, status: 500 + remove_expired_card unless e.message != EXPIRED_CARD_ERROR_CODE + render json: { error: e.message, success: false }, status: 422 rescue ArgumentError => e @status = 400 @status = 404 if e.to_s == 'PaymentProcessor::Exceptions::CustomerNotFound' @@ -67,6 +70,7 @@ def one_click render status: :unprocessable_entity, errors: oneclick_payment_errors unless @result.success? rescue PaymentProcessor::Exceptions::BraintreePaymentError => e @result = e + remove_expired_card unless e.message != EXPIRED_CARD_ERROR_CODE render status: :unprocessable_entity, errors: e.message end @@ -160,4 +164,20 @@ def member_matches_payload recognized_member.email == user_params[:email] end + + def remove_expired_card + @payment_options = BraintreeServices::PaymentOptions.new(unsafe_params, cookies.signed[:payment_methods]) + existing_payment_methods = (cookies.signed[:payment_methods] || '').split(',') + unless @payment_options.nil? + @payment_method_obj = Payment::Braintree::PaymentMethod.find_by_token(@payment_options.token)&.attributes + existing_payment_methods.delete(@payment_options.token) + + cookies.signed[:payment_methods] = { + value: existing_payment_methods.uniq.join(','), + expires: 1.year.from_now, + domain: :all + } + Payment::Braintree::PaymentMethod.find_by_token(@payment_options.token).destroy unless @payment_method_obj.nil? + end + end end diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index 032b8d313..66b3d01da 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -13,6 +13,8 @@ class PagesController < ApplicationController # rubocop:disable Metrics/ClassLen before_action :localize, only: %i[show follow_up double_opt_in_notice] before_action :record_tracking, only: %i[show] + EXPIRED_CARD_ERROR_CODE = '2004' + attr_reader :error_code def index @pages = Search::PageSearcher.search(search_params) @@ -201,6 +203,7 @@ def process_one_click ).process rescue PaymentProcessor::Exceptions::BraintreePaymentError => e set_error_code(e.message) + remove_expired_card unless e.message != EXPIRED_CARD_ERROR_CODE @process_one_click = false rescue StandardError @process_one_click = false @@ -225,4 +228,21 @@ def redirect_to_donations_experiment redirect_to request.fullpath.gsub(path_match, "/#{@page.language_code}/a/") end end + + def remove_expired_card + @payment_options = recognized_member.payment_methods.last if recognized_member.present? + existing_payment_methods = (cookies.signed[:payment_methods] || '').split(',') + + unless @payment_options.nil? + @payment_method_obj = Payment::Braintree::PaymentMethod.find_by_token(@payment_options.token)&.attributes + existing_payment_methods.delete(@payment_options.token) + + cookies.signed[:payment_methods] = { + value: existing_payment_methods.uniq.join(','), + expires: 1.year.from_now, + domain: :all + } + Payment::Braintree::PaymentMethod.find_by_token(@payment_options.token).destroy unless @payment_method_obj.nil? + end + end end