# encoding: UTF-8 # frozen_string_literal: true require 'securerandom' class Member < ApplicationRecord include ModelCaching MAX_GROUP = 4 has_many :orders has_many :accounts has_many :stats_member_pnl has_many :payment_addresses has_many :withdraws, -> { order(id: :desc) } has_many :deposits, -> { order(id: :desc) } has_many :beneficiaries, -> { order(id: :desc) } has_many :bonus serialize :cards, Array serialize :ibans, Array scope :enabled, -> { where(state: 'active') } before_validation :downcase_email validates :uid, length: { maximum: 32 } validates :referral_uid, length: { maximum: 32 } validates :email, presence: true, uniqueness: true, email: true validates :level, numericality: { greater_than_or_equal_to: 0 } validates :role, inclusion: { in: ::Ability.roles } before_create { self.group = self.group.strip.downcase } class << self def groups TradingFee.distinct.pluck(:group) end def group_name_by_number(number) "vip-#{number}" end def min_max_group(group_name) return [ENV.fetch('VIP_4_AMOUNT').to_d, 0] if group_name == 'vip-4' return [ENV.fetch('VIP_3_AMOUNT').to_d, ENV.fetch('VIP_4_AMOUNT').to_d] if group_name == 'vip-3' return [ENV.fetch('VIP_2_AMOUNT').to_d, ENV.fetch('VIP_3_AMOUNT').to_d] if group_name == 'vip-2' return [ENV.fetch('VIP_1_AMOUNT').to_d, ENV.fetch('VIP_2_AMOUNT').to_d] if group_name == 'vip-1' return [0, ENV.fetch('VIP_1_AMOUNT').to_d] if group_name == 'vip-0' end end def group_update(order) return true if self.group == 'bot' return true unless self.group.include?('vip') all = self.trades.last_month.inject(0){ |sum, x| sum + x.rls } return self.update(group: 'vip-4') if all > ENV.fetch('VIP_4_AMOUNT').to_d return self.update(group: 'vip-3') if all > ENV.fetch('VIP_3_AMOUNT').to_d return self.update(group: 'vip-2') if all > ENV.fetch('VIP_2_AMOUNT').to_d return self.update(group: 'vip-1') if all > ENV.fetch('VIP_1_AMOUNT').to_d self.update(group: 'vip-0') end def next_group return group if group == Member.group_name_by_number(MAX_GROUP) Member.group_name_by_number(group[4].to_i + 1) end def trades Trade.where('maker_id = ? OR taker_id = ?', id, id) end def role super&.inquiry end def admin? role == "admin" end def get_account(model_or_id_or_code) if model_or_id_or_code.is_a?(String) || model_or_id_or_code.is_a?(Symbol) accounts.find_or_create_by(currency_id: model_or_id_or_code) elsif model_or_id_or_code.is_a?(Currency) accounts.find_or_create_by(currency: model_or_id_or_code) end # Thread Safe Account creation rescue ActiveRecord::RecordNotUnique if model_or_id_or_code.is_a?(String) || model_or_id_or_code.is_a?(Symbol) accounts.find_by(currency_id: model_or_id_or_code) elsif model_or_id_or_code.is_a?(Currency) accounts.find_by(currency: model_or_id_or_code) end end def referral_user self.class.find_by(uid: referral_uid) if referral_uid.present? end # @deprecated def touch_accounts Currency.find_each do |currency| next if accounts.where(currency: currency).exists? accounts.create!(currency: currency) end end def balance_for(currency:, kind:) account_code = Operations::Account.find_by( type: :liability, kind: kind, currency_type: currency.type ).code liabilities = Operations::Liability.where(member_id: id, currency: currency, code: account_code) liabilities.sum('credit - debit') end def legacy_balance_for(currency:, kind:) if kind.to_sym == :main get_account(currency).balance elsif kind.to_sym == :locked get_account(currency).locked else raise Operations::Exception, "Account for #{options} doesn't exists." end end def revert_trading_activity!(trades) trades.each(&:revert_trade!) end def payment_address(wallet_id, remote = false) wallet = Wallet.find(wallet_id) return if wallet.blank? pa = PaymentAddress.find_by(member: self, wallet: wallet, remote: remote) if pa.blank? pa = payment_addresses.create!(wallet: wallet) elsif pa.address.blank? pa.enqueue_address_generation end pa end # Attempts to create additional deposit address for account. def payment_address!(wallet_id, remote = false) wallet = Wallet.find(wallet_id) return if wallet.blank? pa = PaymentAddress.find_by(member: self, wallet: wallet) # The address generation process is in progress. if pa.present? && pa.address.blank? pa else # allows user to have multiple addresses pa = payment_addresses.create!(wallet: wallet, remote: remote) end pa end def as_json_for_event_api { uid: uid, email: email, role: role, level: level, otp: otp, state: state, referral_uid: referral_uid } end def sync_beneficiaries fiat_currency = Currency.find('irt') fiat_beneficiaries = beneficiaries.active_to_member.where(currency: fiat_currency) array_beneficiaries = fiat_beneficiaries.map { |beneficiary| beneficiary&.data.dig('account_number') }&.compact # delete extra beneficiary that was deleted in the Dalan fiat_beneficiaries.each { |b| b.delete unless b.data.dig('account_number')&.in?(flat_cards + flat_ibans) } # add beneficiary that was added in the Dalan cards&.each { |c| create_beneficiary('card', c&.keys&.last, c&.values&.last, fiat_currency) unless c&.values&.last.in? (array_beneficiaries) } ibans&.each { |i| create_beneficiary('iban', i&.keys&.last, i&.values&.last, fiat_currency) unless i&.values&.last.in? (array_beneficiaries) } end def flat_cards return [] unless cards.present? cards.map(&:values).flatten end def flat_ibans return [] unless ibans.present? ibans.map(&:values).flatten end def create_beneficiary(kind, name, data, currency, state: 'active') beneficiaries.create!(currency: currency, data: { full_name: name, account_number: data, kind: kind }, name: name, state: state) end private def downcase_email self.email = email.try(:downcase) end class << self def uid(member_id) Member.find_by(id: member_id)&.uid end def find_by_username_or_uid(uid_or_username) if Member.find_by(uid: uid_or_username).present? Member.find_by(uid: uid_or_username) elsif Member.find_by(username: uid_or_username).present? Member.find_by(username: uid_or_username) end end def from_payload(p) params = filter_payload(p) validate_payload(params) member = ::Member.find_or_create_by(uid: p[:uid]) do |m| m.email = params[:email] m.username = params[:username] m.role = params[:role] m.state = params[:state] m.level = params[:level] m.referral_uid = params.dig(:referral_uid) m.last_change_pass = params.dig(:last_change_pass) m.cards = params.dig(:cards) m.ibans = params.dig(:ibans) m.otp = params.dig(:otp) end member.assign_attributes(params) # comment if dont want sync beneficiary # flag_beneficiary = member.ibans_changed? || member.cards_changed? ? true : false member.save! if member.changed? # member.reload.sync_beneficiaries if flag_beneficiary member end # Filter and validate payload params def filter_payload(payload) payload.slice(:email, :username, :uid, :role, :state, :level, :referral_uid, :last_change_pass, :cards, :ibans, :otp) end def validate_payload(p) fetch_email(p) p.fetch(:uid).tap { |uid| raise(Peatio::Auth::Error, 'UID is blank.') if uid.blank? } p.fetch(:role).tap { |role| raise(Peatio::Auth::Error, 'Role is blank.') if role.blank? } p.fetch(:level).tap { |level| raise(Peatio::Auth::Error, 'Level is blank.') if level.blank? } p.fetch(:state).tap do |state| raise(Peatio::Auth::Error, 'State is blank.') if state.blank? raise(Peatio::Auth::Error, 'State is not active.') unless state == 'active' end end def fetch_email(payload) payload[:email].to_s.tap do |email| raise(Peatio::Auth::Error, 'E-Mail is blank.') if email.blank? raise(Peatio::Auth::Error, 'E-Mail is invalid.') unless EmailValidator.valid?(email) end end def search(field: nil, term: nil) term = "%#{term}%" case field when 'email' where("email LIKE ?", term) when 'uid' where('uid LIKE ?', term) when 'wallet_address' joins(:payment_addresses).where('payment_addresses.address LIKE ?', term) else all end.order(:id).reverse_order end end end # == Schema Information # Schema version: 20201207134745 # # Table name: members # # id :integer not null, primary key # uid :string(32) not null # email :string(255) not null # username :string(255) # level :integer not null # role :string(16) not null # group :string(32) default("vip-0"), not null # state :string(16) not null # created_at :datetime not null # updated_at :datetime not null # # Indexes # # index_members_on_email (email) UNIQUE # index_members_on_uid (uid) UNIQUE # index_members_on_username (username) UNIQUE #