Initial commit

This commit is contained in:
Yaser
2026-08-13 19:50:53 +03:30
commit 38084458fe
879 changed files with 95198 additions and 0 deletions

216
app/models/beneficiary.rb Normal file
View File

@@ -0,0 +1,216 @@
# encoding: UTF-8
# frozen_string_literal: true
class Beneficiary < ApplicationRecord
# == Constants ============================================================
extend Enumerize
include Vault::EncryptedModel
vault_lazy_decrypt!
acts_as_eventable prefix: 'beneficiary', on: %i[create update]
include AASM
include AASM::Locking
STATES_MAPPING = { pending: 0, active: 1, archived: 2, aml_processing: 3, aml_suspicious: 4 }.freeze
STATES = %i[pending aml_processing aml_suspicious active archived].freeze
STATES_AVAILABLE_FOR_MEMBER = %i[pending active]
PIN_LENGTH = 6
PIN_RANGE = 10**5..10**Beneficiary::PIN_LENGTH
INVALID_ADDRESS_SYMBOLS = /[\<\>\'\,\[\]\}\{\"\)\(\*\&\^\%\$\#\`\~\{\}\@]/.freeze
# == Attributes ===========================================================
vault_attribute :data, serialize: :json, default: {}
# == Extensions ===========================================================
enumerize :state, in: STATES_MAPPING, scope: true
aasm column: :state, enum: :states_mapping, whiny_transitions: false do
state :pending, initial: true
state :active
state :aml_processing
state :aml_suspicious
state :archived
event :activate do
if Peatio::AML.adapter.present?
transitions from: :pending, to: :aml_processing, guard: :valid_pin?
after do
enable! if aml_check!
end
else
transitions from: :pending, to: :active, guard: :valid_pin?
end
end
event :enable do
transitions from: :aml_processing, to: :active
end if Peatio::AML.adapter.present?
event :aml_suspicious do
transitions from: :aml_processing, to: :aml_suspicious
end if Peatio::AML.adapter.present?
event :archive do
transitions from: %i[pending aml_processing aml_suspicious active], to: :archived
end
end
# == Relationships ========================================================
belongs_to :currency, required: true
belongs_to :member, required: true
# == Validations ==========================================================
validates :pin, presence: true, numericality: { only_integer: true }
validates :data, presence: true
# Validates that data contains address field which is required for coin.
validate if: ->(b) { b.currency.present? && b.currency.coin? } do
errors.add(:data, 'address can\'t be blank') if data.blank? || data.symbolize_keys[:address].blank?
end
# Validates address field which is required for coin.
validate if: ->(b) { b.currency.present? && b.currency.coin? } do
errors.add(:data, 'invlalid address') if data.present? && data.symbolize_keys[:address].present? && data.symbolize_keys[:address].match?(INVALID_ADDRESS_SYMBOLS)
end
# Validates that data contains full_name field which is required for fiat.
validate if: ->(b) { b.currency.present? && b.currency.fiat? } do
errors.add(:data, 'full_name can\'t be blank') if data.blank? || data.symbolize_keys[:full_name].blank?
end
# == Scopes ===============================================================
scope :available_to_member, -> { with_state(:pending, :active) }
scope :active_to_member, -> { with_state(:active) }
# == Callbacks ============================================================
before_validation(on: :create) do
# Truncate spaces
data['address'] = data['address'].gsub(/\s+/, '') if data.present? && data['address'].present?
# Generate Beneficiary Pin
self.pin ||= self.class.generate_pin
# Record time when we send event to Event API
self.sent_at = Time.now
end
# == Class Methods ========================================================
class << self
def generate_pin
SecureRandom.rand(Beneficiary::PIN_RANGE)
end
# Method used for passing states mapping to AASM.
def states_mapping
STATES_MAPPING
end
end
# == Instance Methods =====================================================
def as_json_for_event_api
{ user: { uid: member.uid, email: member.email },
currency: currency_id,
name: name,
description: description,
data: data,
pin: pin,
state: state,
sent_at: sent_at.iso8601,
created_at: created_at.iso8601,
updated_at: updated_at.iso8601 }
end
def aml_check!
result = Peatio::AML.check!(rid, currency_id, member.uid)
if result.risk_detected
b.aml_suspicious!
return nil
end
return nil if result.pending
true
end
def valid_pin?(user_pin)
case user_pin
when Integer
return pin == user_pin
when String
return pin == user_pin.to_i
else
return false
end
end
def rid
currency.coin? ? coin_rid : fiat_rid
end
def regenerate_pin!
update(pin: self.class.generate_pin, sent_at: Time.now)
end
def masked_account_number
account_number = data.symbolize_keys[:account_number]
if data.present? && account_number.present?
account_number.sub(/(?<=\A.{2})(.*)(?=.{4}\z)/) { |match| '*' * match.length }
end
end
def masked_data
data.merge(account_number: masked_account_number).compact if data.present?
end
private
def coin_rid
return unless currency.coin?
data.symbolize_keys[:address]
end
def fiat_rid
return unless currency.fiat?
"%s-%s-%08d" % [data.symbolize_keys[:full_name].downcase.split.join('-'), currency_id.downcase, id]
end
end
# == Schema Information
# Schema version: 20201125134745
#
# Table name: beneficiaries
#
# id :bigint not null, primary key
# member_id :bigint not null
# currency_id :string(10) not null
# name :string(64) not null
# description :string(255) default("")
# data_encrypted :string(1024)
# pin :integer unsigned, not null
# sent_at :datetime
# state :integer default("pending"), unsigned, not null
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_beneficiaries_on_currency_id (currency_id)
# index_beneficiaries_on_member_id (member_id)
#