SumOfUs/Champaign

View on GitHub
app/controllers/api/payment/braintree_controller.rb

Summary

Maintainability
A
2 hrs
Test Coverage
# frozen_string_literal: true

class Api::Payment::BraintreeController < PaymentController # rubocop:disable Metrics/ClassLength
  include ExceptionHandler
  protect_from_forgery with: :exception, prepend: true
  skip_before_action :verify_authenticity_token, raise: false
  before_action :check_api_key, only: [:refund]
  before_action :verify_bot, only: [:transaction], if: -> { authenticate_cypress_http_token == false }

  def token
    @merchant_account_id = unsafe_params[:merchantAccountId]
    render json: { token: ::Braintree::ClientToken.generate(merchant_account_id: @merchant_account_id) }
  end

  def express_payment
    @page = Page.find(params[:page_id])
    @follow_up_url = ''

    begin
      @follow_up_url = PageFollower.new_from_page(
        @page
      ).follow_up_path
    rescue StandardError
    end

    begin
      @process_one_click ||= PaymentProcessor::Braintree::OneClickFromUri.new(
        params.to_unsafe_hash,
        page: @page,
        member: recognized_member,
        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
    rescue ArgumentError => e
      @status = 400
      @status = 404 if e.to_s == 'PaymentProcessor::Exceptions::CustomerNotFound'
      render json: { error: e, success: false }, status: @status
    rescue StandardError => e
      render json: { error: e.message, success: false }, status: 500
    else
      render json: { success: true, follow_up_url: @follow_up_url }, status: 200
    end
  end

  def payment_methods
    tokens = (cookies.signed[:payment_methods] || '').split(',')
    render json: Payment::Braintree::PaymentMethod.where(token: tokens)
  end

  def webhook
    if client::WebhookHandler.handle(unsafe_params[:bt_signature], unsafe_params[:bt_payload])
      head :ok
    else
      head :not_found
    end
  end

  def refund
    @tracker = PaymentProcessor::Braintree::RefundTracker.new
    @tracker.sync
    render json: { refund_ids_synced: @tracker.unsynced_ids }, status: :ok
  end

  def one_click
    @result = client::OneClick.new(unsafe_params, cookies.signed[:payment_methods], member).run
    render status: :unprocessable_entity, errors: oneclick_payment_errors unless @result.success?
  rescue PaymentProcessor::Exceptions::BraintreePaymentError => e
    @result = e
    render status: :unprocessable_entity, errors: e.message
  end

  private

  def member
    if params[:user][:email].present?
      Member.find_by_email(params[:user][:email])
    elsif unsafe_params[:akid].present?
      Member.find_from_request(akid: unsafe_params[:akid], id: cookies.signed[:member_id])
    end
  end

  def oneclick_payment_errors
    if @result.class == Braintree::ErrorResult
      client::ErrorProcessing.new(@result, locale: locale).process
    else
      @result.errors
    end
  end

  def payment_options
    {
      nonce: params.require(:payment_method_nonce),
      amount: params.require(:amount).to_f,
      user: user_params,
      currency: params.require(:currency),
      page_id: params.require(:page_id),
      store_in_vault: store_in_vault?
    }.to_hash.tap do |options|
      options[:device_data] = unsafe_params[:device_data].to_json unless unsafe_params[:device_data].nil?

      options[:extra_params] = unsafe_params[:extra_action_fields] if unsafe_params[:extra_action_fields].present?

      if params[:source].present?
        options[:extra_params] ||= {}
        options[:extra_params][:source] = params[:source]
      end
    end
  end

  def client
    PaymentProcessor::Braintree
  end

  def page
    @page ||= Page.find(params.require(:page_id))
  end

  def recurring?
    @recurring ||= ActiveRecord::Type::Boolean.new.cast(unsafe_params[:recurring])
  end

  def user_params
    user_data = params
      .require(:user).permit!
      .merge(mobile_value)
      .to_hash
      .symbolize_keys
      .compact

    raise Api::Exceptions::InvalidParameters unless valid_user?(user_data)

    user_data
  end

  def valid_user?(user)
    user.slice(:email, :name, :country).all? { |_, value| value.present? }
  end

  def verify_bot
    action = 'donate/' + params[:page_id]
    @authorizer = PaymentRequestAuthorizer.new(recaptcha: params[:recaptcha_token],
                                               action: action, params: params, email: user_params[:email])
    unless @authorizer.valid?
      msg = @authorizer.errors.present? ? @authorizer.errors : 'Invalid request'
      render json: { success: false, message: msg }, status: :unprocessable_entity
      return false
    end
  end

  def authenticate_cypress_http_token
    authenticate_with_http_token do |token, _options|
      return ActiveSupport::SecurityUtils.secure_compare(token, Settings.pronto_api_secret_key)
    end
    false
  end

  def member_matches_payload
    return false unless recognized_member.present?

    recognized_member.email == user_params[:email]
  end
end