module PerfectMoneyMerchant class Account < ActiveRecord::Base class Unit < ActiveRecord::Base self.table_name = 'perfect_money_merchant_account_units' belongs_to :account, class_name: 'Account', foreign_key: :account_id validates :currency, :code_number, presence: true validates :currency, inclusion: { in: %w(usd eur) } validates :code_number, format: { with: /\A[EU]\d{7}\z/ } # transfer money to other PerfectMoney account # @param [String] PerfectMoney account code number # @param [Float] just desirable amount to trasnfer # @return [Hash] PerfectMoney http response body def transfer!(payee_account, amount) Api.new.transfer( AccountID: self.account.login, PassPhrase: self.account.password, Payer_Account: self.code_number, Payee_Account: payee_account, Amount: amount ) end after_create :sync_with_pm_server # unit synchronization with perfect money server def sync_with_pm_server account.sync_with_pm_server end end class Query attr_reader :relation def initialize(relation = Account.all) @relation = relation end # find account by unit's code number # @param [String] code number # @return [PerfectMoneyMerchant::Account] def find_by_unit_code_number(code_number) relation.joins(:units).where(perfect_money_merchant_account_units: { code_number: code_number }).take(1).first end def get_secret_key(code_number) find_by_unit_code_number(code_number).try(:secret_key) end end self.table_name = 'perfect_money_merchant_accounts' has_many :units, dependent: :destroy accepts_nested_attributes_for :units, :reject_if => :all_blank, :allow_destroy => true validates :login, :password, :secret_key, presence: true # Synchronize account unit balances with Perfect Money server via PerfectMoneyMerchant::Api # @see PerfectMoneyMerchant::Api PerfectMoneyMerchant Api # @return [PerfectMoneyMerchant::Account] itself def sync_with_pm_server response = Api.new.balance(AccountID: login, PassPhrase: password) response.each_pair do |code_number, balance| units.each do |unit| unit.update!(balance: balance) if unit.code_number == code_number end end self end # Transfer money to whatever perfect money account # @param [String] payee_account payee account code number # @param [Float] amount amount of money # @return [Hash] Perfect Money http response body def transfer!(payee_account, amount) if payee_account =~ /\A([UE])\d{7}\z/ currency = $1 == 'U' ? 'usd' : 'eur' unit = units.where(currency: currency).where('balance > ?', amount).order('balance DESC').take(1).first if unit.nil? exception = BasicError.new('no unit was found') exception.add_error(:unit, :not_found) raise exception else unit.transfer!(payee_account, amount).tap do |params| sync_with_pm_server Payment.create( payment_batch_num: params.payment_batch_num, payment_id: params.payment_id, payment_amount: params.payment_amount, payer_account: params.payer_account, payee_account: params.payee_account ) end end else exception = BasicError.new('invalid payee_account') exception.add_error(:payee_account, :invalid) raise exception end end class << self # Obtain the most suitable deposit account from database # @param [String] currency currency like 'usd' or 'eur' # @return [String] PerfectMoney account code number def obtain_deposit_account(currency) currency = currency.to_s.downcase unit = Account::Unit. joins(:account). where.not(perfect_money_merchant_accounts: { secret_key: nil }). where(currency: currency). order('balance ASC').take(1).first if unit unit.code_number else raise BasicError.new('no unit was found').tap do |exception| exception.add_error(:unit, :not_found) end end end # Transfer money to whatever perfect money account # @param [String] payee_account payee account code number # @param [Float] amount amount of money # @return [Hash] Perfect Money http response body # # PerfectMoneyMerchant::Account.transfer('U8259997',0.01) def transfer!(payee_account, amount) if payee_account =~ /\A([UE])\d{7}\z/ currency = $1 == 'U' ? 'usd' : 'eur' account = joins(:units). where.not(login: nil, password: nil). where(perfect_money_merchant_account_units: { currency: currency }). where('perfect_money_merchant_account_units.balance > ?', amount). order('perfect_money_merchant_account_units.balance DESC'). take(1).first if account.nil? raise StandardError.new('account nil') else account.transfer!(payee_account, amount) end else raise BasicError.new('invalid payee_account').tap do |exception| exception.add_error(:payee_account, :invalid) end end end end end end