Initial commit
This commit is contained in:
48
app/api/v2/management/accounts.rb
Normal file
48
app/api/v2/management/accounts.rb
Normal file
@@ -0,0 +1,48 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
class Accounts < Grape::API
|
||||
desc 'Queries the account balance for the given UID and currency.' do
|
||||
@settings[:scope] = :read_accounts
|
||||
success API::V2::Management::Entities::Balance
|
||||
end
|
||||
|
||||
params do
|
||||
requires :uid, type: String, desc: 'The shared user ID.'
|
||||
requires :currency, type: String, values: -> { Currency.codes(bothcase: true) }, desc: 'The currency code.'
|
||||
end
|
||||
|
||||
post '/accounts/balance' do
|
||||
member = Member.find_by!(uid: params[:uid])
|
||||
account = member.get_account(params[:currency])
|
||||
present account, with: API::V2::Management::Entities::Balance
|
||||
status 200
|
||||
end
|
||||
|
||||
desc 'Queries the non-zero balance accounts for the given currency.' do
|
||||
@settings[:scope] = :read_accounts
|
||||
success API::V2::Management::Entities::Balance
|
||||
end
|
||||
|
||||
params do
|
||||
requires :currency, type: String, values: -> { Currency.codes(bothcase: true) }, desc: 'The currency code.'
|
||||
optional :page, type: Integer, default: 1, integer_gt_zero: true, desc: 'The page number (defaults to 1).'
|
||||
optional :limit, type: Integer, default: 1000, range: 1..100000, desc: 'The number of accounts per page (defaults to 100, maximum is 1000).'
|
||||
end
|
||||
|
||||
post '/accounts/balances' do
|
||||
accounts = ::Account.where("currency_id = ? AND (balance > 0 OR locked > 0)", params[:currency])
|
||||
accounts
|
||||
.order(id: :asc)
|
||||
.page(params[:page])
|
||||
.per(params[:limit])
|
||||
.tap { |q| present q, with: API::V2::Management::Entities::Balance }
|
||||
status 200
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
111
app/api/v2/management/beneficiaries.rb
Normal file
111
app/api/v2/management/beneficiaries.rb
Normal file
@@ -0,0 +1,111 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
class Beneficiaries < Grape::API
|
||||
namespace :beneficiaries do
|
||||
|
||||
desc 'Get list of user beneficiaries' do
|
||||
@settings[:scope] = :read_beneficiaries
|
||||
success API::V2::Management::Entities::Beneficiary
|
||||
end
|
||||
params do
|
||||
requires :uid,
|
||||
type: String,
|
||||
desc: 'The shared user ID.'
|
||||
optional :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.codes(bothcase: true) }, message: 'management.currency.doesnt_exist' },
|
||||
as: :currency_id,
|
||||
desc: 'Beneficiary currency code.'
|
||||
optional :state,
|
||||
type: String,
|
||||
values: { value: -> { ::Beneficiary::STATES_AVAILABLE_FOR_MEMBER.map(&:to_s) }, message: 'management.beneficiary.invalid_state'},
|
||||
desc: 'Defines either beneficiary active - user can use it to withdraw money'\
|
||||
'or pending - requires beneficiary activation with pin.'
|
||||
|
||||
end
|
||||
post '/list' do
|
||||
member = Member.find_by!(uid: params[:uid])
|
||||
|
||||
member
|
||||
.beneficiaries
|
||||
.available_to_member
|
||||
.tap { |q| q.where!(currency_id: params[:currency_id]) if params[:currency_id].present? }
|
||||
.tap {|q| q.where!(state: params[:state]) if params[:state].present? }
|
||||
.yield_self { |b| present paginate(b), with: API::V2::Management::Entities::Beneficiary }
|
||||
|
||||
status 200
|
||||
end
|
||||
|
||||
desc 'Create new beneficiary' do
|
||||
@settings[:scope] = :write_beneficiaries
|
||||
success API::V2::Management::Entities::Beneficiary
|
||||
end
|
||||
params do
|
||||
requires :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.codes(bothcase: true) }, message: 'management.currency.doesnt_exist' },
|
||||
as: :currency_id,
|
||||
desc: 'Beneficiary currency code.'
|
||||
requires :name,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
values: { value: ->(v) { v.present? && v.size <= 64 }, message: 'management.beneficiary.too_long_name' },
|
||||
desc: 'Human rememberable name which refer beneficiary.'
|
||||
optional :description,
|
||||
type: String,
|
||||
values: { value: ->(v) { v.size <= 255 }, message: 'management.beneficiary.too_long_description' },
|
||||
desc: 'Human rememberable description which refer beneficiary.'
|
||||
requires :data,
|
||||
type: { value: JSON, message: 'management.beneficiary.non_json_data' },
|
||||
allow_blank: false,
|
||||
desc: 'Beneficiary data in JSON format'
|
||||
requires :uid,
|
||||
type: String,
|
||||
desc: 'The shared user ID.'
|
||||
optional :state,
|
||||
type: String,
|
||||
values: { value: -> { ::Beneficiary::STATES_AVAILABLE_FOR_MEMBER.map(&:to_s) }, message: 'management.beneficiary.invalid_state'},
|
||||
desc: 'Defines either beneficiary active - user can use it to withdraw money'\
|
||||
'or pending - requires beneficiary activation with pin.'
|
||||
end
|
||||
post do
|
||||
declared_params = declared(params)
|
||||
member = Member.find_by!(uid: params[:uid])
|
||||
currency = Currency.find_by!(id: params[:currency_id])
|
||||
|
||||
if !currency.withdrawal_enabled?
|
||||
error!({ errors: ['management.currency.withdrawal_disabled'] }, 422)
|
||||
elsif currency.coin? && declared_params.dig(:data, :address).blank?
|
||||
error!({ errors: ['management.beneficiary.missing_address_in_data'] }, 422)
|
||||
elsif currency.fiat? && declared_params.dig(:data, :full_name).blank?
|
||||
error!({ errors: ['management.beneficiary.missing_full_name_in_data'] }, 422)
|
||||
end
|
||||
|
||||
# Since data is stored in MySQL JSON format we iterate through all
|
||||
# beneficiaries one by one to detect duplicated address.
|
||||
if currency.coin? &&
|
||||
member
|
||||
.beneficiaries
|
||||
.available_to_member
|
||||
.where(currency: currency)
|
||||
.any? { |b| b.data['address'] == declared_params.dig(:data, :address) }
|
||||
error!({ errors: ['management.beneficiary.duplicate_address'] }, 422)
|
||||
end
|
||||
|
||||
present member
|
||||
.beneficiaries
|
||||
.create!(declared_params.except(:uid)),
|
||||
with: API::V2::Management::Entities::Beneficiary
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
report_exception(e)
|
||||
error!({ errors: ['management.beneficiary.failed_to_create'] }, 422)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
124
app/api/v2/management/currencies.rb
Normal file
124
app/api/v2/management/currencies.rb
Normal file
@@ -0,0 +1,124 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
class Currencies < Grape::API
|
||||
# POST: api/v2/management/currencies/list
|
||||
desc 'Return currencies list.' do
|
||||
@settings[:scope] = :read_currencies
|
||||
success API::V2::Management::Entities::Currency
|
||||
end
|
||||
params do
|
||||
optional :type,
|
||||
type: String,
|
||||
values: { value: %w[fiat coin], message: 'management.currency.invalid_type' },
|
||||
desc: -> { API::V2::Entities::Currency.documentation[:type][:desc] }
|
||||
end
|
||||
post '/currencies/list' do
|
||||
currencies = Currency.all
|
||||
currencies = currencies.where(type: params[:type]).includes(:blockchain) if params[:type] == 'coin'
|
||||
currencies = currencies.where(type: params[:type]) if params[:type] == 'fiat'
|
||||
present currencies.ordered, with: API::V2::Entities::Currency
|
||||
|
||||
status 200
|
||||
end
|
||||
|
||||
# POST: api/v2/management/currencies
|
||||
desc 'Returns currency by code.' do
|
||||
@settings[:scope] = :read_currencies
|
||||
success API::V2::Management::Entities::Currency
|
||||
end
|
||||
|
||||
params do
|
||||
requires :code, type: String, desc: 'The currency code.'
|
||||
end
|
||||
post '/currencies/:code', requirements: { code: /[\w\.\-]+/ } do
|
||||
present Currency.find_by!(params.slice(:code)), with: API::V2::Management::Entities::Currency
|
||||
end
|
||||
|
||||
desc 'Update currency.' do
|
||||
@settings[:scope] = :write_currencies
|
||||
success API::V2::Management::Entities::Currency
|
||||
end
|
||||
|
||||
params do
|
||||
requires :id,
|
||||
type: String,
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:id][:desc] }
|
||||
optional :name, desc: -> { API::V2::Management::Entities::Currency.documentation[:name][:desc] }
|
||||
optional :deposit_fee,
|
||||
type: { value: BigDecimal, message: 'management.currency.non_decimal_deposit_fee' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'management.currency.invalid_deposit_fee' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:deposit_fee][:desc] }
|
||||
optional :min_deposit_amount,
|
||||
type: { value: BigDecimal, message: 'management.currency.min_deposit_amount' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'management.currency.invalid_min_deposit_amount' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:min_deposit_amount][:desc] }
|
||||
optional :min_collection_amount,
|
||||
type: { value: BigDecimal, message: 'management.currency.non_decimal_min_collection_amount' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'management.currency.invalid_min_collection_amount' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:min_collection_amount][:desc] }
|
||||
optional :withdraw_fee,
|
||||
type: { value: BigDecimal, message: 'management.currency.non_decimal_withdraw_fee' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'management.currency.invalid_withdraw_fee' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:withdraw_fee][:desc] }
|
||||
optional :min_withdraw_amount,
|
||||
type: { value: BigDecimal, message: 'management.currency.non_decimal_min_withdraw_amount' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'management.currency.invalid_min_withdraw_amount' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:min_withdraw_amount][:desc] }
|
||||
optional :withdraw_limit_24h,
|
||||
type: { value: BigDecimal, message: 'management.currency.non_decimal_withdraw_limit_24h' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'management.currency.invalid_withdraw_limit_24h' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:withdraw_limit_24h][:desc] }
|
||||
optional :withdraw_limit_72h,
|
||||
type: { value: BigDecimal, message: 'management.currency.non_decimal_withdraw_limit_72h' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'management.currency.invalid_withdraw_limit_72h' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:withdraw_limit_72h][:desc] }
|
||||
optional :position,
|
||||
type: { value: Integer, message: 'management.currency.non_integer_position' },
|
||||
values: { value: -> (p){ p >= ::Currency::TOP_POSITION }, message: 'management.currency.invalid_position' },
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:position][:desc] }
|
||||
optional :options,
|
||||
type: { value: JSON, message: 'management.currency.non_json_options' },
|
||||
default: {},
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:options][:desc] }
|
||||
optional :visible,
|
||||
type: { value: Boolean, message: 'management.currency.non_boolean_visible' },
|
||||
default: true,
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:visible][:desc] }
|
||||
optional :deposit_enabled,
|
||||
type: { value: Boolean, message: 'management.currency.non_boolean_deposit_enabled' },
|
||||
default: true,
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:deposit_enabled][:desc] }
|
||||
optional :withdrawal_enabled,
|
||||
type: { value: Boolean, message: 'management.currency.non_boolean_withdrawal_enabled' },
|
||||
default: true,
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:withdrawal_enabled][:desc] }
|
||||
optional :precision,
|
||||
type: { value: Integer, message: 'management.currency.non_integer_base_precision' },
|
||||
default: 8,
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:precision][:desc] }
|
||||
optional :icon_url, desc: -> { API::V2::Management::Entities::Currency.documentation[:icon_url][:desc] }
|
||||
end
|
||||
put '/currencies/update' do
|
||||
currency = ::Currency.find_by!(params.slice(:id))
|
||||
if currency.update(declared(params, include_missing: false))
|
||||
present currency, with: API::V2::Management::Entities::Currency
|
||||
else
|
||||
body errors: currency.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
104
app/api/v2/management/deposits.rb
Normal file
104
app/api/v2/management/deposits.rb
Normal file
@@ -0,0 +1,104 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
class Deposits < Grape::API
|
||||
|
||||
desc 'Returns deposits as paginated collection.' do
|
||||
@settings[:scope] = :read_deposits
|
||||
success API::V2::Management::Entities::Deposit
|
||||
end
|
||||
params do
|
||||
optional :uid, type: String, desc: 'The shared user ID.'
|
||||
optional :currency, type: String, values: -> { Currency.codes(bothcase: true) }, desc: 'The currency code.'
|
||||
optional :page, type: Integer, default: 1, integer_gt_zero: true, desc: 'The page number (defaults to 1).'
|
||||
optional :limit, type: Integer, default: 100, range: 1..1000, desc: 'The number of deposits per page (defaults to 100, maximum is 1000).'
|
||||
optional :state, type: String, values: -> { ::Deposit.aasm.states.map(&:name).map(&:to_s) }, desc: 'The state to filter by.'
|
||||
end
|
||||
post '/deposits' do
|
||||
currency = Currency.find(params[:currency]) if params[:currency].present?
|
||||
member = Member.find_by!(uid: params[:uid]) if params[:uid].present?
|
||||
Deposit
|
||||
.order(id: :desc)
|
||||
.tap { |q| q.where!(currency: currency) if currency }
|
||||
.tap { |q| q.where!(member: member) if member }
|
||||
.tap { |q| q.where!(aasm_state: params[:state]) if params[:state] }
|
||||
.includes(:member, :currency)
|
||||
.page(params[:page])
|
||||
.per(params[:limit])
|
||||
.tap { |q| present q, with: API::V2::Management::Entities::Deposit }
|
||||
status 200
|
||||
end
|
||||
|
||||
desc 'Returns deposit by TID.' do
|
||||
@settings[:scope] = :read_deposits
|
||||
success API::V2::Management::Entities::Deposit
|
||||
end
|
||||
params do
|
||||
requires :tid, type: String, desc: 'The transaction ID.'
|
||||
end
|
||||
post '/deposits/get' do
|
||||
present Deposit.find_by!(params.slice(:tid)), with: API::V2::Management::Entities::Deposit
|
||||
end
|
||||
|
||||
desc 'Creates new fiat deposit with state set to «submitted». ' \
|
||||
'Optionally pass field «state» set to «accepted» if want to load money instantly. ' \
|
||||
'You can also use PUT /fiat_deposits/:id later to load money or cancel deposit.' do
|
||||
@settings[:scope] = :write_deposits
|
||||
success API::V2::Management::Entities::Deposit
|
||||
end
|
||||
params do
|
||||
requires :uid, type: String, desc: 'The shared user ID.'
|
||||
optional :tid, type: String, desc: 'The shared transaction ID. Must not exceed 64 characters. Peatio will generate one automatically unless supplied.'
|
||||
requires :currency, type: String, values: -> { Currency.fiats.codes(bothcase: true) }, desc: 'The currency code.'
|
||||
requires :amount, type: BigDecimal, desc: 'The deposit amount.'
|
||||
optional :state, type: String, desc: 'The state of deposit.', values: %w[accepted]
|
||||
optional :transfer_type, type: String,
|
||||
values: { value: -> { Deposit::TRANSFER_TYPES.keys }, message: 'account.deposit.transfer_type_not_in_list' },
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:transfer_type][:desc] }
|
||||
end
|
||||
post '/deposits/new' do
|
||||
member = Member.find_by(uid: params[:uid])
|
||||
currency = Currency.find(params[:currency])
|
||||
|
||||
unless currency.deposit_enabled?
|
||||
error!({ errors: ['management.currency.deposit_disabled'] }, 422)
|
||||
end
|
||||
|
||||
data = { member: member, currency: currency }.merge!(params.slice(:amount, :tid, :transfer_type))
|
||||
deposit = ::Deposits::Fiat.new(data)
|
||||
if deposit.save
|
||||
deposit.charge! if params[:state] == 'accepted'
|
||||
present deposit, with: API::V2::Management::Entities::Deposit
|
||||
else
|
||||
body errors: deposit.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Allows to load money or cancel deposit.' do
|
||||
@settings[:scope] = :write_deposits
|
||||
success API::V2::Management::Entities::Deposit
|
||||
end
|
||||
params do
|
||||
requires :tid, type: String, desc: 'The shared transaction ID.'
|
||||
requires :state, type: String, desc: 'The new state to apply.', values: %w[canceled accepted]
|
||||
end
|
||||
put '/deposits/state' do
|
||||
deposit = ::Deposits::Fiat.find_by!(params.slice(:tid))
|
||||
if deposit.submitted?
|
||||
deposit.with_lock do
|
||||
params[:state] == 'canceled' ? deposit.cancel! : deposit.accept!
|
||||
end
|
||||
present deposit, with: API::V2::Management::Entities::Deposit
|
||||
status 200
|
||||
else
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
99
app/api/v2/management/engines.rb
Normal file
99
app/api/v2/management/engines.rb
Normal file
@@ -0,0 +1,99 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
class Engines < Grape::API
|
||||
namespace :engines do
|
||||
desc 'Get all engine, result is paginated.' do
|
||||
@settings[:scope] = :read_engines
|
||||
success API::V2::Management::Entities::Engine
|
||||
end
|
||||
params do
|
||||
optional :limit,
|
||||
type: { value: Integer, message: 'management.pagination.non_integer_limit' },
|
||||
values: { value: 1..1000, message: 'management.pagination.invalid_limit' },
|
||||
default: 100,
|
||||
desc: 'Limit the number of returned paginations. Defaults to 100.'
|
||||
optional :page,
|
||||
type: { value: Integer, message: 'management.pagination.non_integer_page' },
|
||||
allow_blank: false,
|
||||
default: 1,
|
||||
desc: 'Specify the page of paginated results.'
|
||||
optional :ordering,
|
||||
values: { value: %w(asc desc), message: 'management.pagination.invalid_ordering' },
|
||||
default: 'asc',
|
||||
desc: 'If set, returned values will be sorted in specific order, defaults to \'asc\'.'
|
||||
optional :order_by,
|
||||
default: 'id',
|
||||
desc: 'Name of the field, which result will be ordered by.'
|
||||
end
|
||||
post '/get' do
|
||||
result = ::Engine.order(params[:order_by] => params[:ordering])
|
||||
present paginate(result), with: API::V2::Management::Entities::Engine
|
||||
end
|
||||
|
||||
desc 'Creates new engine' do
|
||||
@settings[:scope] = :write_engines
|
||||
success API::V2::Management::Entities::Engine
|
||||
end
|
||||
params do
|
||||
requires :name,
|
||||
desc: -> { API::V2::Management::Entities::Engine.documentation[:name][:desc] },
|
||||
values: { value: ->(v) { !v.in?(::Engine.pluck(:name)) }, message: 'management.engine.duplicate_name' }
|
||||
requires :driver,
|
||||
desc: -> { API::V2::Management::Entities::Engine.documentation[:driver][:desc] }
|
||||
optional :uid,
|
||||
desc: -> { API::V2::Management::Entities::Engine.documentation[:uid][:desc] }
|
||||
optional :key,
|
||||
desc: -> { 'Credentials for remote engine' }
|
||||
optional :secret,
|
||||
desc: -> { 'Credentials for remote engine' }
|
||||
optional :data,
|
||||
desc: -> { 'Metadata for engine' }
|
||||
end
|
||||
post do
|
||||
engine = ::Engine.new(declared(params))
|
||||
if engine.save
|
||||
present engine, with: API::V2::Management::Entities::Engine
|
||||
status 201
|
||||
else
|
||||
body errors: engine.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Update engine' do
|
||||
@settings[:scope] = :write_engines
|
||||
success API::V2::Management::Entities::Engine
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
desc: -> { API::V2::Management::Entities::Engine.documentation[:id][:desc] }
|
||||
optional :name,
|
||||
desc: -> { API::V2::Management::Entities::Engine.documentation[:name][:desc] }
|
||||
optional :driver,
|
||||
desc: -> { API::V2::Management::Entities::Engine.documentation[:driver][:desc] }
|
||||
optional :key,
|
||||
desc: -> { 'Credentials for remote engine' }
|
||||
optional :secret,
|
||||
desc: -> { 'Credentials for remote engine' }
|
||||
optional :state,
|
||||
values: { value: ::Engine::STATES.values, message: 'management.engine.invalid_state' },
|
||||
default: 1,
|
||||
desc: -> { API::V2::Management::Entities::Engine.documentation[:state][:desc] }
|
||||
end
|
||||
post '/update' do
|
||||
engine = ::Engine.find(params[:id])
|
||||
if engine.update(declared(params, include_missing: false))
|
||||
present engine, with: API::V2::Management::Entities::Engine
|
||||
else
|
||||
body errors: engine.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
16
app/api/v2/management/entities/balance.rb
Normal file
16
app/api/v2/management/entities/balance.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
module Entities
|
||||
class Balance < Base
|
||||
expose(:uid, documentation: { type: String, desc: 'The shared user ID.' }) { |w| w.member.uid }
|
||||
expose(:balance, documentation: { type: String, desc: 'The account balance.' }, format_with: :decimal)
|
||||
expose(:locked, documentation: { type: String, desc: 'The locked account balance.' }, format_with: :decimal)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
15
app/api/v2/management/entities/base.rb
Normal file
15
app/api/v2/management/entities/base.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
module Entities
|
||||
class Base < Grape::Entity
|
||||
format_with(:iso8601) { |t| t.to_time.in_time_zone(::Rails.configuration.time_zone).iso8601 if t }
|
||||
format_with(:decimal) { |d| d.to_s('F') if d }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
20
app/api/v2/management/entities/beneficiary.rb
Normal file
20
app/api/v2/management/entities/beneficiary.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
module Entities
|
||||
class Beneficiary < API::V2::Entities::Beneficiary
|
||||
expose(
|
||||
:data,
|
||||
documentation: {
|
||||
desc: 'Bank Account details for fiat Beneficiary in JSON format.'\
|
||||
'For crypto it\'s blockchain address.',
|
||||
type: JSON
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
80
app/api/v2/management/entities/currency.rb
Normal file
80
app/api/v2/management/entities/currency.rb
Normal file
@@ -0,0 +1,80 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
module Entities
|
||||
class Currency < ::API::V2::Entities::Currency
|
||||
|
||||
expose(
|
||||
:code,
|
||||
documentation: {
|
||||
desc: 'Unique currency code.',
|
||||
type: String
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:min_collection_amount,
|
||||
documentation: {
|
||||
desc: 'Minimal deposit amount that will be collected',
|
||||
example: -> { ::Currency.visible.first.min_collection_amount }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:visible,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Currency display possibility status (true/false).'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:subunits,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Fraction of the basic monetary unit.'
|
||||
}
|
||||
) { |currency| currency.subunits }
|
||||
|
||||
expose(
|
||||
:options,
|
||||
documentation: {
|
||||
type: JSON,
|
||||
desc: 'Currency options.'
|
||||
},
|
||||
if: -> (currency){ currency.coin? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:position,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Currency position.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Currency created time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Currency updated time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
30
app/api/v2/management/entities/deposit.rb
Normal file
30
app/api/v2/management/entities/deposit.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
module Entities
|
||||
class Deposit < Base
|
||||
expose :tid, documentation: { type: Integer, desc: 'The shared transaction ID.' }
|
||||
expose :currency_id, as: :currency, documentation: { type: String, desc: 'The currency code.' }
|
||||
expose(:uid, documentation: { type: String, desc: 'The shared user ID.' }) { |w| w.member.uid }
|
||||
expose(:type, documentation: { type: String, desc: 'The deposit type (fiat or coin).' }) { |d| d.class.name.demodulize.underscore }
|
||||
expose :amount, documentation: { type: String, desc: 'The deposit amount.' }, format_with: :decimal
|
||||
states = [
|
||||
'«submitted» – initial state.',
|
||||
'«canceled» – deposit has been canceled by outer service.',
|
||||
'«rejected» – deposit has been rejected by outer service..',
|
||||
'«accepted» – deposit has been accepted by outer service, money are loaded.'
|
||||
]
|
||||
expose :aasm_state, as: :state, documentation: { type: String, desc: 'The deposit state. ' + states.join(' ') }
|
||||
expose :created_at, format_with: :iso8601, documentation: { type: String, desc: 'The datetime when deposit was created.' }
|
||||
expose :completed_at, format_with: :iso8601, documentation: { type: String, desc: 'The datetime when deposit was completed.' }
|
||||
expose :txid, as: :blockchain_txid, if: -> (d, _) { d.currency.coin? }, documentation: { type: String, desc: 'The transaction ID on the Blockchain (coin only).' }
|
||||
expose :confirmations, as: :blockchain_confirmations, if: -> (d, _) { d.currency.coin? }, documentation: { type: String, desc: 'The number of transaction confirmations on the Blockchain (coin only).' }
|
||||
expose :transfer_type, documentation: { type: String, desc: 'deposit transfer_type.' }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
51
app/api/v2/management/entities/engine.rb
Normal file
51
app/api/v2/management/entities/engine.rb
Normal file
@@ -0,0 +1,51 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
module Entities
|
||||
class Engine < API::V2::Entities::Base
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Engine uniq id'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:name,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Engine name'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:driver,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Engine driver'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:uid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Owner of a engine'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:state,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Engine state'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
38
app/api/v2/management/entities/market.rb
Normal file
38
app/api/v2/management/entities/market.rb
Normal file
@@ -0,0 +1,38 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
module Entities
|
||||
class Market < ::API::V2::Entities::Market
|
||||
expose(
|
||||
:position,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Market position.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Market created time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Market updated time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
66
app/api/v2/management/entities/member.rb
Normal file
66
app/api/v2/management/entities/member.rb
Normal file
@@ -0,0 +1,66 @@
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
module Entities
|
||||
class Member < Base
|
||||
expose(
|
||||
:uid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The shared user ID.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:email,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'User email.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:level,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'User level.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:role,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'User role.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:group,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'User group (vip-0, vip-1, etc).'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:state,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'User state (active/pending/banned).'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'User create time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
53
app/api/v2/management/entities/operation.rb
Normal file
53
app/api/v2/management/entities/operation.rb
Normal file
@@ -0,0 +1,53 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
module Entities
|
||||
class Operation < Base
|
||||
expose :code,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The Account code which this operation related to.'
|
||||
}
|
||||
expose :currency_id,
|
||||
as: :currency,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Operation currency ID.'
|
||||
}
|
||||
expose :credit,
|
||||
if: ->(operation) { !operation.credit.zero? },
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Operation credit amount.'
|
||||
}
|
||||
expose :debit,
|
||||
if: ->(operation) { !operation.debit.zero? },
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Operation debit amount.'
|
||||
}
|
||||
expose(:uid,
|
||||
if: ->(operation) { operation.try(:member).try(:uid) },
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The shared user ID.'
|
||||
}) { |operation| operation.try(:member).try(:uid) }
|
||||
expose(:reference_type,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The type of operations.'
|
||||
}) { |operation| operation.reference_type.downcase if operation.reference_type.present? }
|
||||
expose :created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The datetime when operation was created.'
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
176
app/api/v2/management/entities/order.rb
Normal file
176
app/api/v2/management/entities/order.rb
Normal file
@@ -0,0 +1,176 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
module Entities
|
||||
class Order < Base
|
||||
expose(
|
||||
:id,
|
||||
documentation:{
|
||||
type: Integer,
|
||||
desc: "Unique order id."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:member_id,
|
||||
documentation:{
|
||||
type: Integer,
|
||||
desc: "Member id."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:uuid,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: "Unique order UUID."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:side,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "Either 'sell' or 'buy'."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:ord_type,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "Type of order, either 'limit' or 'market'."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:price,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "Price for each unit. e.g."\
|
||||
"If you want to sell/buy 1 btc at 3000 usd, the price is '3000.0'"
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:avg_price,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "Average execution price, average of price in trades."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:state,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "One of 'wait', 'done', or 'cancel'."\
|
||||
"An order in 'wait' is an active order, waiting fulfillment;"\
|
||||
"a 'done' order is an order fulfilled;"\
|
||||
"'cancel' means the order has been canceled."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:market_id,
|
||||
as: :market,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "The market in which the order is placed, e.g. 'btcusd'."\
|
||||
"All available markets can be found at /api/v2/markets."
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "Order create time in iso8601 format."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "Order updated time in iso8601 format."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:origin_volume,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "The amount user want to sell/buy."\
|
||||
"An order could be partially executed,"\
|
||||
"e.g. an order sell 5 btc can be matched with a buy 3 btc order,"\
|
||||
"left 2 btc to be sold; in this case the order's volume would be '5.0',"\
|
||||
"its remaining_volume would be '2.0', its executed volume is '3.0'."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:volume,
|
||||
as: :remaining_volume,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "The remaining volume, see 'volume'."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:executed_volume,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "The executed volume, see 'volume'."
|
||||
}
|
||||
) do |order, _options|
|
||||
order.origin_volume - order.volume
|
||||
end
|
||||
|
||||
expose(
|
||||
:maker_fee,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "Fee for maker."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:taker_fee,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "Fee for taker."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:trades_count,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: "Count of trades."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:trades,
|
||||
documentation: {
|
||||
type: 'API::V2::Entities::Trade',
|
||||
is_array: true,
|
||||
desc: "Trades wiht this order."
|
||||
},
|
||||
if: { type: :full }
|
||||
) do |order, _options|
|
||||
API::V2::Entities::Trade.represent order.trades, side: order.side
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
27
app/api/v2/management/entities/payment_address.rb
Normal file
27
app/api/v2/management/entities/payment_address.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
module Entities
|
||||
class PaymentAddress < ::API::V2::Entities::PaymentAddress
|
||||
expose(
|
||||
:uid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The shared user ID.'
|
||||
}
|
||||
) { |w| w.member.uid }
|
||||
|
||||
expose(
|
||||
:remote,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Payment address remote creation (true/false).'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
130
app/api/v2/management/entities/trade.rb
Normal file
130
app/api/v2/management/entities/trade.rb
Normal file
@@ -0,0 +1,130 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
module Entities
|
||||
class Trade < Base
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade ID.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:price,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Trade price.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:amount,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Trade amount.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:total,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Trade total.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:market_id,
|
||||
as: :market,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade market id.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade create time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:maker_order_id,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade maker order id.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:taker_order_id,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade taker order id.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:maker_member_uid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade ask member uid.'
|
||||
}
|
||||
) do |trade|
|
||||
trade.maker.uid
|
||||
end
|
||||
|
||||
expose(
|
||||
:taker_member_uid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade bid member uid.'
|
||||
}
|
||||
) do |trade|
|
||||
trade.taker.uid
|
||||
end
|
||||
|
||||
expose(
|
||||
:taker_type,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade maker order type (sell or buy).'
|
||||
}
|
||||
) do |trade, _options|
|
||||
trade.taker_order.side
|
||||
end
|
||||
|
||||
expose(
|
||||
:side,
|
||||
if: ->(trade, options) { options[:side] || options[:current_user] },
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade side.'
|
||||
}
|
||||
) do |trade, options|
|
||||
options[:side] || trade.order_for_member(options[:current_user]).side
|
||||
end
|
||||
|
||||
expose(
|
||||
:order_id,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Order id.'
|
||||
},
|
||||
if: ->(_, options) { options[:current_user] }
|
||||
) do |trade, options|
|
||||
trade.order_for_member(options[:current_user]).id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
40
app/api/v2/management/entities/transfer.rb
Normal file
40
app/api/v2/management/entities/transfer.rb
Normal file
@@ -0,0 +1,40 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
module Entities
|
||||
class Transfer < Base
|
||||
expose :key,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Unique Transfer Key.'
|
||||
}
|
||||
|
||||
expose :category,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Transfer Category.'
|
||||
}
|
||||
|
||||
expose :description,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Transfer Description'
|
||||
}
|
||||
|
||||
# Expose assets, expenses, liabilities, revenues if present.
|
||||
::Operations::Account::TYPES.map(&:pluralize).each do |op_t|
|
||||
expose op_t,
|
||||
using: Operation,
|
||||
if: ->(transfer) { transfer.public_send(op_t).present? },
|
||||
documentation: {
|
||||
desc: "Transfer #{op_t}"
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
36
app/api/v2/management/entities/withdraw.rb
Normal file
36
app/api/v2/management/entities/withdraw.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
module Entities
|
||||
class Withdraw < Base
|
||||
expose :tid, documentation: { type: Integer, desc: 'The shared transaction ID.' }
|
||||
expose(:uid, documentation: { type: String, desc: 'The shared user ID.' }) { |w| w.member.uid }
|
||||
expose :currency_id, as: :currency, documentation: { type: String, desc: 'The currency code.' }
|
||||
expose :note, documentation: { type: String, desc: 'The note for withdraw.' }
|
||||
expose(:type, documentation: { type: String, desc: 'The withdraw type (fiat or coin).' }) { |w| w.class.name.demodulize.underscore }
|
||||
expose :amount, documentation: { type: String, desc: 'The withdraw amount excluding fee.' }, format_with: :decimal
|
||||
expose :fee, documentation: { type: String, desc: 'The exchange fee.' }, format_with: :decimal
|
||||
expose :rid, documentation: { type: String, desc: 'The beneficiary ID or wallet address on the Blockchain.' }
|
||||
states = [
|
||||
'«prepared» – initial state, money are not locked.',
|
||||
'«submitted» – withdraw has been allowed by outer service for further validation, money are locked.',
|
||||
'«canceled» – withdraw has been canceled by outer service, money are unlocked.',
|
||||
'«accepted» – system has validated withdraw and queued it for processing by worker, money are locked.',
|
||||
'«rejected» – system has validated withdraw and found errors, money are unlocked.',
|
||||
'«processing» – worker is processing withdraw as the current moment, money are locked.',
|
||||
'«skipped» – worker skipped withdrawal in case of insufficient balance of hot wallet or it absence.',
|
||||
'«succeed» – worker has successfully processed withdraw, money are subtracted from the account.',
|
||||
'«failed» – worker has encountered an unhandled error while processing withdraw, money are unlocked.'
|
||||
]
|
||||
expose :aasm_state, as: :state, documentation: { type: String, desc: 'The withdraw state. ' + states.join(' ') }
|
||||
expose :created_at, format_with: :iso8601, documentation: { type: String, desc: 'The datetime when withdraw was created.' }
|
||||
expose :txid, as: :blockchain_txid, documentation: { type: String, desc: 'The transaction ID on the Blockchain (coin only).' }, if: -> (w, _) { w.currency.coin? }
|
||||
expose :transfer_type, documentation: { type: String, desc: 'withdraw transfer_type.' }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
16
app/api/v2/management/exceptions/authentication.rb
Normal file
16
app/api/v2/management/exceptions/authentication.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
module Exceptions
|
||||
class Authentication < Base
|
||||
def status
|
||||
@options.fetch(:status, 401)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
34
app/api/v2/management/exceptions/base.rb
Normal file
34
app/api/v2/management/exceptions/base.rb
Normal file
@@ -0,0 +1,34 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
module Exceptions
|
||||
class Base < StandardError
|
||||
def initialize(message:, **options)
|
||||
@options = options
|
||||
super(message)
|
||||
end
|
||||
|
||||
def debug_message
|
||||
@options[:debug_message]
|
||||
end
|
||||
|
||||
def headers
|
||||
@options.fetch(:headers, {})
|
||||
end
|
||||
|
||||
def status
|
||||
@options.fetch(:status)
|
||||
end
|
||||
|
||||
# Change "#<Exception: message>" to "#<Exception: message (debug_message)>".
|
||||
def inspect
|
||||
debug_message.present? ? super.gsub(/>\z/, " (#{debug_message})>") : super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
125
app/api/v2/management/helpers.rb
Normal file
125
app/api/v2/management/helpers.rb
Normal file
@@ -0,0 +1,125 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
module Helpers
|
||||
def create_operation!(attrs)
|
||||
account = ::Operations::Account.find_by(code: attrs.fetch(:code))
|
||||
if account.scope.member?
|
||||
create_member_operation!(attrs)
|
||||
else
|
||||
create_platform_operation!(attrs)
|
||||
end
|
||||
end
|
||||
|
||||
def set_ets_context!
|
||||
Raven.tags_context(
|
||||
peatio_version: Peatio::Application::VERSION
|
||||
) if defined?(Raven)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_platform_operation!(attrs)
|
||||
currency = Currency.find(attrs.fetch(:currency))
|
||||
klass = ::Operations::Account
|
||||
.find_by(code: attrs.fetch(:code))
|
||||
.type
|
||||
.yield_self { |type| "operations/#{type}" }
|
||||
.camelize
|
||||
.constantize
|
||||
|
||||
if klass == ::Operations::Revenue && attrs.dig(:uid)
|
||||
member_id = Member.find_by!(uid: attrs.fetch(:uid)).id
|
||||
else
|
||||
member_id = nil
|
||||
end
|
||||
|
||||
if attrs[:credit].present?
|
||||
klass.credit!({
|
||||
amount: attrs.fetch(:credit),
|
||||
currency: currency,
|
||||
code: attrs.fetch(:code),
|
||||
member_id: member_id,
|
||||
reference: attrs[:reference]
|
||||
}.compact)
|
||||
elsif attrs[:debit].present?
|
||||
klass.debit!({
|
||||
amount: attrs.fetch(:debit),
|
||||
currency: currency,
|
||||
code: attrs.fetch(:code),
|
||||
member_id: member_id,
|
||||
reference: attrs[:reference]
|
||||
}.compact)
|
||||
end
|
||||
end
|
||||
|
||||
def create_member_operation!(attrs)
|
||||
member = Member.find_by!(uid: attrs.fetch(:uid))
|
||||
currency = Currency.find(attrs.fetch(:currency))
|
||||
klass = ::Operations::Account
|
||||
.find_by(code: attrs.fetch(:code))
|
||||
.type
|
||||
.yield_self { |type| "operations/#{type}" }
|
||||
.camelize
|
||||
.constantize
|
||||
|
||||
if attrs[:credit].present?
|
||||
amount = attrs.fetch(:credit)
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
op = klass.credit!(amount: amount,
|
||||
currency: currency,
|
||||
code: attrs.fetch(:code),
|
||||
member_id: member.id,
|
||||
reference: attrs[:reference])
|
||||
|
||||
credit_legacy_balance!(amount: amount,
|
||||
member: member,
|
||||
currency: currency,
|
||||
account: op.account)
|
||||
op
|
||||
end
|
||||
elsif attrs[:debit].present?
|
||||
amount = attrs.fetch(:debit)
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
op = klass.debit!(amount: amount,
|
||||
currency: currency,
|
||||
code: attrs.fetch(:code),
|
||||
member_id: member.id,
|
||||
reference: attrs[:reference])
|
||||
|
||||
debit_legacy_balance!(amount: amount,
|
||||
member: member,
|
||||
currency: currency,
|
||||
account: op.account)
|
||||
op
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# @deprecated
|
||||
def credit_legacy_balance!(amount:, member:, currency:, account:)
|
||||
if account.kind.main?
|
||||
member.get_account(currency).plus_funds(amount)
|
||||
elsif account.kind.locked?
|
||||
member.get_account(currency).plus_funds(amount)
|
||||
member.get_account(currency).lock_funds(amount)
|
||||
end
|
||||
end
|
||||
|
||||
# @deprecated
|
||||
def debit_legacy_balance!(amount:, member:, currency:, account:)
|
||||
if account.kind.main?
|
||||
member.get_account(currency).sub_funds(amount)
|
||||
elsif account.kind.locked?
|
||||
member.get_account(currency).unlock_and_sub_funds(amount)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
93
app/api/v2/management/jwt_authentication_middleware.rb
Normal file
93
app/api/v2/management/jwt_authentication_middleware.rb
Normal file
@@ -0,0 +1,93 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'stringio'
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
class JWTAuthenticationMiddleware < Grape::Middleware::Base
|
||||
extend Memoist
|
||||
|
||||
def before
|
||||
return if request.path == '/api/v2/management/swagger'
|
||||
check_request_method!
|
||||
check_query_parameters!
|
||||
check_content_type!
|
||||
payload = check_jwt!(jwt)
|
||||
env['rack.input'] = StringIO.new(payload.fetch(:data, {}).to_json)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def request
|
||||
Grape::Request.new(env)
|
||||
end
|
||||
memoize :request
|
||||
|
||||
def jwt
|
||||
JSON.parse(request.body.read)
|
||||
rescue => e
|
||||
raise Exceptions::Authentication, \
|
||||
message: 'Couldn\'t parse JWT.',
|
||||
debug_message: e.inspect,
|
||||
status: 400
|
||||
end
|
||||
memoize :jwt
|
||||
|
||||
def check_request_method!
|
||||
unless request.post? || request.put? || request.delete?
|
||||
raise Exceptions::Authentication, \
|
||||
message: 'Only POST, PUT, and DELETE verbs are allowed.',
|
||||
status: 405
|
||||
end
|
||||
end
|
||||
|
||||
def check_query_parameters!
|
||||
unless request.GET.empty?
|
||||
raise Exceptions::Authentication, \
|
||||
message: 'Query parameters are not allowed.',
|
||||
status: 400
|
||||
end
|
||||
end
|
||||
|
||||
def check_content_type!
|
||||
unless request.content_type == 'application/json'
|
||||
raise Exceptions::Authentication, \
|
||||
message: 'Only JSON body is accepted.',
|
||||
status: 400
|
||||
end
|
||||
end
|
||||
|
||||
def check_jwt!(jwt)
|
||||
security_configuration = ::Rails.configuration.x.security_configuration
|
||||
begin
|
||||
scope = security_configuration.fetch(:scopes).fetch(security_scope)
|
||||
keychain = security_configuration
|
||||
.fetch(:keychain)
|
||||
.slice(*scope.fetch(:permitted_signers))
|
||||
.each_with_object({}) { |(k, v), memo| memo[k] = v.fetch(:value) }
|
||||
result = JWT::Multisig.verify_jwt(jwt, keychain, security_configuration.fetch(:jwt, {}))
|
||||
rescue => e
|
||||
raise Exceptions::Authentication, \
|
||||
message: 'Failed to verify JWT.',
|
||||
debug_message: e.inspect,
|
||||
status: 401
|
||||
end
|
||||
|
||||
unless (scope.fetch(:mandatory_signers) - result[:verified]).empty?
|
||||
raise Exceptions::Authentication, \
|
||||
message: 'Not enough signatures for the action.',
|
||||
status: 401
|
||||
end
|
||||
|
||||
result[:payload]
|
||||
end
|
||||
|
||||
def security_scope
|
||||
request.env['api.endpoint'].options.fetch(:route_options).fetch(:scope)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
69
app/api/v2/management/markets.rb
Normal file
69
app/api/v2/management/markets.rb
Normal file
@@ -0,0 +1,69 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
class Markets < Grape::API
|
||||
# PUT: api/v2/management/markets/update
|
||||
desc 'Update market.' do
|
||||
@settings[:scope] = :write_markets
|
||||
success API::V2::Management::Entities::Market
|
||||
end
|
||||
|
||||
params do
|
||||
requires :id,
|
||||
type: String,
|
||||
desc: -> { API::V2::Management::Entities::Market.documentation[:id][:desc] }
|
||||
optional :state,
|
||||
type: String,
|
||||
values: { value: ::Market::STATES },
|
||||
desc: -> { API::V2::Management::Entities::Market.documentation[:state][:desc] }
|
||||
optional :min_price,
|
||||
type: { value: BigDecimal },
|
||||
values: { value: ->(p) { p >= 0 } },
|
||||
desc: -> { API::V2::Management::Entities::Market.documentation[:min_price][:desc] }
|
||||
optional :min_amount,
|
||||
type: { value: BigDecimal },
|
||||
values: { value: ->(p) { p >= 0 } },
|
||||
desc: -> { API::V2::Management::Entities::Market.documentation[:min_amount][:desc] }
|
||||
optional :amount_precision,
|
||||
type: { value: Integer },
|
||||
values: { value: ->(p) { p && p >= 0 } },
|
||||
desc: -> { API::V2::Management::Entities::Market.documentation[:amount_precision][:desc] }
|
||||
optional :price_precision,
|
||||
type: { value: Integer },
|
||||
values: { value: ->(p) { p && p >= 0 } },
|
||||
desc: -> { API::V2::Management::Entities::Market.documentation[:price_precision][:desc] }
|
||||
optional :max_price,
|
||||
type: { value: BigDecimal },
|
||||
values: { value: ->(p) { p >= 0 } },
|
||||
desc: -> { API::V2::Management::Entities::Market.documentation[:max_price][:desc] }
|
||||
optional :position,
|
||||
type: { value: Integer },
|
||||
values: { value: -> (p){ p >= ::Market::TOP_POSITION } },
|
||||
desc: -> { API::V2::Management::Entities::Market.documentation[:position][:desc] }
|
||||
end
|
||||
put '/markets/update' do
|
||||
market = ::Market.find_by!(params.slice(:id))
|
||||
if market.update(declared(params, include_missing: false))
|
||||
present market, with: API::V2::Management::Entities::Market
|
||||
else
|
||||
body errors: market.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
# POST: api/v2/management/markets/list
|
||||
desc 'Return markets list.' do
|
||||
@settings[:scope] = :read_markets
|
||||
success API::V2::Management::Entities::Market
|
||||
end
|
||||
post '/markets/list' do
|
||||
present ::Market.ordered, with: API::V2::Management::Entities::Market
|
||||
status 200
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
65
app/api/v2/management/members.rb
Normal file
65
app/api/v2/management/members.rb
Normal file
@@ -0,0 +1,65 @@
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
class Members < Grape::API
|
||||
|
||||
desc 'Create a member.' do
|
||||
@settings[:scope] = :write_members
|
||||
end
|
||||
params do
|
||||
requires :email,
|
||||
type: String,
|
||||
desc: 'User email.'
|
||||
requires :uid,
|
||||
type: String,
|
||||
desc: 'The shared user ID.'
|
||||
requires :level,
|
||||
type: Integer,
|
||||
desc: 'User level.'
|
||||
requires :role,
|
||||
type: String,
|
||||
desc: 'User role.'
|
||||
requires :state,
|
||||
type: String,
|
||||
desc: 'User state.'
|
||||
requires :group,
|
||||
type: String,
|
||||
desc: 'User group'
|
||||
end
|
||||
post '/members' do
|
||||
declared_params = declared(params)
|
||||
|
||||
member = Member.create!(declared_params)
|
||||
present member, with: Entities::Member
|
||||
status 200
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
body errors: e.message
|
||||
status 422
|
||||
end
|
||||
|
||||
desc 'Set user group.' do
|
||||
@settings[:scope] = :write_members
|
||||
end
|
||||
params do
|
||||
requires :uid,
|
||||
type: String,
|
||||
desc: 'The shared user ID.'
|
||||
requires :group,
|
||||
type: String,
|
||||
desc: 'User gruop'
|
||||
end
|
||||
post '/members/group' do
|
||||
declared_params = declared(params)
|
||||
|
||||
member = Member.find_by!(uid: declared_params[:uid])
|
||||
member.update!(group: declared_params[:group])
|
||||
present member, with: Entities::Member
|
||||
status 200
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
body errors: e.message
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
80
app/api/v2/management/mount.rb
Normal file
80
app/api/v2/management/mount.rb
Normal file
@@ -0,0 +1,80 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
class Mount < Grape::API
|
||||
PREFIX = '/management'
|
||||
|
||||
format :json
|
||||
content_type :json, 'application/json'
|
||||
default_format :json
|
||||
|
||||
do_not_route_options!
|
||||
|
||||
helpers Management::Helpers
|
||||
|
||||
before { set_ets_context! }
|
||||
|
||||
rescue_from Management::Exceptions::Base do |e|
|
||||
Management::Mount.logger.error { e.inspect }
|
||||
error!(e.message, e.status, e.headers)
|
||||
end
|
||||
|
||||
rescue_from Grape::Exceptions::ValidationErrors do |e|
|
||||
Management::Mount.logger.error { e.inspect }
|
||||
Management::Mount.logger.debug { e.full_messages }
|
||||
error!(e.message, 422)
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound do |e|
|
||||
Management::Mount.logger.error { e.inspect }
|
||||
error!('Couldn\'t find record.', 404)
|
||||
end
|
||||
|
||||
use Management::JWTAuthenticationMiddleware
|
||||
|
||||
mount Management::Accounts
|
||||
mount Management::Deposits
|
||||
mount Management::Withdraws
|
||||
mount Management::Tools
|
||||
mount Management::Operations
|
||||
mount Management::Orders
|
||||
mount Management::Transfers
|
||||
mount Management::Trades
|
||||
mount Management::Members
|
||||
mount Management::TradingFees
|
||||
mount Management::Currencies
|
||||
mount Management::Markets
|
||||
mount Management::Beneficiaries
|
||||
mount Management::PaymentAddress
|
||||
mount Management::Engines
|
||||
|
||||
# The documentation is accessible at http://localhost:3000/swagger?url=/api/v2/management/swagger
|
||||
# Add swagger documentation for Peatio Management API
|
||||
add_swagger_documentation base_path: File.join(API::Mount::PREFIX, API::V2::Mount::API_VERSION, PREFIX, 'peatio'),
|
||||
add_base_path: true,
|
||||
mount_path: '/swagger',
|
||||
api_version: API::V2::Mount::API_VERSION,
|
||||
doc_version: Peatio::Application::VERSION,
|
||||
info: {
|
||||
title: "Peatio Management API #{API::V2::Mount::API_VERSION}",
|
||||
description: 'Management API is server-to-server API with high privileges.',
|
||||
contact_name: 'openware.com',
|
||||
contact_email: 'hello@openware.com',
|
||||
contact_url: 'https://www.openware.com',
|
||||
licence: 'MIT',
|
||||
license_url: 'https://github.com/openware/peatio/blob/master/LICENSE.md'
|
||||
},
|
||||
models: [
|
||||
API::V2::Management::Entities::Balance,
|
||||
API::V2::Management::Entities::Deposit,
|
||||
API::V2::Management::Entities::Withdraw,
|
||||
API::V2::Management::Entities::Operation,
|
||||
API::V2::Management::Entities::Engine
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
202
app/api/v2/management/operations.rb
Normal file
202
app/api/v2/management/operations.rb
Normal file
@@ -0,0 +1,202 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
class Operations < Grape::API
|
||||
|
||||
# POST: api/v2/management/assets
|
||||
# POST: api/v2/management/expenses
|
||||
# POST: api/v2/management/revenues
|
||||
#
|
||||
# POST: api/v2/management/assets/new
|
||||
# POST: api/v2/management/expenses/new
|
||||
# POST: api/v2/management/revenues/new
|
||||
::Operations::Account::PLATFORM_TYPES.each do |op_type|
|
||||
op_type_plural = op_type.to_s.pluralize
|
||||
|
||||
desc "Returns #{op_type_plural} as paginated collection." do
|
||||
@settings[:scope] = :read_operations
|
||||
success API::V2::Management::Entities::Operation
|
||||
end
|
||||
params do
|
||||
optional :currency,
|
||||
type: String,
|
||||
values: -> { Currency.codes(bothcase: true) },
|
||||
desc: 'The currency for operations filtering.'
|
||||
optional :page,
|
||||
type: Integer, default: 1,
|
||||
integer_gt_zero: true,
|
||||
desc: 'The page number (defaults to 1).'
|
||||
optional :limit,
|
||||
type: Integer,
|
||||
default: 100,
|
||||
range: 1..1000,
|
||||
desc: 'The number of objects per page (defaults to 100, maximum is 1000).'
|
||||
optional :time_from,
|
||||
type: Integer,
|
||||
desc: "An integer represents the seconds elapsed since Unix epoch."\
|
||||
"If set, only operations after the time will be returned."
|
||||
optional :time_to,
|
||||
type: Integer,
|
||||
desc: "An integer represents the seconds elapsed since Unix epoch."\
|
||||
"If set, only operations before the time will be returned."
|
||||
optional :reference_type,
|
||||
type: String,
|
||||
desc: "The reference type for operations filtering"
|
||||
end
|
||||
post op_type_plural do
|
||||
currency_id = params.fetch(:currency, nil)
|
||||
|
||||
"operations/#{op_type}"
|
||||
.camelize
|
||||
.constantize
|
||||
.order(id: :desc)
|
||||
.tap { |q| q.where!(currency_id: currency_id) if currency_id }
|
||||
.tap { |q| q.where!(reference_type: params[:reference_type]) if params[:reference_type].present? }
|
||||
.tap { |q| q.where!('created_at >= ?', Time.at(params[:time_from])) if params[:time_from].present? }
|
||||
.tap { |q| q.where!('created_at < ?', Time.at(params[:time_to])) if params[:time_to].present? }
|
||||
.page(params[:page])
|
||||
.per(params[:limit])
|
||||
.tap { |q| present q, with: API::V2::Management::Entities::Operation }
|
||||
status 200
|
||||
end
|
||||
|
||||
desc "Creates new #{op_type} operation." do
|
||||
@settings[:scope] = :write_operations
|
||||
success API::V2::Management::Entities::Operation
|
||||
end
|
||||
params do
|
||||
requires :currency,
|
||||
type: String,
|
||||
values: -> { ::Currency.codes(bothcase: true) },
|
||||
desc: 'The currency code.'
|
||||
requires :code,
|
||||
type: Integer,
|
||||
values: -> { ::Operations::Account.where(type: op_type).pluck(:code) },
|
||||
desc: 'Operation account code'
|
||||
optional :debit,
|
||||
type: BigDecimal,
|
||||
values: ->(v) { v.to_d.positive? },
|
||||
desc: 'Operation debit amount.'
|
||||
optional :credit,
|
||||
type: BigDecimal,
|
||||
values: ->(v) { v.to_d.positive? },
|
||||
desc: 'Operation credit amount.'
|
||||
exactly_one_of :debit, :credit
|
||||
end
|
||||
post "/#{op_type_plural}/new" do
|
||||
attributes = declared(params)
|
||||
|
||||
create_operation!(attributes).tap do |op|
|
||||
present op, with: Entities::Operation
|
||||
end
|
||||
status 200
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
body errors: e.message
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
# POST: api/v2/management/liabilities
|
||||
#
|
||||
# POST: api/v2/management/liabilities/new
|
||||
::Operations::Account::MEMBER_TYPES.each do |op_type|
|
||||
op_type_plural = op_type.to_s.pluralize
|
||||
|
||||
desc "Returns #{op_type_plural} as paginated collection." do
|
||||
@settings[:scope] = :read_operations
|
||||
success API::V2::Management::Entities::Operation
|
||||
end
|
||||
params do
|
||||
optional :currency,
|
||||
type: String,
|
||||
values: -> { Currency.codes(bothcase: true) },
|
||||
desc: 'The currency for operations filtering.'
|
||||
optional :uid,
|
||||
type: String,
|
||||
desc: 'The user ID for operations filtering.'
|
||||
optional :reference_type,
|
||||
type: String,
|
||||
desc: "The reference type for operations filtering"
|
||||
optional :time_from,
|
||||
type: Integer,
|
||||
desc: "An integer represents the seconds elapsed since Unix epoch."\
|
||||
"If set, only operations after the time will be returned."
|
||||
optional :time_to,
|
||||
type: Integer,
|
||||
desc: "An integer represents the seconds elapsed since Unix epoch."\
|
||||
"If set, only operations before the time will be returned."
|
||||
optional :page,
|
||||
type: Integer,
|
||||
default: 1,
|
||||
integer_gt_zero: true,
|
||||
desc: 'The page number (defaults to 1).'
|
||||
optional :limit,
|
||||
type: Integer,
|
||||
default: 100,
|
||||
range: 1..10000,
|
||||
desc: 'The number of objects per page (defaults to 100, maximum is 10000).'
|
||||
end
|
||||
post op_type_plural do
|
||||
currency_id = params.fetch(:currency, nil)
|
||||
member = Member.find_by!(uid: params[:uid]) if params[:uid].present?
|
||||
|
||||
"operations/#{op_type}"
|
||||
.camelize
|
||||
.constantize
|
||||
.order(id: :desc)
|
||||
.tap { |q| q.where!(currency_id: currency_id) if currency_id }
|
||||
.tap { |q| q.where!(member: member) if member }
|
||||
.tap { |q| q.where!(reference_type: params[:reference_type]) if params[:reference_type].present? }
|
||||
.tap { |q| q.where!('created_at >= ?', Time.at(params[:time_from])) if params[:time_from].present? }
|
||||
.tap { |q| q.where!('created_at < ?', Time.at(params[:time_to])) if params[:time_to].present? }
|
||||
.tap { |q| present paginate(q), with: API::V2::Management::Entities::Operation }
|
||||
status 200
|
||||
end
|
||||
|
||||
desc "Creates new #{op_type} operation." do
|
||||
@settings[:scope] = :write_operations
|
||||
success API::V2::Management::Entities::Operation
|
||||
end
|
||||
params do
|
||||
requires :currency,
|
||||
type: String,
|
||||
values: -> { ::Currency.codes(bothcase: true) },
|
||||
desc: 'The currency code.'
|
||||
requires :code,
|
||||
type: Integer,
|
||||
values: -> { ::Operations::Account.where(type: op_type).pluck(:code) },
|
||||
desc: 'Operation account code'
|
||||
given code: ->(code) { ::Operations::Account.find_by(code: code).try(:scope).try(:member?) } do
|
||||
requires :uid,
|
||||
type: String,
|
||||
desc: 'The user ID for operation owner.'
|
||||
end
|
||||
optional :debit,
|
||||
type: BigDecimal,
|
||||
values: ->(v) { v.to_d.positive? },
|
||||
desc: 'Operation debit amount.'
|
||||
optional :credit,
|
||||
type: BigDecimal,
|
||||
values: ->(v) { v.to_d.positive? },
|
||||
desc: 'Operation credit amount.'
|
||||
exactly_one_of :debit, :credit
|
||||
end
|
||||
post "/#{op_type_plural}/new" do
|
||||
attributes = declared(params)
|
||||
|
||||
create_operation!(attributes).tap do |op|
|
||||
present op, with: Entities::Operation
|
||||
end
|
||||
status 200
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
body errors: e.message
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
112
app/api/v2/management/orders.rb
Normal file
112
app/api/v2/management/orders.rb
Normal file
@@ -0,0 +1,112 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
class Orders < Grape::API
|
||||
helpers ::API::V2::OrderHelpers
|
||||
|
||||
desc 'Returns orders' do
|
||||
@settings[:scope] = :read_orders
|
||||
success API::V2::Management::Entities::Order
|
||||
end
|
||||
params do
|
||||
optional :uid,
|
||||
values: { value: ->(v) { Member.exists?(uid: v) }, message: 'management.orders.uid_doesnt_exist' },
|
||||
desc: 'Filter order by owner uid'
|
||||
optional :market,
|
||||
values: { value: -> { ::Market.ids }, message: 'management.orders.market_doesnt_exist' },
|
||||
desc: -> { API::V2::Management::Entities::Market.documentation[:id][:desc] }
|
||||
optional :state,
|
||||
values: { value: -> { ::Order.state.values }, message: 'management.orders.invalid_state' },
|
||||
desc: 'Filter order by state.'
|
||||
optional :ord_type,
|
||||
values: { value: ::Order::TYPES, message: 'management.orders.invalid_ord_type' },
|
||||
desc: 'Filter order by ord_type.'
|
||||
end
|
||||
post '/orders' do
|
||||
if params[:uid].present?
|
||||
member = Member.find_by(uid: params[:uid])
|
||||
params.merge!(member_id: member.id) if member.present?
|
||||
end
|
||||
|
||||
ransack_params = API::V2::Admin::Helpers::RansackBuilder.new(params)
|
||||
.eq(:ord_type, :state, :member_id)
|
||||
.translate(market: :market_id)
|
||||
.build
|
||||
|
||||
search = Order.ransack(ransack_params)
|
||||
|
||||
present search.result, with: API::V2::Management::Entities::Order
|
||||
status 200
|
||||
end
|
||||
|
||||
desc 'Cancel specific order' do
|
||||
@settings[:scope] = :write_orders
|
||||
success API::V2::Management::Entities::Order
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: -> { API::V2::Management::Entities::Order.documentation[:id][:desc] }
|
||||
end
|
||||
|
||||
post '/orders/:id/cancel' do
|
||||
begin
|
||||
order = Order.find(params[:id])
|
||||
order.trigger_cancellation
|
||||
present order, with: API::V2::Management::Entities::Order
|
||||
status 200
|
||||
rescue ActiveRecord::RecordNotFound => e
|
||||
# RecordNotFound in rescued by ExceptionsHandler.
|
||||
raise(e)
|
||||
rescue
|
||||
error!({ errors: ['management.order.cancel_error'] }, 422)
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Cancel all open orders' do
|
||||
@settings[:scope] = :write_orders
|
||||
success API::V2::Management::Entities::Order
|
||||
end
|
||||
params do
|
||||
optional :uid,
|
||||
values: { value: ->(v) { Member.exists?(uid: v) }, message: 'management.orders.uid_doesnt_exist' },
|
||||
desc: 'Filter order by owner uid'
|
||||
requires :market,
|
||||
values: { value: -> { ::Market.active.ids }, message: 'management.order.market_doesnt_exist' },
|
||||
desc: -> { API::V2::Management::Entities::Market.documentation[:id][:desc] }
|
||||
end
|
||||
|
||||
post '/orders/cancel' do
|
||||
if params[:uid].present?
|
||||
member = Member.find_by(uid: params[:uid])
|
||||
params.merge!(member_id: member.id) if member.present?
|
||||
end
|
||||
|
||||
market = ::Market.find(params[:market])
|
||||
market_engine = market.engine
|
||||
|
||||
if market_engine.peatio_engine?
|
||||
ransack_params = API::V2::Admin::Helpers::RansackBuilder.new(params)
|
||||
.eq(:member_id, state: 'wait')
|
||||
.translate(market: :market_id)
|
||||
.build
|
||||
|
||||
orders = Order.ransack(ransack_params).result
|
||||
orders.map(&:trigger_internal_cancellation)
|
||||
else
|
||||
filters = {
|
||||
market_id: market.id,
|
||||
member_uid: params[:uid]
|
||||
}.compact
|
||||
|
||||
Order.trigger_bulk_cancel_third_party(market_engine.driver, filters)
|
||||
end
|
||||
status 204
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
70
app/api/v2/management/payment_address.rb
Normal file
70
app/api/v2/management/payment_address.rb
Normal file
@@ -0,0 +1,70 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
class PaymentAddress < Grape::API
|
||||
desc 'Create payment address' do
|
||||
@settings[:scope] = :write_payment_addresses
|
||||
success API::V2::Management::Entities::PaymentAddress
|
||||
end
|
||||
|
||||
params do
|
||||
requires :uid,
|
||||
type: String,
|
||||
values: { value: ->(v) { Member.exists?(uid: v) }, message: 'management.payment_address.uid_doesnt_exist' },
|
||||
desc: API::V2::Management::Entities::PaymentAddress.documentation[:uid][:desc]
|
||||
requires :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.codes(bothcase: true) }, message: 'management.payment_address.currency_doesnt_exist' },
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:code][:desc] }
|
||||
optional :remote,
|
||||
type: { value: Boolean, message: 'management.payment_address.non_boolean_remote' },
|
||||
desc: API::V2::Management::Entities::PaymentAddress.documentation[:remote][:desc]
|
||||
end
|
||||
|
||||
post '/deposit_address/new' do
|
||||
member = Member.find_by(uid: params[:uid]) if params[:uid].present?
|
||||
|
||||
currency = Currency.find(params[:currency])
|
||||
unless currency.deposit_enabled?
|
||||
error!({ errors: ['account.currency.deposit_disabled'] }, 422)
|
||||
end
|
||||
|
||||
wallet = Wallet.deposit_wallet(currency.id)
|
||||
unless wallet.present?
|
||||
error!({ errors: ['account.wallet.not_found'] }, 422)
|
||||
end
|
||||
|
||||
unless params[:remote].nil?
|
||||
pa = member.payment_address!(wallet.id, params[:remote])
|
||||
else
|
||||
pa = member.payment_address!(wallet.id)
|
||||
end
|
||||
|
||||
wallet_service = WalletService.new(wallet)
|
||||
|
||||
begin
|
||||
pa.with_lock do
|
||||
next if pa.address.present?
|
||||
|
||||
# Supply address ID in case of BitGo address generation if it exists.
|
||||
result = wallet_service.create_address!(member.uid, pa.details.merge(updated_at: pa.updated_at))
|
||||
if result.present?
|
||||
pa.update!(address: result[:address],
|
||||
secret: result[:secret],
|
||||
details: result.fetch(:details, {}).merge(pa.details))
|
||||
end
|
||||
end
|
||||
|
||||
present pa, with: API::V2::Management::Entities::PaymentAddress
|
||||
status 200
|
||||
rescue StandardError => e
|
||||
::Rails.logger.error { "Error: #{e} while generating payment address for #{params[:currency]} for user: #{params[:uid]}" }
|
||||
error!({ errors: ['management.payment_address.failed_to_generate'] }, 422)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
18
app/api/v2/management/tools.rb
Normal file
18
app/api/v2/management/tools.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
class Tools < Grape::API
|
||||
desc 'Returns server time in seconds since Unix epoch.' do
|
||||
@settings[:scope] = :tools
|
||||
end
|
||||
post '/timestamp' do
|
||||
body timestamp: Time.now.iso8601
|
||||
status 200
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
38
app/api/v2/management/trades.rb
Normal file
38
app/api/v2/management/trades.rb
Normal file
@@ -0,0 +1,38 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
class Trades < Grape::API
|
||||
|
||||
desc 'Returns trades as paginated collection.' do
|
||||
@settings[:scope] = :read_trades
|
||||
success API::V2::Management::Entities::Trade
|
||||
end
|
||||
params do
|
||||
optional :uid, type: String, desc: 'The shared user ID.'
|
||||
optional :market, type: String,
|
||||
values: { value: -> { ::Market.active.ids },
|
||||
message: 'Market does not exist' }
|
||||
optional :page, type: Integer, default: 1, integer_gt_zero: true, desc: 'The page number (defaults to 1).'
|
||||
optional :limit, type: Integer, default: 100, range: 1..1000, desc: 'The number of objects per page (defaults to 100, maximum is 1000).'
|
||||
end
|
||||
post '/trades' do
|
||||
market = ::Market.find(params[:market]) if params[:market].present?
|
||||
member = Member.find_by!(uid: params[:uid]) if params[:uid].present?
|
||||
|
||||
Trade
|
||||
.order(id: :desc)
|
||||
.includes(:maker, :taker)
|
||||
.tap { |q| q.where!(market: market) if market }
|
||||
.tap { |q| q.where!("maker_id = #{member.id} OR taker_id = #{member.id}") if member }
|
||||
.page(params[:page])
|
||||
.per(params[:limit])
|
||||
.tap { |q| present q, with: API::V2::Management::Entities::Trade }
|
||||
status 200
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
34
app/api/v2/management/trading_fees.rb
Normal file
34
app/api/v2/management/trading_fees.rb
Normal file
@@ -0,0 +1,34 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
class TradingFees < Grape::API
|
||||
desc 'Returns trading_fees table as paginated collection' do
|
||||
@settings[:scope] = :read_trading_fees
|
||||
end
|
||||
params do
|
||||
optional :group,
|
||||
type: String,
|
||||
desc: 'Member group'
|
||||
optional :market_id,
|
||||
type: String,
|
||||
desc: 'Market id',
|
||||
values: { value: -> { ::Market.ids.append(::TradingFee::ANY) },
|
||||
message: 'Market does not exist' }
|
||||
optional :page, type: Integer, default: 1, integer_gt_zero: true, desc: 'The page number (defaults to 1).'
|
||||
optional :limit, type: Integer, default: 100, range: 1..1000, desc: 'The number of objects per page (defaults to 100, maximum is 1000).'
|
||||
end
|
||||
post '/fee_schedule/trading_fees' do
|
||||
TradingFee
|
||||
.order(id: :desc)
|
||||
.tap { |t| t.where!(market_id: params[:market_id]) if params[:market_id] }
|
||||
.tap { |t| t.where!(group: params[:group]) if params[:group] }
|
||||
.tap { |q| present paginate(q), with: API::V2::Entities::TradingFee }
|
||||
status 200
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
98
app/api/v2/management/transfers.rb
Normal file
98
app/api/v2/management/transfers.rb
Normal file
@@ -0,0 +1,98 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
class Transfers < Grape::API
|
||||
# TODO: Add endpoints for listing Transfer/Transfers.
|
||||
|
||||
desc 'Creates new transfer.' do
|
||||
@settings[:scope] = :write_transfers
|
||||
end
|
||||
params do
|
||||
requires :key,
|
||||
type: String,
|
||||
desc: 'Unique Transfer Key.'
|
||||
requires :category,
|
||||
type: String,
|
||||
desc: 'Transfer Category.'
|
||||
optional :description,
|
||||
type: String,
|
||||
desc: 'Transfer Description.'
|
||||
|
||||
requires(:operations, type: Array, allow_blank: false) do
|
||||
requires :currency,
|
||||
type: String,
|
||||
values: -> { Currency.codes(bothcase: true) },
|
||||
desc: 'Operation currency.'
|
||||
requires :amount,
|
||||
type: BigDecimal,
|
||||
values: ->(v) { v.to_d.positive? },
|
||||
desc: 'Operation amount.'
|
||||
|
||||
requires :account_src, type: Hash do
|
||||
requires :code,
|
||||
type: Integer,
|
||||
values: -> { ::Operations::Account.pluck(:code) },
|
||||
desc: 'Source Account code.'
|
||||
given code: ->(code) { ::Operations::Account.find_by(code: code).try(:scope).try(:member?) } do
|
||||
requires :uid,
|
||||
type: String,
|
||||
desc: 'Source Account User ID (for accounts with member scope).'
|
||||
end
|
||||
end
|
||||
|
||||
requires :account_dst, type: Hash do
|
||||
requires :code,
|
||||
type: Integer,
|
||||
values: -> { ::Operations::Account.pluck(:code) },
|
||||
desc: 'Destination Account code.'
|
||||
given code: ->(code) { ::Operations::Account.find_by(code: code).try(:scope).try(:member?) } do
|
||||
requires :uid,
|
||||
type: String,
|
||||
desc: 'Destination Account User ID (for accounts with member scope).'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
post '/transfers/new' do
|
||||
declared_params = declared(params)
|
||||
|
||||
attrs = declared_params.slice(:key, :category, :description)
|
||||
|
||||
declared_params[:operations].each do |op_pair|
|
||||
currency = Currency.find(op_pair[:currency])
|
||||
|
||||
debit_op = op_pair[:account_src].merge(debit: op_pair[:amount], credit: 0.0, currency: currency)
|
||||
credit_op = op_pair[:account_dst].merge(credit: op_pair[:amount], debit: 0.0, currency: currency)
|
||||
|
||||
[debit_op, credit_op].each do |op|
|
||||
klass = ::Operations.klass_for(code: op['code'])
|
||||
|
||||
uid = op.delete(:uid)
|
||||
op.merge!(member: Member.find_by!(uid: uid)) if uid.present?
|
||||
|
||||
type = ::Operations::Account.find_by(code: op[:code]).type
|
||||
type_plural = type.pluralize
|
||||
if attrs[type_plural].present?
|
||||
attrs[type_plural].push(klass.new(op))
|
||||
else
|
||||
attrs[type_plural] = [klass.new(op)]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
present Transfer.create!(attrs), with: Entities::Transfer
|
||||
status 201
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
body errors: e.message
|
||||
status 422
|
||||
rescue ::Account::AccountError => e
|
||||
body errors: "Account balance is insufficient (#{e.message})"
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
155
app/api/v2/management/withdraws.rb
Normal file
155
app/api/v2/management/withdraws.rb
Normal file
@@ -0,0 +1,155 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
class Withdraws < Grape::API
|
||||
|
||||
helpers do
|
||||
def perform_action(withdraw, action)
|
||||
withdraw.with_lock do
|
||||
case action
|
||||
when 'process'
|
||||
withdraw.accept!
|
||||
# Process fiat withdraw immediately. Crypto withdraws will be processed by workers.
|
||||
if withdraw.currency.fiat?
|
||||
withdraw.process!
|
||||
withdraw.dispatch!
|
||||
withdraw.success!
|
||||
end
|
||||
when 'cancel'
|
||||
withdraw.cancel!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Returns withdraws as paginated collection.' do
|
||||
@settings[:scope] = :read_withdraws
|
||||
success API::V2::Management::Entities::Withdraw
|
||||
end
|
||||
params do
|
||||
optional :uid, type: String, desc: 'The shared user ID.'
|
||||
optional :currency, type: String, values: -> { Currency.codes(bothcase: true) }, desc: 'The currency code.'
|
||||
optional :page, type: Integer, default: 1, integer_gt_zero: true, desc: 'The page number (defaults to 1).'
|
||||
optional :limit, type: Integer, default: 100, range: 1..1000, desc: 'The number of objects per page (defaults to 100, maximum is 1000).'
|
||||
optional :state, type: String, values: -> { Withdraw::STATES.map(&:to_s) }, desc: 'The state to filter by.'
|
||||
end
|
||||
post '/withdraws' do
|
||||
currency = Currency.find(params[:currency]) if params[:currency].present?
|
||||
member = Member.find_by!(uid: params[:uid]) if params[:uid].present?
|
||||
|
||||
Withdraw
|
||||
.order(id: :desc)
|
||||
.includes(:member, :currency)
|
||||
.tap { |q| q.where!(currency: currency) if currency }
|
||||
.tap { |q| q.where!(member: member) if member }
|
||||
.tap { |q| q.where!(aasm_state: params[:state]) if params[:state] }
|
||||
.page(params[:page])
|
||||
.per(params[:limit])
|
||||
.tap { |q| present q, with: API::V2::Management::Entities::Withdraw }
|
||||
status 200
|
||||
end
|
||||
|
||||
desc 'Returns withdraw by ID.' do
|
||||
@settings[:scope] = :read_withdraws
|
||||
success API::V2::Management::Entities::Withdraw
|
||||
end
|
||||
params do
|
||||
requires :tid, type: String, desc: 'The shared transaction ID.'
|
||||
end
|
||||
post '/withdraws/get' do
|
||||
present Withdraw.find_by!(params.slice(:tid)), with: API::V2::Management::Entities::Withdraw
|
||||
end
|
||||
|
||||
desc 'Creates new withdraw.' do
|
||||
@settings[:scope] = :write_withdraws
|
||||
detail 'Creates new withdraw. The behaviours for fiat and crypto withdraws are different. ' \
|
||||
'Fiat: money are immediately locked, withdraw state is set to «submitted», system workers ' \
|
||||
'will validate withdraw later against suspected activity, and assign state to «rejected» or «accepted». ' \
|
||||
'The processing will not begin automatically. The processing may be initiated manually from admin panel or by PUT /management_api/v1/withdraws/action. ' \
|
||||
'Coin: money are immediately locked, withdraw state is set to «submitted», system workers ' \
|
||||
'will validate withdraw later against suspected activity, validate withdraw address and ' \
|
||||
'set state to «rejected» or «accepted». ' \
|
||||
'Then in case state is «accepted» withdraw workers will perform interactions with blockchain. ' \
|
||||
'The withdraw receives new state «processing». Then withdraw receives state either «confirming» or «failed».' \
|
||||
'Then in case state is «confirming» withdraw confirmations workers will perform interactions with blockchain.' \
|
||||
'Withdraw receives state «succeed» when it receives minimum necessary amount of confirmations.'
|
||||
success API::V2::Management::Entities::Withdraw
|
||||
end
|
||||
params do
|
||||
requires :uid, type: String, desc: 'The shared user ID.'
|
||||
optional :tid, type: String, desc: 'The shared transaction ID. Must not exceed 64 characters. Peatio will generate one automatically unless supplied.'
|
||||
optional :rid, type: String, desc: 'The beneficiary ID or wallet address on the Blockchain.'
|
||||
optional :beneficiary_id, type: String, desc: 'ID of Active Beneficiary belonging to user.'
|
||||
requires :currency, type: String, values: -> { Currency.codes(bothcase: true) }, desc: 'The currency code.'
|
||||
requires :amount, type: BigDecimal, desc: 'The amount to withdraw.'
|
||||
optional :note, type: String, desc: 'The note for withdraw.'
|
||||
optional :action, type: String, values: %w[process], desc: 'The action to perform.'
|
||||
optional :transfer_type, type: String,
|
||||
values: { value: -> { Withdraw::TRANSFER_TYPES.keys }, message: 'account.withdraw.transfer_type_not_in_list' },
|
||||
desc: -> { API::V2::Admin::Entities::Withdraw.documentation[:transfer_type][:desc] }
|
||||
|
||||
exactly_one_of :rid, :beneficiary_id
|
||||
end
|
||||
post '/withdraws/new' do
|
||||
member = Member.find_by(uid: params[:uid])
|
||||
|
||||
currency = Currency.find(params[:currency])
|
||||
unless currency.withdrawal_enabled?
|
||||
error!({ errors: ['management.currency.withdrawal_disabled'] }, 422)
|
||||
end
|
||||
|
||||
beneficiary = Beneficiary.find_by(id: params[:beneficiary_id]) if params[:beneficiary_id].present?
|
||||
if params[:rid].blank? && beneficiary.blank?
|
||||
error!({ errors: ['management.beneficiary.doesnt_exist'] }, 422)
|
||||
elsif params[:rid].blank? && !beneficiary&.active?
|
||||
error!({ errors: ['management.beneficiary.invalid_state_for_withdrawal'] }, 422)
|
||||
end
|
||||
|
||||
if params[:tid].present?
|
||||
error!({ errors: ['TID already exist'] }, 422) if Withdraw.where(tid: params[:tid]).present?
|
||||
end
|
||||
|
||||
declared_params = declared(params, include_missing: false).slice(:tid, :rid, :note, :transfer_type).merge(
|
||||
sum: params[:amount],
|
||||
member: member,
|
||||
currency: currency,
|
||||
tid: params[:tid]
|
||||
)
|
||||
|
||||
declared_params.merge!(beneficiary: beneficiary) if params[:beneficiary_id].present?
|
||||
withdraw = "withdraws/#{currency.type}".camelize.constantize.new(declared_params)
|
||||
|
||||
withdraw.save!
|
||||
withdraw.with_lock { withdraw.accept! }
|
||||
perform_action(withdraw, params[:action]) if params[:action]
|
||||
present withdraw, with: API::V2::Management::Entities::Withdraw
|
||||
rescue ::Account::AccountError => e
|
||||
report_api_error(e, request)
|
||||
error!({ errors: [e.to_s] }, 422)
|
||||
rescue => e
|
||||
report_exception(e)
|
||||
error!({ errors: ['Failed to create withdraw!'] }, 422)
|
||||
end
|
||||
|
||||
desc 'Performs action on withdraw.' do
|
||||
@settings[:scope] = :write_withdraws
|
||||
detail '«process» – system will lock the money, check for suspected activity, validate recipient address, and initiate the processing of the withdraw. ' \
|
||||
'«cancel» – system will mark withdraw as «canceled», and unlock the money.'
|
||||
success API::V2::Management::Entities::Withdraw
|
||||
end
|
||||
params do
|
||||
requires :tid, type: String, desc: 'The shared transaction ID.'
|
||||
requires :action, type: String, values: %w[process cancel], desc: 'The action to perform.'
|
||||
end
|
||||
put '/withdraws/action' do
|
||||
record = Withdraw.find_by!(params.slice(:tid))
|
||||
perform_action(record, params[:action])
|
||||
present record, with: API::V2::Management::Entities::Withdraw
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user