internetee/registry

View on GitHub
app/models/payment_orders/bank_link.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
95%
module PaymentOrders
  class BankLink < PaymentOrder
    BANK_LINK_VERSION = '008'.freeze

    NEW_TRANSACTION_SERVICE_NUMBER    = '1012'.freeze
    SUCCESSFUL_PAYMENT_SERVICE_NUMBER = '1111'.freeze
    CANCELLED_PAYMENT_SERVICE_NUMBER  = '1911'.freeze

    NEW_MESSAGE_KEYS     = %w[VK_SERVICE VK_VERSION VK_SND_ID VK_STAMP VK_AMOUNT
                              VK_CURR VK_REF VK_MSG VK_RETURN VK_CANCEL
                              VK_DATETIME].freeze
    SUCCESS_MESSAGE_KEYS = %w[VK_SERVICE VK_VERSION VK_SND_ID VK_REC_ID VK_STAMP
                              VK_T_NO VK_AMOUNT VK_CURR VK_REC_ACC VK_REC_NAME
                              VK_SND_ACC VK_SND_NAME VK_REF VK_MSG
                              VK_T_DATETIME].freeze
    CANCEL_MESSAGE_KEYS  = %w[VK_SERVICE VK_VERSION VK_SND_ID VK_REC_ID VK_STAMP
                              VK_REF VK_MSG].freeze

    def form_fields
      hash = {}
      hash['VK_SERVICE']  = NEW_TRANSACTION_SERVICE_NUMBER
      hash['VK_VERSION']  = BANK_LINK_VERSION
      hash['VK_SND_ID']   = seller_account
      hash['VK_STAMP']    = invoice.number
      hash['VK_AMOUNT']   = number_with_precision(invoice.total, precision: 2, separator: ".")
      hash['VK_CURR']     = invoice.currency
      hash['VK_REF']      = ''
      hash['VK_MSG']      = invoice.order
      hash['VK_RETURN']   = return_url
      hash['VK_CANCEL']   = return_url
      hash['VK_DATETIME'] = Time.zone.now.strftime('%Y-%m-%dT%H:%M:%S%z')
      hash['VK_MAC']      = calc_mac(hash)
      hash['VK_ENCODING'] = 'UTF-8'
      hash['VK_LANG']     = 'ENG'
      hash
    end

    def valid_response_from_intermediary?
      return false unless response

      case response['VK_SERVICE']
      when SUCCESSFUL_PAYMENT_SERVICE_NUMBER
        valid_successful_transaction?
      when CANCELLED_PAYMENT_SERVICE_NUMBER
        valid_cancel_notice?
      else
        false
      end
    end

    def payment_received?
      valid_response_from_intermediary? && settled_payment?
    end

    def create_failure_report
      notes = "User failed to make payment. Bank responded with code #{response['VK_SERVICE']}"
      status = 'cancelled'
      update!(notes: notes, status: status)
    end

    def composed_transaction
      paid_at = Time.parse(response['VK_T_DATETIME'])
      transaction = base_transaction(sum: response['VK_AMOUNT'],
                                     paid_at: paid_at,
                                     buyer_name: response['VK_SND_NAME'])

      transaction.bank_reference = response['VK_T_NO']
      transaction.buyer_bank_code = response['VK_SND_ID']
      transaction.buyer_iban = response['VK_SND_ACC']

      transaction
    end

    def settled_payment?
      response['VK_SERVICE'] == SUCCESSFUL_PAYMENT_SERVICE_NUMBER
    end

    private

    def valid_successful_transaction?
      valid_success_notice? && valid_amount? && valid_currency?
    end

    def valid_cancel_notice?
      valid_mac?(response, CANCEL_MESSAGE_KEYS)
    end

    def valid_success_notice?
      valid_mac?(response, SUCCESS_MESSAGE_KEYS)
    end

    def valid_amount?
      source = number_with_precision(
        BigDecimal(response['VK_AMOUNT']), precision: 2, separator: '.'
      )
      target = number_with_precision(invoice.total, precision: 2, separator: '.')

      source == target
    end

    def valid_currency?
      invoice.currency == response['VK_CURR']
    end

    def sign(data)
      private_key = OpenSSL::PKey::RSA.new(File.read(seller_certificate))
      signed_data = private_key.sign(OpenSSL::Digest.new('SHA1'), data)
      Base64.encode64(signed_data).gsub(/\n|\r/, '')
    end

    def calc_mac(fields)
      pars = NEW_MESSAGE_KEYS
      data = pars.map { |element| prepend_size(fields[element]) }.join
      sign(data)
    end

    def valid_mac?(hash, keys)
      data = keys.map { |element| prepend_size(hash[element]) }.join
      verify_mac(data, hash['VK_MAC'])
    end

    def verify_mac(data, mac)
      bank_public_key = OpenSSL::X509::Certificate.new(File.read(bank_certificate)).public_key
      bank_public_key.verify(OpenSSL::Digest.new('SHA1'), Base64.decode64(mac), data)
    end

    def prepend_size(value)
      value = (value || '').to_s.strip
      string = ''
      string << format("%03i", value.size)
      string << value
    end

    def seller_account
      ENV["payments_#{self.class.config_namespace_name}_seller_account"]
    end

    def seller_certificate
      ENV["payments_#{self.class.config_namespace_name}_seller_private"]
    end

    def bank_certificate
      ENV["payments_#{self.class.config_namespace_name}_bank_certificate"]
    end
  end
end