plugins/payment/classes/yf_payment_api__provider_remote.class.php

Summary

Maintainability
F
1 wk
Test Coverage
<?php

if ( ! function_exists('array_replace_recursive')) {
    trigger_error('Not exists function "array_replace_recursive ( PHP 5 >= 5.3.0 )"', E_USER_ERROR);
}

class yf_payment_api__provider_remote
{
    public $ENABLE = null;
    public $TEST_MODE = null;

    public $URL = null;
    public $KEY_PUBLIC = null;
    public $KEY_PRIVATE = null;

    public $URL_API = null;
    public $URL_API_TEST = null;
    public $API_KEY_PUBLIC = null;
    public $API_KEY_PRIVATE = null;

    public $method_allow = null;

    public $API_SSL_VERIFY = true;

    public $IS_DEPOSITION = null;
    public $IS_PAYMENT = null;

    public $IS_PAYIN_MANUAL = null;
    public $IS_PAYOUT_MANUAL = null;

    public $service_allow = null;
    public $description = null;

    public $_status = [];
    public $_status_message = [
        'success' => 'Выполнено',
        'in_progress' => 'Ожидание',
        'processing' => 'Обработка',
        'refused' => 'Отклонено',
    ];

    public $payment_api = null;
    public $api = null;

    public $config = null;

    public function _init()
    {
        if ( ! $this->ENABLE) {
            return  null;
        }
        $this->payment_api = _class('payment_api');
        ! empty($this->service_allow) && $this->description = implode(', ', $this->service_allow);
        $this->config();
    }

    public function config($options = null)
    {
        $config = &$this->CONFIG;
        if ( ! is_array($config)) {
            return  null;
        }
        // var
        $payment_api = $this->payment_api;
        // method_allow
        foreach ($config as $key => $item) {
            if (is_array($this->$key)) {
                $this->$key = $payment_api->_replace($this->$key, $item);
            } else {
                $this->$key = $item;
            }
        }
    }

    public function allow($value = null)
    {
        $result = &$this->ENABLE;
        if (isset($value)) {
            $value = (bool) $value;
            // init if enable
            if ( ! $result && $value) {
                $this->_init();
            }
            $result = $value;
        }
        return  $result;
    }

    public function _state($value, $status = null, $status_message = null)
    {
        if ( ! $this->ENABLE) {
            return  null;
        }
        if ( ! is_array($status)) {
            $status = &$this->_status;
        }
        if ( ! is_array($status_message)) {
            $status_message = &$this->_status_message;
        }
        $name = @$status[$value] ?: null;
        $message = @$status_message[$name] ?: @$status_message[$value] ?: null;
        return  [$name, $message];
    }

    public function _ip($options = null)
    {
        $result = _class('api')->_ip($options);
        return  $result;
    }

    public function _check_ip($options = null)
    {
        if ( ! $this->ENABLE) {
            return  null;
        }
        // import options
        is_array($options) && extract($options, EXTR_PREFIX_ALL | EXTR_REFS, '');
        // allow ip
        $ip_filter = @$_ip_filter ? $_ip_filter : $this->ip_filter;
        $ip = isset($_ip) ? $_ip : $this->_ip();
        $result = _class('api')->_check_ip([
            'ip' => $ip,
            'ip_filter' => $ip_filter,
        ]);
        return  $result;
    }

    public function is_test($options = null)
    {
        $result = false;
        // import options
        is_array($options) && extract($options, EXTR_PREFIX_ALL | EXTR_REFS, '');
        if ( ! empty($this->TEST_MODE) || ! empty($_test_mode)) {
            $result = true;
        }
        return  $result;
    }

    public function option_transform($options = null)
    {
        // import options
        is_array($options) && extract($options, EXTR_PREFIX_ALL | EXTR_REFS, '');
        foreach ((array) $_transform as $from => $to) {
            if (isset($_option[$from]) && $from != $to) {
                $_option[$to] = $_option[$from];
                unset($_option[$from]);
            }
        }
    }

    public function api_url($options = null, $request_options = null)
    {
        // import options
        is_array($options) && extract($options, EXTR_PREFIX_ALL | EXTR_REFS, '');
        if ( ! empty($_url)) {
            $result = $_url;
        } elseif ($this->is_test($options) && ! empty($this->URL_API_TEST)) {
            $result = $this->URL_API_TEST;
        } else {
            $result = $this->URL_API;
        }
        if (is_array($_uri)) {
            // prepare uri
            $uri = [];
            foreach ($_uri as $id => $value) {
                if (is_string($value) && strpos($value, '$') !== false) {
                    $v = str_replace('$', '', $value);
                    $v = @$request_options[$v];
                    if ($v === null) {
                        $result = [
                            'status' => false,
                            'status_message' => 'error api url: required option ' . $value,
                        ];
                        return  $result;
                    }
                    $value = $v;
                }
                $uri[$id] = $value;
            }
            $result = str_replace(array_keys($uri), array_values($uri), $result);
        }
        return  $result;
    }

    public function _update_status($options = null)
    {
        if ( ! $this->ENABLE) {
            return  null;
        }
        // import options
        is_array($options) && extract($options, EXTR_PREFIX_ALL | EXTR_REFS, '');
        // check
        if (empty($_name)) {
            $result = [
                'status' => false,
                'status_message' => 'Статус операции не определен',
            ];
            return  $result;
        }
        if (empty($_operation_id)) {
            $result = [
                'status' => false,
                'status_message' => 'Не определен код операции',
            ];
            return  $result;
        }
        // var
        $payment_api = $this->payment_api;
        // operation
        $operation = $payment_api->operation(['operation_id' => $_operation_id]);
        if (empty($operation)) {
            $result = [
                'status' => false,
                'status_message' => 'Операция отсутствует: ' . $_operation_id,
            ];
            return  $result;
        }
        // update status only in_progress
        $object = $payment_api->get_status(['status_id' => $operation['status_id']]);
        list($status_id, $status) = $object;
        if (empty($status_id)) {
            return  $object;
        }
        if ($status['name'] != 'in_progress' && $status['name'] != 'processing') {
            $result = [
                'status' => false,
                'status_message' => 'Операция уже обработана: ' . $_operation_id,
            ];
            return  $result;
        }
        // progress
        $object = $payment_api->get_status(['name' => $_name]);
        list($status_id, $status) = $object;
        if (empty($status_id)) {
            return  $object;
        }
        // prepare
        $sql_datetime = $payment_api->sql_datetime();
        $data = [
            'operation_id' => $_operation_id,
            'status_id' => $status_id,
            'datetime_update' => $sql_datetime,
        ];
        if ($status['name'] == 'processing') {
            // update processing
            $data_options = [
                'processing' => [[
                    'provider_name' => 'administration',
                    'datetime' => $sql_datetime,
                ]],
            ];
        }
        ! empty($_is_finish) && $data['datetime_finish'] = $sql_datetime;
        if (@$data_options) {
            $data['options'] = $payment_api->_merge($data_options, $_options);
        }
        $result = $payment_api->operation_update($data);
        return  $result;
    }

    public function validate($options)
    {
        // import options
        is_array($options) && extract($options, EXTR_PREFIX_ALL | EXTR_REFS, '');
        // check payment method, type
        if (empty($_method_id) || empty($_type_name)) {
            return  $this->result_success();
        }
        // payway
        $payway = null;
        switch ($_type_name) {
            case 'deposition':
                $payway = 'payin';
                $get_currency = 'get_currency';
                break;
            case 'payment':
                $payway = 'payout';
                $get_currency = 'get_currency_payout';
                break;
        }
        // method
        $method = $this->api_method([
            'type' => @$payway,
            'method_id' => @$_method_id,
        ]);
        if (empty($method)) {
            return  $this->result_success();
        }
        // currency_id
        $currency_id = $this->$get_currency($options);
        if (empty($currency_id)) {
            $result = [
                'status' => false,
                'status_message' => 'Неизвестная валюта',
            ];
            return  $result;
        }
        // validation amount
        // amount min/max
        $result = $this->amount_limit([
            'amount' => $_amount,
            'currency_id' => $currency_id,
            'method' => $method,
        ]);
        if ( ! @$result['status']) {
            return  $result;
        }
        // validation options
        if (empty($method['option_validation'])) {
            return  $this->result_success();
        }
        $validation = $method['option_validation'];
        $validation_message = @$method['option_validation_message'];
        $validation_error = [];
        $validate = _class('validate');
        // validation processor
        foreach ($method['option'] as $key => $item) {
            // skip: empty validator
            if (empty($validation[$key])) {
                continue;
            }
            // processor
            $value = trim(${ '_' . $key });
            $rules = $validation[$key];
            $result = $validate->_input_is_valid($value, $rules);
            if (empty($result)) {
                $message = @$validation_message[$key] ?: 'Неверное поле';
                $validation_error[$key] = t($message);
            }
        }
        if (empty($validation_error)) {
            return  $this->result_success();
        }
        return  $this->result_fail(t('Неверно заполненные поля для вывода средств, проверьте и повторите запрос.'), $validation_error);
    }

    public function result_success()
    {
        return  ['status' => true];
    }

    public function result_fail($message, $options = null)
    {
        return  ['status' => false, 'status_message' => $message, 'options' => $options];
    }

    public function api_authorization($method = null)
    {
        $result = null;
        if ( ! @$method['is_authorization']) {
            return  $result;
        }
        $result = [
            'user' => $this->API_KEY_PUBLIC,
            'password' => $this->API_KEY_PRIVATE,
        ];
        return  $result;
    }

    public function api_method($options = null)
    {
        // import options
        is_array($options) && extract($options, EXTR_PREFIX_ALL | EXTR_REFS, '');
        $result = null;
        if ( ! is_array(@$this->method_allow[$_type][$_method_id])) {
            return  $result;
        }
        $result = $this->method_allow[$_type][$_method_id];
        return  $result;
    }

    public function _api_request($url, $data, $options = null)
    {
        if ( ! $this->ENABLE) {
            return  null;
        }
        $result = $this->_api_post($url, $data, $options);
        return  $result;
    }

    public function _api_deposition($options)
    {
        if ( ! $this->ENABLE) {
            return  null;
        }
        $result = $this->_api_transaction($options);
        return  $result;
    }

    public function _api_payment($options)
    {
        if ( ! $this->ENABLE) {
            return  null;
        }
        $result = $this->_api_transaction($options);
        return  $result;
    }

    public function _api_transaction($options)
    {
        if ( ! $this->ENABLE) {
            return  null;
        }
        // import options
        is_array($options) && extract($options, EXTR_PREFIX_ALL | EXTR_REFS, '');
        // vars
        $payment_api = $this->payment_api;
        // DUMP
        $payment_api->dump(['var' => [
            'transaction' => $options,
        ]]);
        // response operation id
        $operation_id = (int) ($_operation_id ?: $_response['operation_id']);
        if (empty($operation_id)) {
            $result = [
                'status' => false,
                'status_message' => 'Не определен код операции',
            ];
            return  $result;
        }
        $sql_datetime = $payment_api->sql_datetime();
        // start transaction with row lock by operation_id
        $result = $payment_api->transaction_start(['operation_id' => $operation_id]);
        if ( ! $result) {
            $message = 'Ошибка установки уровня изоляции транзакции';
            $result = [
                'status' => false,
                'status_message' => $message,
            ];
            // DUMP
            $payment_api->dump(['var' => $result]);
            // mail admin
            $tpl = $mail_tpl . '_error';
            $payment_api->mail([
                'subject' => 'Ошибка платежа (id: ' . $operation_id . ')',
                'tpl' => $tpl,
                'user_id' => $account['user_id'],
                'is_admin' => true,
                'data' => [
                    'operation_id' => $operation_id,
                    'message' => $message,
                ],
            ]);
            return  $result;
        }
        // exists operation
        $operation = $payment_api->operation([
            'operation_id' => $operation_id,
        ]);
        if (empty($operation)) {
            $result = [
                'status' => false,
                'status_message' => 'Операция отсутствует: ' . $operation_id,
            ];
            return  $result;
        }
        $operation_options = $operation['options'];
        /*
                // operation request options
                if( !is_array( $operation_options[ 'request' ] ) ) {
                    $result = array(
                        'status'         => false,
                        'status_message' => 'Отсутствуют опции операции',
                    );
                    return( $result );
                }
                // request data
                $request = reset( $operation_options[ 'request' ] );
                $request_data = $request[ 'data' ];
        */
        // operation options
        $_operation_id = (int) $operation['operation_id'];
        $account_id = (int) $operation['account_id'];
        $provider_id = (int) $operation['provider_id'];
        $amount = $payment_api->_number_float($operation['amount']);
        // check request/response operation_id
        if ($operation_id != $_operation_id) {
            $result = [
                'status' => false,
                'status_message' => 'Неверный код операции',
            ];
            return  $result;
        }
        // get current status
        $new_status_name = $_status_name;
        $object = $payment_api->get_status(['name' => $new_status_name]);
        list($new_status_id, $new_status) = $object;
        if (empty($new_status_id)) {
            return  $object;
        }
        // check request provider
        $object = $payment_api->provider([
            'is_service' => true,
            'provider_id' => $provider_id,
        ]);
        if (empty($object)) {
            $result = [
                'status' => false,
                'status_message' => 'Неверный провайдер',
            ];
            return  $result;
        }
        $provider = reset($object);
        $provider_name = $provider['name'];
        // check request/response provider
        if ( ! @$_provider_force && $provider_name != $_provider_name) {
            $result = [
                'status' => false,
                'status_message' => "Провайдер не совпадает ($_provider_name)",
            ];
            return  $result;
        }
        // check account
        $object = $payment_api->get_account__by_id(['account_id' => $account_id]);
        if (empty($object)) {
            $result = [
                'status' => false,
                'status_message' => 'Неверный счет',
            ];
            return  $result;
        }
        list($account_id, $account) = $object;
        // sql options
        $sql_amount = $payment_api->_number_mysql($amount);
        $balance = null;
        // get current status_name
        $current_status_id = (int) $operation['status_id'];
        $object = $payment_api->get_status(['status_id' => $current_status_id]);
        list($current_status_id, $current_status) = $object;
        if (empty($current_status_id)) {
            return  $object;
        }
        $current_status_name = $current_status['name'];
        // get payment type
        $current_type_id = (int) $operation['type_id'];
        $object = $payment_api->get_type(['type_id' => $current_type_id]);
        list($current_type_id, $current_type) = $object;
        if (empty($current_type_id)) {
            return  $object;
        }
        $current_type_name = $current_type['name'];
        // start update
        $is_try =
            ($current_type_name == 'payment' && $current_status_name == 'in_progress')
            ||
            ($current_type_name == 'payment' && $current_status_name == 'processing')
            ||
            ($current_type_name == 'deposition' && $current_status_name != 'success');
        // DUMP
        $payment_api->dump(['var' => [
            'is_try' => $is_try,
            'current_type_name' => $current_type_name,
            'current_status_name' => $current_status_name,
            'datetime' => $sql_datetime,
        ]]);
        // prepare
        $is_manual = null;
        $is_payin = null;
        $is_payout = null;
        $event_name = null;
        $is_update_balance = null;
        $is_update_status = null;
        switch ($current_type_name) {
            case 'payment':
                $is_payout = true;
                $is_manual = $this->IS_PAYOUT_MANUAL;
                // revert amount
                if ( ! $is_manual) {
                    $is_update_status = true;
                    if ($new_status_name == 'refused') {
                        $is_update_balance = true;
                        $sql_sign = '+';
                    }
                }
                $event_name = 'payout';
                $mail_tpl = 'payout';
                break;
            case 'deposition':
                $is_payin = true;
                $is_manual = $this->IS_PAYIN_MANUAL;
                // add amount
                if ( ! $is_manual) {
                    $is_update_status = true;
                    if ($new_status_name == 'success') {
                        $is_update_balance = true;
                        $sql_sign = '+';
                    }
                }
                $event_name = 'payin';
                $mail_tpl = 'payin';
                break;
        }
        if ($is_try) {
            // db()->begin();
            $direction = $operation['direction'];
            // update account balance
            if ($current_status_id != $new_status_id) {
                if ($is_update_balance) {
                    // DUMP
                    $payment_api->dump(['var' => [
                        'is_update_balance' => $is_update_balance,
                    ]]);
                    // update account
                    $_data = [
                        'account_id' => $account_id,
                        'datetime_update' => db()->escape_val($sql_datetime),
                        'balance' => "( balance $sql_sign $sql_amount )",
                    ];
                    $_result = $payment_api->balance_update($_data, ['is_escape' => false]);
                    if ( ! $_result['status']) {
                        $payment_api->transaction_rollback();
                        $message = 'Ошибка при обновлении счета';
                        $result = [
                            'status' => false,
                            'status_message' => $message,
                        ];
                        // DUMP
                        $payment_api->dump(['var' => $result]);
                        // mail admin
                        $tpl = $mail_tpl . '_error';
                        $payment_api->mail([
                            'subject' => 'Ошибка платежа (id: ' . $operation_id . ')',
                            'tpl' => $tpl,
                            'user_id' => $account['user_id'],
                            'is_admin' => true,
                            'data' => [
                                'operation_id' => $operation_id,
                                'message' => $message,
                            ],
                        ]);
                        return  $result;
                    }
                }
            }
            if ($is_update_status) {
                // DUMP
                $payment_api->dump(['var' => [
                    'is_update_status' => $is_update_status,
                ]]);
                // get balance
                $object = $payment_api->get_account__by_id(['account_id' => $account_id, 'force' => true]);
                list($account_id, $account) = $object;
                $balance = $account['balance'];
                // save response
                if (empty($_response['message'])) {
                    switch ($new_status_name) {
                        case 'success':
                            $message = 'Выполнено';
                            break;
                        case 'in_progress':
                            $message = 'В процессе';
                            break;
                        case 'processing':
                            $message = 'Обработка';
                            break;
                        case 'refused':
                        default:
                            $message = 'Отклонено';
                            break;
                    }
                    $_response['message'] = $message;
                }
                // update operation
                $data = [
                    'response' => [[
                        'datetime' => $sql_datetime,
                        'provider_name' => $provider_name,
                        'state' => $_state,
                        'status' => $new_status_name,
                        'status_message' => @$_status_message,
                        'data' => $_response,
                    ]],
                ];
                $data = [
                    'operation_id' => $operation_id,
                    'status_id' => $new_status_id,
                    'datetime_update' => $sql_datetime,
                    'options' => $data,
                ];
                $balance && ($data += [
                    'balance' => $balance,
                    'datetime_finish' => $sql_datetime,
                ]);
                $result = $payment_api->operation_update($data);
                if ( ! $result['status']) {
                    $payment_api->transaction_rollback();
                    // mail admin
                    $tpl = $mail_tpl . '_error';
                    $message = 'Ошибка при обновлении операции';
                    $payment_api->mail([
                        'subject' => 'Ошибка платежа (id: ' . $operation_id . ')',
                        'tpl' => $tpl,
                        'user_id' => $account['user_id'],
                        'is_admin' => true,
                        'data' => [
                            'operation_id' => $operation_id,
                            'message' => $message,
                        ],
                    ]);
                    return  $result;
                } elseif ($is_update_balance) {
                    // mail
                    $tpl = $mail_tpl . '_' . $new_status_name;
                    $payment_api->mail([
                        'tpl' => $tpl,
                        'user_id' => $account['user_id'],
                        'admin' => true,
                        'data' => [
                            'operation_id' => $operation_id,
                            'amount' => $amount,
                        ],
                    ]);
                }
            }
            @$_status_message && $status_message = $_status_message;
            // event
            // get updated account
            $object = $payment_api->get_account__by_id(['account_id' => $account_id]);
            list($account_id, $account) = $object;
            // get updated operation
            $operation = $payment_api->operation([
                    'operation_id' => $operation_id,
                ]);
            events()->fire($event_name . '.' . $new_status_name, [$account, $operation]);
            events()->fire($event_name . '.finish', [$account, $operation]);
            events()->fire('payment.balance_refresh', [$account]);
        } else {
            $message = 'Повторный запрос на выполнение операции';
            $status_message = $message;
            // mail admin
            $tpl = $mail_tpl . '_error';
            $payment_api->mail([
                'subject' => 'Ошибка платежа (id: ' . $operation_id . ')',
                'tpl' => $tpl,
                'user_id' => $account['user_id'],
                'is_admin' => true,
                'data' => [
                    'operation_id' => $operation_id,
                    'message' => $message,
                ],
            ]);
        }
        if ( ! @$status_message) {
            $status_message = $operation['title'] . ', сумма: ' . $amount;
            if ( ! empty($payment_api->currency['short'])) {
                $status_message .= ' ' . $payment_api->currency['short'];
            }
        }
        $payment_api->transaction_commit();
        $result = [
            'status' => $new_status_name,
            'status_message' => $status_message,
        ];
        return  $result;
    }

    public function get_currency_payout($options)
    {
        if ( ! $this->ENABLE) {
            return  null;
        }
        // import options
        is_array($options) && extract($options, EXTR_PREFIX_ALL | EXTR_REFS, '');
        $method = $this->api_method([
            'type' => 'payout',
            'method_id' => $_method_id,
        ]);
        $key = 'currency';
        if (empty($method) || empty($method[$key])) {
            return  null;
        }
        $currency = $method[$key];
        if (empty($_currency) || empty($currency[$_currency])) {
            $default = reset($currency);
            $result = $default['currency_id'];
        } else {
            $result = $currency['currency_id'];
        }
        return  $result;
    }

    public function get_fee_payout($options)
    {
        if ( ! $this->ENABLE) {
            return  null;
        }
        // import options
        is_array($options) && extract($options, EXTR_PREFIX_ALL | EXTR_REFS, '');
        $method = $this->api_method([
            'type' => 'payout',
            'method_id' => $_method_id,
        ]);
        $key = 'fee';
        if (empty($method) || empty($method[$key])) {
            return  null;
        }
        $result = $method[$key];
        if (is_array($result['out'])) {
            $result = $result['out'];
        }
        return  $result;
    }

    public function currency_conversion_payout($options)
    {
        // import options
        is_array($options) && extract($options, EXTR_PREFIX_ALL | EXTR_REFS, '');
        // var
        $amount = &$_amount;
        // processing
        if ( ! empty($_method['is_currency'])) {
            // currency_id
            $currency_id = $this->get_currency_payout($_options);
            if (empty($currency_id)) {
                $result = [
                    'status' => false,
                    'status_message' => 'Неизвестная валюта',
                ];
                return  $result;
            }
            $payment_api = &$this->payment_api;
            // currency conversion
            $amount_currency = $payment_api->currency_conversion([
                'type' => 'sell',
                'currency_id' => $currency_id,
                'amount' => $_amount,
            ]);
            if (empty($amount_currency)) {
                $result = [
                    'status' => false,
                    'status_message' => 'Невозможно произвести конвертацию валют',
                ];
                return  $result;
            }
            $amount = $amount_currency;
            // fee
            if ( ! empty($_method['is_fee'])) {
                $fee = $this->get_fee_payout($_options);
                $amount_currency_total = $payment_api->fee($amount_currency, $fee);
                $amount = $amount_currency_total;
            }
        }
        $result = [
            'status' => true,
            'amount' => $amount,
            'amount_currency' => @$amount_currency,
            'amount_currency_total' => @$amount_currency_total,
            'currency_id' => @$currency_id,
        ];
        return  $result;
    }

    public function amount_limit($options = null)
    {
        // import options
        is_array($options) && extract($options, EXTR_PREFIX_ALL | EXTR_REFS, '');
        if ( ! empty($_method['amount'])) {
            $min = @$_method['amount']['min'];
            $max = @$_method['amount']['max'];
            $status = false;
            switch (true) {
                case isset($min) && $_amount < $min: $status_message = 'больше'; $limit = $min; break;
                case isset($max) && $_amount > $max: $status_message = 'меньше'; $limit = $max; break;
                default: $status = true; break;
            }
            if (empty($status)) {
                $payment_api = &$this->payment_api;
                $amount_text = $payment_api->money_text([
                    'value' => $_amount,
                    'currency_id' => $_currency_id,
                ]);
                $limit_text = $payment_api->money_text([
                    'value' => $limit,
                    'currency_id' => $_currency_id,
                ]);
                $result = [
                    'status' => false,
                    'status_message' => @sprintf(
                        'Сумма %s должна быть %s %s',
                        $amount_text,
                        $status_message,
                        $limit_text
                    ),
                ];
                return  $result;
            }
        }
        $result = ['status' => true];
        return  $result;
    }

    public function _amount_payout($amount, $currency_id, $method, $is_request = true)
    {
        if ( ! $this->ENABLE) {
            return  null;
        }
        empty($currency_id) && $currency_id = $this->currency_default;
        if (empty($currency_id)) {
            return  null;
        }
        $payment_api = &$this->payment_api;
        list($_currency_id, $currency) = $payment_api->get_currency__by_id([
            'currency_id' => $currency_id,
        ]);
        if (empty($_currency_id)) {
            return  null;
        }
        // use conversion to integer by minor_units
        $is_int = false;
        if ( ! empty($method)
            && ! empty($method['currency'])
            && ! empty($method['currency'][$_currency_id])
            && ! empty($method['currency'][$_currency_id]['is_int'])
        ) {
            $is_int = true;
        }
        if ($is_int) {
            $units = pow(10, $currency['minor_units']);
            if ($is_request) {
                $result = (int) ($amount * $units);
            } else {
                $result = (float) $amount / $units;
            }
        } else {
            $result = $amount;
        }
        return  $result;
    }

    public function _payout_processing($options = null)
    {
        // import options
        is_array($options) && extract($options, EXTR_PREFIX_ALL | EXTR_REFS, '');
        // progress
        db()->begin();
        $result = $this->_update_status([
                'operation_id' => $_operation_id,
                'name' => 'processing',
            ]);
        if (empty($result['status'])) {
            db()->rollback();
            return  $result;
        }
        db()->commit();
        return  $result;
    }

    public function _payout_success($options = null)
    {
        // import options
        is_array($options) && extract($options, EXTR_PREFIX_ALL | EXTR_REFS, '');
        // progress
        db()->begin();
        $result = $this->_payout_balance_update($options);
        if (empty($result['status'])) {
            db()->rollback();
            return  $result;
        }
        $result = $this->_update_status([
                'operation_id' => $_operation_id,
                'name' => 'success',
                'is_finish' => true,
            ]);
        if (empty($result['status'])) {
            db()->rollback();
            return  $result;
        }
        db()->commit();
        return  $result;
    }

    public function _payout_refused($options = null)
    {
        // import options
        is_array($options) && extract($options, EXTR_PREFIX_ALL | EXTR_REFS, '');
        // progress
        db()->begin();
        $result = $this->_payout_amount_revert($options);
        if (empty($result['status'])) {
            db()->rollback();
            return  $result;
        }
        $result = $this->_payout_balance_update($options);
        if (empty($result['status'])) {
            db()->rollback();
            return  $result;
        }
        $result = $this->_update_status([
                'operation_id' => $_operation_id,
                'name' => 'refused',
                'is_finish' => true,
            ]);
        if (empty($result['status'])) {
            db()->rollback();
            return  $result;
        }
        db()->commit();
        return  $result;
    }

    public function _payout_amount_revert($options = null)
    {
        $payment_api = $this->payment_api;
        // operation
        $result = $payment_api->_amount_revert($options);
        return  $result;
    }

    public function _payout_balance_update($options = null)
    {
        $payment_api = $this->payment_api;
        // operation
        $result = $payment_api->_operation_balance_update($options);
        return  $result;
    }

    public function get_currency($options)
    {
        if ( ! $this->ENABLE) {
            return  null;
        }
        $_ = &$options;
        $allow = &$this->currency_allow;
        $default = $this->currency_default;
        // check: allow currency_id
        $id = $_['currency_id'];
        $result = $default;
        if (isset($allow[$id]) && $allow[$id]['active']) {
            $result = $id;
        }
        return  $result;
    }

    public function deposition($options)
    {
        if ( ! $this->ENABLE) {
            return  null;
        }
        $payment_api = $this->payment_api;
        $_ = $options;
        $data = &$_['data'];
        $options = &$_['options'];
        $operation_data = &$_['operation_data'];
        // prepare data
        $user_id = (int) $operation_data['user_id'];
        $operation_id = (int) $data['operation_id'];
        $account_id = (int) $data['account_id'];
        $provider_id = (int) $data['provider_id'];
        $amount = $payment_api->_number_float($data['amount']);
        $currency_id = $this->get_currency($options);
        if (empty($operation_id)) {
            $result = [
                'status' => false,
                'status_message' => 'Не определен код операции',
            ];
            return  $result;
        }
        // currency conversion
        $amount_currency = $payment_api->currency_conversion([
            'type' => 'buy',
            'currency_id' => $currency_id,
            'amount' => $amount,
        ]);
        if (empty($amount_currency)) {
            $result = [
                'status' => false,
                'status_message' => 'Невозможно произвести конвертацию валют',
            ];
            return  $result;
        }
        // fee
        $fee = $this->fee;
        $amount_currency_total = $payment_api->fee($amount_currency, $fee);
        // prepare request form
        $form_data = [
            'user_id' => $user_id,
            'operation_id' => $operation_id,
            'account_id' => $account_id,
            'provider_id' => $provider_id,
            'currency_id' => $currency_id,
            'fee' => $fee,
            'amount' => $amount,
            'amount_currency' => $amount_currency,
            'amount_currency_total' => $amount_currency_total,
        ];
        $form_options = [
            'amount' => $amount_currency_total,
            'currency' => $currency_id,
            'operation_id' => $operation_id,
            'title' => $data['title'],
            'description' => $operation_id,
        ];
        // add options
        $method = $this->api_method([
            'type' => 'payin',
            'method_id' => @$options['method_id'],
        ]);
        ! empty($method['option']) && $form_options += $method['option'];
        // form
        $form = $this->_form($form_options);
        // $form = $this->_form( $form_options, array( 'is_array' => true, ) );
        // save options
        $data = array_merge($form_data, ['method_id' => @$options['method_id']]);
        $operation_options = [
            'request' => [[
                'data' => $data,
                'form' => $form_options,
                'datetime' => $operation_data['sql_datetime'],
            ]],
        ];
        $result = $payment_api->operation_update([
            'operation_id' => $operation_id,
            'options' => $operation_options,
        ]);
        if ( ! $result['status']) {
            return  $result;
        }
        $result = [
            'form' => $form,
            'status' => true,
            'status_message' => t('Заявка на ввод средств принята'),
        ];
        return  $result;
    }

    public function payment($options)
    {
        if ( ! $this->ENABLE) {
            return  null;
        }
        $payment_api = $this->payment_api;
        $_ = $options;
        $data = &$_['data'];
        $options = &$_['options'];
        $operation_data = &$_['operation_data'];
        // prepare data
        $user_id = (int) $operation_data['user_id'];
        $operation_id = (int) $data['operation_id'];
        $account_id = (int) $data['account_id'];
        $provider_id = (int) $data['provider_id'];
        $amount = $payment_api->_number_float($data['amount']);
        $currency_id = $this->get_currency_payout($options);
        if (empty($operation_id)) {
            $result = [
                'status' => false,
                'status_message' => 'Не определен код операции',
            ];
            return  $result;
        }
        // currency conversion
        $amount_currency = $payment_api->currency_conversion([
            'type' => 'sell',
            'currency_id' => $currency_id,
            'amount' => $amount,
        ]);
        if (empty($amount_currency)) {
            $result = [
                'status' => false,
                'status_message' => 'Невозможно произвести конвертацию валют',
            ];
            return  $result;
        }
        // fee
        $fee = $this->get_fee_payout($options);
        $amount_currency_total = $payment_api->fee($amount_currency, $fee);
        // check balance
        $account_result = $payment_api->get_account(['account_id' => $account_id]);
        if (empty($account_result)) {
            $result = [
                'status' => false,
                'status_message' => 'Ошибка при проверке, баланса',
            ];
            return  $result;
        }
        list($account_id, $account) = $account_result;
        $balance = $account['balance'];
        if ($amount > $balance) {
            $result = [
                'status' => false,
                'status_message' => 'Недостаточно средств на счету',
            ];
            return  $result;
        }
        // update account balance
        db()->begin();
        $sql_datetime = $operation_data['sql_datetime'];
        $sql_amount = $payment_api->_number_mysql($amount);
        $_data = [
            'account_id' => $account_id,
            'datetime_update' => db()->escape_val($sql_datetime),
            'balance' => "( balance - $sql_amount )",
        ];
        $_result = $payment_api->balance_update($_data, ['is_escape' => false]);
        if ( ! $_result['status']) {
            db()->rollback();
            $result = [
                'status' => false,
                'status_message' => 'Ошибка при обновлении счета',
            ];
            return  $result;
        }
        $result = [
            'status' => true,
        ];
        // check account
        $account_result = $payment_api->get_account(['account_id' => $account_id]);
        if (empty($account_result)) {
            db()->rollback();
            $result = [
                'status' => false,
                'status_message' => 'Ошибка при получении, баланса',
            ];
            return  $result;
        }
        list($account_id, $account) = $account_result;
        // prepare
        // save options
        $request_data = [
            'user_id' => $user_id,
            'operation_id' => $operation_id,
            'account_id' => $account_id,
            'provider_id' => $provider_id,
            'currency_id' => $currency_id,
            'fee' => $fee,
            'amount' => $amount,
            'amount_currency' => $amount_currency,
            'amount_currency_total' => $amount_currency_total,
        ];
        $operation_options = $payment_api->_merge($operation_data['options'], [
            'request' => [[
                'options' => $options,
                'data' => $request_data,
                'datetime' => $operation_data['sql_datetime'],
            ]],
        ]);
        $operation_update_data = [
            'operation_id' => $operation_id,
            'status_id' => $data['status_id'],
            'balance' => $account['balance'],
            'datetime_update' => $sql_datetime,
            'options' => $operation_options,
        ];
        $result = $payment_api->operation_update($operation_update_data);
        if ( ! $result['status']) {
            db()->rollback();
            return  $result;
        }
        db()->commit();
        events()->fire('payment.balance_refresh', [$account]);
        // mail
        $data = [
            'operation_id' => $operation_id,
            'amount' => $amount,
        ];
        // mail: payout request
        $tpl = 'payout_request';
        if ($payment_api->IS_PAYOUT_CONFIRMATION) {
            // mail: confirmation
            $tpl = 'payout_confirmation';
            $data['code'] = $operation_data['options']['confirmation']['code'];
        }
        // mail
        $payment_api->mail([
            'tpl' => $tpl,
            'user_id' => $account['user_id'],
            'admin' => true,
            'data' => $data,
        ]);
        // message
        $result = [
            'status' => true,
            'status_message' => @$operation_data['status_message'] ?: t('Заявка на вывод средств принята'),
        ];
        return  $result;
    }

    protected function _api_post($url, $post, $options = null)
    {
        if ( ! $this->ENABLE) {
            return  null;
        }
        // import options
        $result = _class('api')->_request($url, $post, $options);
        return  $result;
    }
}