Initial commit
This commit is contained in:
245
app/api/v2/account/withdraws.rb
Normal file
245
app/api/v2/account/withdraws.rb
Normal file
@@ -0,0 +1,245 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Account
|
||||
class Withdraws < Grape::API
|
||||
|
||||
before { withdraws_must_be_permitted! }
|
||||
helpers API::V2::Account::Utils
|
||||
|
||||
desc 'List your withdraws as paginated collection.',
|
||||
is_array: true,
|
||||
success: API::V2::Entities::Withdraw
|
||||
params do
|
||||
optional :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.codes(bothcase: true) }, message: 'account.currency.doesnt_exist'},
|
||||
desc: 'Currency code.'
|
||||
optional :limit,
|
||||
type: { value: Integer, message: 'account.withdraw.non_integer_limit' },
|
||||
values: { value: 1..100, message: 'account.withdraw.invalid_limit' },
|
||||
default: 100,
|
||||
desc: "Number of withdraws per page (defaults to 100, maximum is 100)."
|
||||
optional :state,
|
||||
values: { value: ->(v) { (Array.wrap(v) - Withdraw::STATES.map(&:to_s)).blank? }, message: 'account.withdraw.invalid_state' },
|
||||
desc: 'Filter withdrawals by states.'
|
||||
optional :rid,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: 'Wallet address on the Blockchain.'
|
||||
optional :time_from,
|
||||
allow_blank: { value: false, message: 'account.withdraw.empty_time_from' },
|
||||
type: { value: Integer, message: 'account.withdraw.non_integer_time_from' },
|
||||
desc: 'An integer represents the seconds elapsed since Unix epoch.'
|
||||
optional :time_to,
|
||||
type: { value: Integer, message: 'account.withdraw.non_integer_time_to' },
|
||||
allow_blank: { value: false, message: 'account.withdraw.empty_time_to' },
|
||||
desc: 'An integer represents the seconds elapsed since Unix epoch.'
|
||||
optional :page,
|
||||
type: { value: Integer, message: 'account.withdraw.non_integer_page' },
|
||||
values: { value: -> (p){ p.try(:positive?) }, message: 'account.withdraw.non_positive_page'},
|
||||
default: 1,
|
||||
desc: 'Page number (defaults to 1).'
|
||||
end
|
||||
get '/withdraws' do
|
||||
user_authorize! :read, ::Withdraw
|
||||
|
||||
currency = Currency.find(params[:currency]) if params[:currency].present?
|
||||
|
||||
current_user.withdraws.order(id: :desc)
|
||||
.tap { |q| q.where!(currency: currency) if currency }
|
||||
.tap { |q| q.where!(aasm_state: params[:state]) if params[:state] }
|
||||
.tap { |q| q.where!(rid: params[:rid]) if params[:rid] }
|
||||
.tap { |q| q.where!('updated_at >= ?', Time.at(params[:time_from])) if params[:time_from].present? }
|
||||
.tap { |q| q.where!('updated_at <= ?', Time.at(params[:time_to])) if params[:time_to].present? }
|
||||
.tap { |q| present paginate(q), with: API::V2::Entities::Withdraw }
|
||||
end
|
||||
|
||||
desc 'Returns withdrawal sums for last 4 hours and 1 month'
|
||||
get '/withdraws/sums' do
|
||||
user_authorize! :read, ::Withdraw
|
||||
|
||||
sum_24_hours, sum_1_month = Withdraw.sanitize_execute_sum_queries(current_user.id)
|
||||
|
||||
present({ last_24_hours: sum_24_hours, last_1_month: sum_1_month })
|
||||
end
|
||||
|
||||
desc 'Creates new withdrawal to active beneficiary.'
|
||||
params do
|
||||
requires :otp,
|
||||
type: { value: Integer, message: 'account.withdraw.non_integer_otp' },
|
||||
allow_blank: false,
|
||||
desc: 'OTP to perform action'
|
||||
requires :beneficiary_id,
|
||||
type: { value: Integer, message: 'account.withdraw.non_integer_beneficiary_id' },
|
||||
allow_blank: false,
|
||||
desc: 'ID of Active Beneficiary belonging to user.'
|
||||
requires :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.codes(bothcase: true) }, message: 'account.currency.doesnt_exist'},
|
||||
desc: 'The currency code.'
|
||||
requires :amount,
|
||||
type: { value: BigDecimal, message: 'account.withdraw.non_decimal_amount' },
|
||||
values: { value: ->(v) { v.try(:positive?) }, message: 'account.withdraw.non_positive_amount' },
|
||||
desc: 'The amount to withdraw.'
|
||||
optional :note,
|
||||
type: String,
|
||||
values: { value: ->(v) { v.size <= 256 }, message: 'account.withdraw.too_long_note' },
|
||||
desc: 'Optional user metadata to be applied to the transaction. Used to tag transactions with memorable comments.'
|
||||
end
|
||||
post '/withdraws' do
|
||||
user_authorize! :create, ::Withdraw
|
||||
|
||||
withdraw_api_must_be_enabled!
|
||||
|
||||
if current_user.otp.present? && current_user.read_cache('withdraw_2fa').blank?
|
||||
error!({ errors: ['account.withdraw.need.2fa'] }, 403)
|
||||
end
|
||||
|
||||
totp = Vault::TOTPAction.new('withdraw-coin')
|
||||
error!({ errors: ['withdraw.totp.code'] }, 422) unless totp.validate?(current_user.uid, params[:otp])
|
||||
|
||||
beneficiary = current_user
|
||||
.beneficiaries
|
||||
.available_to_member
|
||||
.find_by(id: params[:beneficiary_id])
|
||||
|
||||
if beneficiary.blank?
|
||||
error!({ errors: ['account.beneficiary.doesnt_exist'] }, 422)
|
||||
elsif !beneficiary.active?
|
||||
error!({ errors: ['account.beneficiary.invalid_state_for_withdrawal'] }, 422)
|
||||
end
|
||||
|
||||
currency = Currency.find(params[:currency])
|
||||
error!({ errors: ['account.currency.withdrawal_disabled'] }, 422) unless currency.withdrawal_enabled?
|
||||
|
||||
# TODO: Delete subclasses from Deposit and Withdraw
|
||||
withdraw = "withdraws/#{currency.type}".camelize.constantize.new \
|
||||
beneficiary: beneficiary,
|
||||
sum: params[:amount],
|
||||
member: current_user,
|
||||
currency: currency,
|
||||
note: params[:note]
|
||||
withdraw.save!
|
||||
withdraw.with_lock { withdraw.accept! }
|
||||
current_user.delete_cache('withdraw_2fa')
|
||||
present withdraw, with: API::V2::Entities::Withdraw
|
||||
|
||||
rescue ::Account::AccountError => e
|
||||
report_api_error(e, request)
|
||||
error!({ errors: ['account.withdraw.insufficient_balance'] }, 422)
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
report_api_error(e, request)
|
||||
# TODO: Check if there are other errors possible here.
|
||||
# For now single error which is not handled by params validations is
|
||||
# sum precision validation error (PrecisionValidator).
|
||||
error!({ errors: ['account.withdraw.invalid_amount'] }, 422)
|
||||
rescue => e
|
||||
report_exception(e)
|
||||
error!({ errors: ['account.withdraw.create_error'] }, 422)
|
||||
end
|
||||
|
||||
desc 'Creates new fiat withdrawal'
|
||||
params do
|
||||
requires :amount,
|
||||
type: { value: BigDecimal, message: 'account.withdraw.non_decimal_amount' },
|
||||
values: { value: ->(v) { v.try(:positive?) }, message: 'account.withdraw.non_positive_amount' },
|
||||
desc: 'The amount to withdraw.'
|
||||
requires :iban,
|
||||
type: String,
|
||||
desc: 'iban bank number'
|
||||
requires :currency,
|
||||
type: String,
|
||||
default: 'irt',
|
||||
desc: -> { 'currency' }
|
||||
optional :note,
|
||||
type: String,
|
||||
values: { value: ->(v) { v.size <= 256 }, message: 'account.withdraw.too_long_note' },
|
||||
desc: 'Optional user metadata to be applied to the transaction. Used to tag transactions with memorable comments.'
|
||||
end
|
||||
post '/withdraws/fiat' do
|
||||
user_authorize! :create, ::Withdraw
|
||||
withdraw_api_must_be_enabled!
|
||||
|
||||
if current_user.otp.present? && current_user.read_cache('withdraw_2fa').blank?
|
||||
error!({ errors: ['account.withdraw.need.2fa'] }, 403)
|
||||
end
|
||||
|
||||
error!({ errors: ['account.withdraw.use_valid_iban'] }, 403) if current_user.ibans.exclude?(params[:iban])
|
||||
|
||||
currency = Currency.find(params[:currency])
|
||||
|
||||
withdraw = "withdraws/#{currency.type}".camelize.constantize.new \
|
||||
rid: params[:iban],
|
||||
sum: params[:amount],
|
||||
member: current_user,
|
||||
currency: currency,
|
||||
transfer_type: :fiat,
|
||||
note: params[:note]
|
||||
|
||||
withdraw.save
|
||||
error!({ errors: withdraw.errors.full_messages }, 422) unless withdraw.valid?
|
||||
# delete 2fa cache
|
||||
current_user.delete_cache('withdraw_2fa')
|
||||
publish_confirmation_code(current_user, 'withdraw-fiat')
|
||||
present withdraw, with: API::V2::Entities::Withdraw
|
||||
end
|
||||
#
|
||||
#
|
||||
desc 'confirm fiat withdrawal'
|
||||
params do
|
||||
requires :id,
|
||||
type: Integer,
|
||||
desc: 'withdraw id'
|
||||
requires :otp,
|
||||
type: Integer,
|
||||
desc: 'auth otp in sms or email'
|
||||
end
|
||||
post '/withdraws/confirm' do
|
||||
user_authorize! :create, ::Withdraw
|
||||
withdraw = current_user.withdraws.find_by(id: params[:id])
|
||||
error!({ errors: ['account.withdraw.not.found'] }, 403) unless withdraw.present?
|
||||
|
||||
error!({ errors: ['account.withdraw.process_before'] }, 403) unless withdraw.aasm_state == 'prepared'
|
||||
|
||||
totp = Vault::TOTPAction.new('withdraw-fiat')
|
||||
error!({ errors: ['withdraw.totp.code'] }, 422) unless totp.validate?(current_user.uid, params[:otp])
|
||||
|
||||
# here must lock found
|
||||
withdraw.with_lock { withdraw.accept! }
|
||||
response = ::VandarService.new.create_withdraw(amount: withdraw.sum, iban: withdraw.rid)
|
||||
if response.dig('status').to_i.positive?
|
||||
withdraw.update(txid: response['data'].dig('settlement', 0, 'transaction_id'), aasm_state: 'confirming')
|
||||
else
|
||||
withdraw.with_lock { withdraw.reject! }
|
||||
end
|
||||
|
||||
present(response: response)
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
desc 'Get fiat withdrawal'
|
||||
params do
|
||||
requires :id,
|
||||
type: Integer,
|
||||
desc: 'withdraw id'
|
||||
optional :with_otp,
|
||||
type: String,
|
||||
desc: -> { 'send otp or not' }
|
||||
end
|
||||
get '/withdraw' do
|
||||
user_authorize! :read, ::Withdraw
|
||||
|
||||
withdraw = current_user.withdraws.find_by(id: params[:id])
|
||||
if withdraw.present? && withdraw.accepted? && params['with_otp'].present?
|
||||
publish_confirmation_code(current_user, 'withdraw')
|
||||
end
|
||||
present withdraw, with: API::V2::Entities::Withdraw
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user