yf_manage_payout admin module

Fileplugins/payment/admin_modules/yf_manage_payout.class.php
Useadmin URL ?object=manage_payout

Public properties

A site sets them in its config with $PROJECT_CONF['manage_payout']['NAME'] = ...;

PropertyDefaultDescription
$IS_PAYOUT_INTERKASSA null
$manage_payment_lib null
$payment_api null

Public methods 40

cancel

public function cancel($options = null)
    public function cancel($options = null)
    {
        $operation_id = (int) $this->_get_operation_id($options);
        if ($operation_id < 1) {
            $result = [
                'status_message' => 'Invalid operation',
            ];
            return  $this->_user_message($result);
        }
        // processing
        $payment_api = &$this->payment_api;
        $result = $payment_api->cancel([
            'operation_id' => $operation_id,
        ]);
        return  $this->_user_message($result);
    }
public function check_all_interkassa($options = null)
    public function check_all_interkassa($options = null)
    {
        // command line interface
        $is_cli = (php_sapi_name() == 'cli');
        $is_cli && $this->_check_all_interkassa_cli();
        // web
        $replace = [
            'is_confirm' => false,
        ];
        $html_result = '';
        $result = form($replace)
            ->on_post(function ($data, $extra, $rules) use (&$html_result) {
                $is_confirm = ! empty($_POST['is_confirm']);
                if ($is_confirm) {
                    $result = $this->_check_all_interkassa();
                    if (empty($result)) {
                        $level = 'warning';
                        $message = 'No operations to process';
                    } else {
                        $level = 'success';
                        $message = 'Done, Interkassa operation statuses updated';
                        // prepare result html
                        $content = [];
                        $html = _class('html');
                        foreach ($result as $operation_id => $item) {
                            $link = $html->a([
                                'href' => $this->_url('view', ['%operation_id' => $operation_id]),
                                'class_add' => 'btn-xs btn-primary',
                                'target' => '_blank',
                                'icon' => 'fa fa-sign-out',
                                'title' => 'Payout #' . $operation_id,
                                'text' => 'Payout #' . $operation_id,
                            ]);
                            $content[$link] = $item['status_message'];
                        }
                        $html_result = $html->simple_table($content, ['no_total' => true]);
                    }
                    common()->add_message($message, $level);
                } else {
                    common()->message_info('Confirmation is required to perform the operation');
                }
            })
            ->check_box('is_confirm', ['desc' => 'Confirmation', 'no_label' => true])
            ->row_start()
                ->submit('operation', 'update', ['desc' => 'Update Interkassa operation statuses', 'icon' => 'fa fa-refresh'])
                ->link('Back', $this->_url('list'), ['class' => 'btn btn-xs btn-default', 'icon' => 'fa fa-chevron-left'])
            ->row_end();
        return  $result . $html_result;
    }
public function check_interkassa()
    public function check_interkassa()
    {
        $result = $this->_check_interkassa();
        return  $this->_user_message($result);
    }
public function confirmation_update_expired()
    public function confirmation_update_expired()
    {
        $url = &$this->url;
        // command line interface
        $is_cli = (php_sapi_name() == 'cli');
        $is_cli && $this->_confirmation_update_expired_cli();
        // web
        $replace = [
            'is_confirm' => false,
        ];
        $result = form($replace)
            ->on_post(function ($data, $extra, $rules) {
                $is_confirm = ! empty($_POST['is_confirm']);
                if ($is_confirm) {
                    $result = $this->_confirmation_update_expired();
                    if (empty($result)) {
                        $level = 'error';
                        $message = 'Update error';
                    } else {
                        $level = 'success';
                        $message = 'Update completed';
                    }
                    common()->add_message($message, $level);
                } else {
                    common()->message_info('Confirmation is required to perform the operation');
                }
            })
            ->info('header', ['value' => 'Deposit: update status of expired operations', 'no_label' => true, 'class' => 'text-warning'])
            ->check_box('is_confirm', ['desc' => 'Confirmation', 'no_label' => true])
            ->row_start()
                ->submit('operation', 'update', ['desc' => 'Update', 'icon' => 'fa fa-refresh'])
                ->link('Back', $url['list'], ['class' => 'btn btn-xs btn-default', 'icon' => 'fa fa-chevron-left'])
            ->row_end();
        return  $result;
    }

csv

public function csv()

https://cliff.ecommpay.com/download/%D0%98%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%86%D0%B8%D1%8F%20%D0%BF%D0%BE%20%D0%B2%D1%8B%D0%BF%D0%BB%D0%B0%D1%82%D0%B0%D0%BC%20%D1%87%D0%B5%D1%80%D0%B5%D0%B7%20%D1%84%D0%B0%D0%B9%D0%BB.pdf.

    public function csv()
    {
        $opt = null;
        // class
        $payment_api = &$this->payment_api;
        $provider_class = $payment_api->provider_class([
            'provider_name' => 'ecommpay',
        ]);
        // var
        $operation_id = (int) ($_GET['operation_id'] ?? null);
        $info = db()->from('payment_operation')->where('operation_id', $operation_id)->get();
        if ( ! $info) {
            return _404();
        }
        $info['options'] = json_decode($info['options'], true);
        $options = $info['options']['request'][0]['options'];
        $opt_data = $info['options']['request'][0]['data'];
        $data = [];
        $data['payment_group_id'] = 1; // Bank cards
        $data['site_id'] = $provider_class->key(); // EcommPay site id
        $data['external_id'] = $operation_id;
        $data['comment'] = 'Payments out request. Date: ' . date('Y-m-d_H-i-s') . ' OID: ' . $operation_id;
        $data['phone'] = preg_replace('~[^0-9]~ims', '', $options['sender_phone']);
        $data['customer_purse'] = $options['card'];
        // Transaction number in the client interface under which the funds were received earlier.
        // Usually used for payouts to bank cards without a PCI DSS certificate.
        // Currency of the payment amount. If the request currency differs from the currency of the account the payment is made from,
        // the provider converts the amount at its exchange rate.
        $data['amount'] = (int) ($options['amount'] * 100);
        $data['currency'] = 'USD';
        $data = [$data];
        $csv = $this->_array2csv($data);
        // Ecommpay wants ";" everywhere
        $csv = explode(PHP_EOL, $csv);
        $csv[0] = str_replace(',', ';', $csv[0]);
        $csv = trim(implode(PHP_EOL, $csv));
        no_graphics(true);
        if (DEBUG_MODE) {
            echo '<pre>';
            print_r($csv);
            print_r($opt);
            print_r($info);
            print_r($data);
        } else {
            header('Content-disposition: attachment; filename=Ecommpay_out_' . (int) $operation_id . '_' . date('Ymd_His') . '.csv');
            header('Content-type: text/csv');
            echo $csv;
        }
        exit;
    }

csv_request

public function csv_request($options = null)
    public function csv_request($options = null)
    {
        $_method = $_request = $_operation_id = null;
        // check operation
        $operation = $this->_operation();
        // import options
        is_array($operation) && extract($operation, EXTR_PREFIX_ALL | EXTR_REFS, '');
        if (empty($_is_valid)) {
            return  $this->_user_message($operation);
        }
        // var
        $payment_api = &$this->payment_api;
        // prepare view: request options
        $content = [];
        foreach ($_method['option'] as $key => $title) {
            if ( ! empty($_request['options'][$key])) {
                $content[$title] = $_request['options'][$key];
            }
        }
        // prepare data
        $data = [];
        $data[] = array_keys($content);
        $data[] = array_values($content);
        $file_name = 'operation_' . $_operation_id . '__' . date('Y-m-d_H-i-s') . '.csv';
        // output
        $result = $this->_http_csv([
            'file_name' => $file_name,
            'data' => $data,
            // 'debug'     => true,
        ]);
        $result['operation_id'] = $_operation_id;
        return  $this->_user_message($result);
    }

expired

public function expired($options = null)
    public function expired($options = null)
    {
        $operation_id = (int) $this->_get_operation_id($options);
        if ($operation_id < 1) {
            $result = [
                'status_message' => 'Invalid operation',
            ];
            return  $this->_user_message($result);
        }
        // processing
        $payment_api = &$this->payment_api;
        $result = $payment_api->expired([
            'operation_id' => $operation_id,
        ]);
        return  $this->_user_message($result);
    }

filter_save

public function filter_save()
    public function filter_save()
    {
        $object = &$this->object;
        $id = &$this->id;
        switch ($id) {
            case 'manage_payout__show':
                $url_redirect_url = url_admin([
                    'object' => $object,
                ]);
                // no break
            case 'clear':
                $url_redirect_url = url_admin([
                    'object' => $object,
                    'action' => 'show',
                ]);
                $id = 'manage_payout__show';
                break;
        }
        $options = [
            'filter_name' => $id,
            'redirect_url' => $url_redirect_url,
        ];
        return  _class('admin_methods')->filter_save($options);
    }
public function interkassa_detect_card($options = null)
    public function interkassa_detect_card($options = null)
    {
        $_card = null;
        // import options
        is_array($options) && extract($options, EXTR_PREFIX_ALL | EXTR_REFS, '');
        if (empty($_card)) {
            $result = [
                'status_message' => 'Card number is not set',
            ];
            return  $result;
        }
        // var
        $payment_api = &$this->payment_api;
        $provider_class = $payment_api->provider_class([
            'provider_name' => 'interkassa',
        ]);
        if (empty($provider_class)) {
            $result = [
                'status_message' => 'Interkassa provider is not available',
            ];
            return  $result;
        }
        // find by card
        $validate = _class('validate');
        $methods = &$provider_class->method_allow['payout'];
        if ( ! ($methods ?? null)) {
            $result = [
                'status_message' => 'Interkassa provider: payout methods not found',
            ];
            return  $result;
        }
        $is_method_id = null;
        foreach ($methods as $method_id => $method) {
            if (empty($method['option_validation']['card'])) {
                continue;
            }
            $rules = &$method['option_validation']['card'];
            $result = $validate->_input_is_valid($_card, $rules);
            if ($result) {
                $is_method_id = $method_id;
                break;
            }
        }
        if (empty($is_method_id)) {
            $result = [
                'status_message' => 'Card not recognized: ' . $_card,
            ];
        } else {
            $result = [$method_id, $method];
        }
        return  $result;
    }

mass_cancel

public function mass_cancel($options = null)
    public function mass_cancel($options = null)
    {
        $operation_id = $this->_get_operation_id($options);
        is_numeric($operation_id) && $operation_id = [$operation_id => 1];
        if ( ! is_array($operation_id)) {
            return  $this->_mass_result([
                'status_header' => 'Cancel operations',
                'status_message' => 'An array of operations is required',
            ]);
        }
        // processing
        $payment_api = &$this->payment_api;
        $html = _class('html');
        $title = 'Cancelled #';
        $content = [];
        foreach ($operation_id as $id => $selected) {
            $id = (int) $id;
            if ($id < 1) {
                continue;
            }
            $header = $title . $id;
            $result = $payment_api->cancel([
                'operation_id' => $id,
            ]);
            $text = $result['status_message'];
            $link = $html->a([
                'href' => $this->_url('view', ['%operation_id' => $id]),
                'class_add' => 'btn-xs ' . (($result['status'] ?? null) ? 'btn-success' : 'btn-warning'),
                'target' => '_blank',
                'icon' => 'fa fa-sign-out',
                'title' => $text,
                'text' => $text,
            ]);
            $content[$header] = $link;
        }
        // table
        $table = $html->simple_table($content, ['no_total' => true]);
        return  $this->_mass_result([
            'status' => true,
            'status_message' => $table,
            'status_header' => 'Cancel operations',
        ]);
    }

mass_payout

public function mass_payout($options = null)
    public function mass_payout($options = null)
    {
        $operation_id = $this->_get_operation_id($options);
        is_numeric($operation_id) && $operation_id = [$operation_id => 1];
        if ( ! is_array($operation_id)) {
            return  $this->_mass_result([
                'status_message' => 'An array of operations is required',
            ]);
        }
        // processing
        $payment_api = &$this->payment_api;
        $html = _class('html');
        $title = 'Payout #';
        $content = [];
        foreach ($operation_id as $id => $selected) {
            $id = (int) $id;
            if ($id < 1) {
                continue;
            }
            $header = $title . $id;
            $result = $this->_request([
                'operation_id' => $id,
            ]);
            $text = $result['status_message'];
            $link = $html->a([
                'href' => $this->_url('view', ['%operation_id' => $id]),
                'class_add' => 'btn-xs ' . (($result['status'] ?? null) ? 'btn-success' : 'btn-warning'),
                'target' => '_blank',
                'icon' => 'fa fa-sign-out',
                'title' => $text,
                'text' => $text,
            ]);
            $content[$header] = $link;
        }
        // table
        $table = $html->simple_table($content, ['no_total' => true]);
        return  $this->_mass_result([
            'status' => true,
            'status_message' => $table,
        ]);
    }

mass_remove

public function mass_remove($options = null)
    public function mass_remove($options = null)
    {
        // start
        $operation_id = $this->_get_operation_id($options);
        is_numeric($operation_id) && $operation_id = [$operation_id => 1];
        if ( ! is_array($operation_id)) {
            return  $this->_mass_result([
                'status_header' => 'Delete operations',
                'status_message' => 'An array of operations is required',
            ]);
        }
        // processing
        $ids = array_keys($operation_id);
        $r = db()->table('payment_operation')
            ->where('operation_id', 'in', db()->es_for_builder($ids))
            ->delete();
        return  $this->_mass_result([
            'status' => $r,
            'status_message' => 'List: ' . implode(', ', $ids),
            'status_header' => 'Delete operations',
        ]);
    }

recreate

public function recreate()
    public function recreate()
    {
        // var
        $payment_api = &$this->payment_api;
        $url = &$this->url;
        $is_post = main()->is_post();
        $operation_id = (int) ($_REQUEST['operation_id'] ?? null);
        // check
        if ($operation_id < 1) {
            $url_list = $this->_url('list');
            return  js_redirect($url_list, false, 'Empty operation_id');
        }
        // operation
        $operation = $payment_api->operation(['operation_id' => $operation_id]);
        $_operation_id = (int) ($operation['operation_id'] ?? null);
        if ($operation_id != $_operation_id) {
            $result = [
                'status_header' => 'Recreate operation',
                'status_message' => 'Operation not found',
            ];
            return  $this->_user_message($result);
        }
        // amount
        $amount = (float) $operation['amount'];
        // form
        $url_view = $this->_url('view', ['%operation_id' => $operation_id]);
        $replace = [
            'operation_id' => $operation_id,
            'amount_text' => $payment_api->money_text($amount),
            'amounts' => ($_POST['amounts'] ?? null),
        ];
        $result = form($replace)
            ->on_post(function ($data, $extra, $rules) {
                $is_recreate = ($_POST['operation'] ?? null) == 'recreate';
                if ($is_recreate) {
                    $result = $this->_recreate($_POST);
                    if ($result['status'] ?? null) {
                        $level = 'success';
                        $message = 'Recreated: ';
                    } else {
                        $level = 'error';
                        $message = 'Recreate error: ';
                    }
                    $message .= $result['status_message'];
                    common()->add_message($message, $level);
                    // recreated items
                    if ($result['status'] ?? null) {
                        $operation_id = (int) $result['operation_id'];
                        $items = implode(',', $result['items']);
                        $url = $this->_url('recreate_items', ['%operation_id' => $operation_id, '%items' => $items]);
                        return  js_redirect($url, false, 'recreated items: ', $items);
                    }
                }
            })
            ->info('operation_id', 'Operation', ['class' => 'text-warning', 'class_add_controls' => 'form-control-static'])
            ->info('amount_text', 'Amount', ['class' => 'text-success', 'class_add_controls' => 'form-control-static'])
            ->hidden('operation_id')
            ->input('amounts', ['desc' => 'Amount', 'label_tip' => 'Must not exceed the operation amount. To split the amount, separate parts with any non-numeric character: 10-20-30 for an amount of 50'])
            ->row_start()
                ->submit('operation', 'recreate', ['desc' => 'Recreate', 'icon' => 'fa fa-refresh'])
                ->link('Back', $url_view, ['class' => 'btn btn-xs btn-default', 'icon' => 'fa fa-chevron-left'])
            ->row_end();
        return  $result;
    }
public function recreate_items()
    public function recreate_items()
    {
        // options
        $operation_id = ($_GET['operation_id'] ?? null);
        $items = explode(',', ($_GET['items'] ?? null));
        $items = array_filter($items, 'trim');
        // table
        $html = _class('html');
        $text = 'Operation';
        $content = [];
        $link = $html->a([
                'href' => $this->_url('view', ['%operation_id' => $operation_id]),
                'class_add' => 'btn-xs btn-danger',
                'target' => '_blank',
                'icon' => 'fa fa-sign-out',
                'title' => $text,
                'text' => $text,
            ]);
        $content['Recreated operation #' . $operation_id] = $link;
        $text = 'Payout';
        $title = 'New operation #';
        foreach ($items as $operation_id) {
            $header = $title . $operation_id;
            $link = $html->a([
                'href' => $this->_url('view', ['%operation_id' => $operation_id]),
                'class_add' => 'btn-xs btn-success',
                'target' => '_blank',
                'icon' => 'fa fa-sign-out',
                'title' => $text,
                'text' => $text,
            ]);
            $content[$header] = $link;
        }
        $result = $html->simple_table($content, ['no_total' => true]);
        return  $result;
    }

request

public function request()
    public function request()
    {
        $result = $this->_request();
        return  $this->_user_message($result);
    }
public function request_interkassa()
    public function request_interkassa()
    {
        $_request = $_operation_id = null;
        // start
        db()->begin();
        // check operation
        $operation = $this->_operation();
        // import options
        is_array($operation) && extract($operation, EXTR_PREFIX_ALL | EXTR_REFS, '');
        if (empty($_is_valid)) {
            return  $this->_user_message($operation);
        }
        // is_processing
        if ($_is_processing ?? null) {
            $result = [
                'status_message' => 'The operation is already being processed',
            ];
            return  $this->_user_message($result);
        }
        // is_progressed
        if ( ! ($_is_progressed ?? null)) {
            $result = [
                'status_message' => 'The operation cannot be processed because its status has changed',
            ];
            return  $this->_user_message($result);
        }
        // var
        $payment_api = &$this->payment_api;
        $data = $_request['options'] + [
            'operation_id' => $_operation_id,
        ];
        // provider
        $provider_class = $payment_api->provider_class([
            'provider_name' => 'interkassa',
        ]);
        if (empty($provider_class)) {
            $result = [
                'status_message' => 'Interkassa provider is not available',
            ];
            return  $this->_user_message($result);
        }
        // detect card type
        $card = ($data['card'] ?? null);
        $result = $this->interkassa_detect_card([
            'card' => $card,
        ]);
        @list($method_id, $method) = $result;
        if (empty($method_id)) {
            return  $this->_user_message($result);
        }
        $data['method_id'] = $method_id;
        $data['provider_force'] = true;
        // result
        $result = $provider_class->api_payout($data);
        // finish
        db()->commit();
        return  $this->_user_message($result);
    }

show

public function show()
    public function show()
    {
        $object = &$this->object;
        $action = &$this->action;
        $filter_name = &$this->filter_name;
        $filter = &$this->filter;
        $url = &$this->url;
        // class
        $payment_api = &$this->payment_api;
        $manage_lib = &$this->manage_payment_lib;
        // mass actions
        if (main()->is_post()) {
            $is_mass = ($_POST['mass'] ?? null);
            switch ($is_mass) {
                case 'payout': return  $this->mass_payout();
                    break;
                case 'cancel': return  $this->mass_cancel();
                    break;
                case 'remove': return  $this->mass_remove();
                    break;
            }
        }
        // payment providers
        $providers = $payment_api->provider();
        $payment_api->provider_options($providers, [
            'method_allow',
        ]);
        // payment status
        $payment_status = $payment_api->get_status();
        $name = 'in_progress';
        $item = $payment_api->get_status(['name' => $name]);
        list($payment_status_in_progress_id, $payment_status_in_progress) = $item;
        if (empty($payment_status_in_progress_id)) {
            $result = [
                'status_message' => 'Payment status not found: ' . $name,
            ];
            return  $this->_user_message($result);
        }
        // prepare sql
        $db = db()->select(
            'o.operation_id',
            'o.account_id',
            'o.provider_id',
            'o.options',
            'a.user_id',
            'o.amount',
            // 'a.balance',
            'o.balance',
            'p.title as provider_title',
            'o.status_id as status_id',
            'o.datetime_start',
            'u.name as user_name',
            'u.login as user_login',
            'u.nick as user_nick',
            'u.email as user_email'
        )
            ->table('payment_operation as o')
                ->left_join('payment_provider as p', 'p.provider_id = o.provider_id')
                ->left_join('payment_account  as a', 'a.account_id  = o.account_id')
                ->left_join('user as u', 'u.id = a.user_id')
            ->where('p.system', 'in', 0)
            ->where('p.active', '>=', 1)
            ->where('o.direction', 'out');
        $sql = $db->sql();
        $result = table($sql, [
                'filter' => $filter,
                'filter_params' => [
                    'status_id' => function ($a) use ($payment_status_in_progress_id) {
                        $result = null;
                        $value = $a['value'];
                        // default status_id = in_progress
                        if (empty($value)) {
                            $value = $payment_status_in_progress_id;
                        } elseif ($value == -1) {
                            $value = null;
                        }
                        isset($value) && $result = ' o.status_id = ' . $value;
                        return  $result;
                    },
                    'provider_id' => ['cond' => 'eq',      'field' => 'o.provider_id'],
                    'balance' => ['cond' => 'between', 'field' => 'o.balance'],
                    'amount' => ['cond' => 'between', 'field' => 'o.amount'],
                    'name' => function ($a) {
                        $v = $a['value'];
                        $like = 'LIKE "' . _es($v) . '%"';
                        if (is_numeric($v)) {
                            return 'u.id = ' . (int) $v;
                        } elseif (false !== strpos($v, ',')) {
                            return 'u.id IN(' . _es($v) . ')';
                        }
                        return '(u.name ' . $like . ' OR u.nick ' . $like . ' OR u.login ' . $like . ' OR u.email ' . $like . ')';
                    },
                    'title' => function ($a) {
                        $v = $a['value'];
                        $like = 'LIKE "' . _es($v) . '%"';
                        if (is_numeric($v)) {
                            return 'o.operation_id = ' . (int) $v;
                        } elseif (false !== strpos($v, ',')) {
                            return 'o.operation_id IN(' . _es($v) . ')';
                        }
                        return '(o.title ' . $like . ' OR o.options ' . $like . ')';
                    },
                    'datetime_start' => 'daterange_dt_between',
                    'datetime_update' => 'daterange_dt_between',
                    'datetime_finish' => 'daterange_dt_between',
                    '__default_order' => 'ORDER BY o.datetime_update DESC',
                ],
            ])
            ->check_box('operation_id', ['desc' => 'Select', 'no_desc' => true])
            ->text('operation_id', 'Operation')
            ->text('provider_title', 'Provider')
            ->func('options', function ($value, $extra, $row) use ($providers) {
                if (empty($row['options'])) {
                    return  null;
                }
                // options
                $options = @json_decode($row['options'], true);
                if (empty($options)) {
                    return  null;
                }
                // request
                is_array($options['request']) && $request = @reset($options['request']);
                if (empty($request)) {
                    return  null;
                }
                // method
                $provider_id = ($request['options']['provider_id'] ?? null);
                if (empty($provider_id)) {
                    return  null;
                }
                $method_id = ($request['options']['method_id'] ?? null);
                if (empty($method_id)) {
                    return  null;
                }
                $method = ($providers[$provider_id]['_method_allow']['payout'][$method_id] ?? null);
                if (empty($method)) {
                    return  null;
                }
                $method_title = ($method['title'] ?? null);
                if (empty($method_title)) {
                    return  null;
                }
                $result = $method_title;
                return  $result;
            }, ['desc' => 'Method'])
            ->text('amount', 'Amount')
            ->text('balance', 'Balance')
            ->func('user_name', function ($value, $extra, $row) {
                $name = ($row['user_name'] ?? null) ?: ($row['user_login'] ?? null) ?: ($row['user_nick'] ?? null) ?: $row['user_email'];
                $result = a('/members/edit/' . $row['user_id'], $name . ' (id: ' . $row['user_id'] . ')');
                return  $result;
            }, ['desc' => 'User'])
            ->func('status_id', function ($value, $extra, $row) use ($manage_lib, $payment_status) {
                $status_name = $payment_status[$value]['name'];
                $title = $payment_status[$value]['title'];
                $css = $manage_lib->css_by_status([
                    'status_name' => $status_name,
                ]);
                $result = sprintf('<span class="%s">%s</span>', $css, $title);
                return  $result;
            }, ['desc' => 'Status'])
            ->text('datetime_start', 'Created')
            ->btn('Payout', $url['view'], ['icon' => 'fa fa-sign-out', 'class_add' => 'btn-primary', 'target' => '_blank']);
        // ->btn( 'User'         , $url[ 'user'    ], array( 'icon' => 'fa fa-user'    , 'class_add' => 'btn-info'   ) )
        // ->btn( 'Account'      , $url[ 'balance' ], array( 'icon' => 'fa fa-money'   , 'class_add' => 'btn-info'   ) )
        // mass actions
        $result->footer_submit(['name' => 'mass', 'value' => 'payout', 'desc' => 'Payout', 'class' => 'btn btn-xs btn-success', 'icon' => 'fa fa-sign-out']);
        $result->footer_submit(['name' => 'mass', 'value' => 'cancel', 'desc' => 'Cancel', 'class' => 'btn btn-xs btn-warning', 'icon' => 'fa fa-ban']);
        $result->footer_submit(['name' => 'mass', 'value' => 'remove', 'desc' => 'Delete', 'class' => 'btn btn-xs btn-danger', 'icon' => 'fa fa-remove']);
        // other actions
        $result->header_link('Update Interkassa operation statuses', $url['check_all_interkassa'], ['class' => 'btn btn-xs btn-primary', 'icon' => 'fa fa-refresh']);
        $result->header_link('Update confirmation operation statuses', $url['confirmation_update_expired'], ['class' => 'btn btn-xs btn-primary', 'icon' => 'fa fa-refresh']);

        $data_daily = $this->_get_daily_data($last_days = 180);
        $data_chart = _class('charts')->jquery_sparklines($data_daily);

        $quick_filter = $this->_show_quick_filter();

        return
            '<div class="col-md-12">' .
                ($data_chart ? '<div class="col-md-6" title="' . t('Transactions per day') . '">' . $data_chart . '</div>' : '') .
                ($quick_filter ? '<div class="col-md-6 pull-right" title="' . t('Quick filter') . '">' . $quick_filter . '</div>' : '') .
            '</div>' .
            $result;
    }

stats

public function stats()
    public function stats()
    {
        $payment_api = &$this->payment_api;
        // payment providers
        $providers = $payment_api->provider();
        $payment_api->provider_options($providers, [
            'method_allow',
        ]);
        // payment status
        $payment_status = $payment_api->get_status();
        $name = 'in_progress';
        $item = $payment_api->get_status(['name' => $name]);
        list($payment_status_in_progress_id, $payment_status_in_progress) = $item;
        // prepare sql
        $sql = db()->select(
            'o.operation_id',
            'o.account_id',
            'o.provider_id',
            'o.options',
            'a.user_id',
            'u.name as user_name',
            'o.amount',
            'o.balance',
            'p.title as provider_title',
            'o.status_id as status_id',
            'o.datetime_start'
        )
            ->table('payment_operation as o')
                ->left_join('payment_provider as p', 'p.provider_id = o.provider_id')
                ->left_join('payment_account  as a', 'a.account_id  = o.account_id')
                ->left_join('user as u', 'u.id = a.user_id')
            ->where('p.system', 'in', 0)
            ->where('p.active', '>=', 1)
            ->where('o.direction', 'out')
            ->where('type_id', 2) // payout
            ->where('status_id', 1); // in_progress
        $data = $sql->all();
        $by_method = [];
        $totals = [];
        foreach ($data as $k => $v) {
            $data[$k]['options'] = json_decode($v['options'], true);
            $data[$k]['request'] = $data[$k]['options']['request'][0];
            unset($data[$k]['options']);
            $options = $data[$k]['request']['options'];
            $payout_method = $options['method_id'];
            if ($payout_method == 'pay_card' && substr($options['sender_phone'], 0, 2) == '38') {
                $payout_method = 'pay_card_ukr';
            }
            $by_method[$payout_method]['method'] = $payout_method;
            $by_method[$payout_method]['sum'] += $data[$k]['amount'];
            @$by_method[$payout_method]['count']++;
        }
        ksort($by_method);
        foreach ($by_method as $k => $v) {
            $totals['method'] = '_TOTAL_';
            $totals['sum'] += $v['sum'];
            $totals['count'] += $v['count'];
        }
        $by_method[] = $totals;
        $by_method = array_filter($by_method);
        return table($by_method)
            ->text('method')
            ->text('sum')
            ->text('count');
    }

status

public function status($options = null)
    public function status($options = null)
    {
        $result = $this->_status($options);
        return  $this->_user_message($result);
    }
public function status_interkassa($options = null)
    public function status_interkassa($options = null)
    {
        $_state = $_data = null;
        // import operation
        is_array($options) && extract($options, EXTR_PREFIX_ALL | EXTR_REFS, '');
        // provider interkassa
        $payment_api = &$this->payment_api;
        $provider_name = 'interkassa';
        $provider_class = $payment_api->provider_class([
            'provider_name' => $provider_name,
        ]);
        if (empty($provider_class)) {
            $result = [
                'status_message' => 'Interkassa provider is not available',
            ];
            return  $result;
        }
        // result
        $result = [
            'operation_id' => &$_operation_id,
            'status' => &$status_name,
            'status_message' => &$status_message,
        ];
        // state
        list($status_name, $status_message) = $provider_class->_state(
            $_state,
            $provider_class->_payout_status,
            $provider_class->_payout_status_message
        );
        $status_message = ($status_message ?? null) ?: ($data['stateName'] ?? null);
        // transaction compile
        if (($result['status'] ?? null) == 'success' || ($result['status'] ?? null) == 'refused') {
            // save response
            $sql_datetime = $payment_api->sql_datetime();
            $operation_options = [
                'response' => [[
                    'datetime' => $sql_datetime,
                    'provider_name' => $provider_name,
                    'state' => $_state,
                    'status_name' => $status_name,
                    'status_message' => $status_message,
                    'data' => $_data,
                ]],
            ];
            $operation_update_data = [
                'operation_id' => $_operation_id,
                'datetime_update' => $sql_datetime,
                'options' => $operation_options,
            ];
            $payment_api->operation_update($operation_update_data);
            // update status
            return  $this->_status($result);
        }
        return  $result;
    }

view

public function view()

operation options: 'operation_id' 'operation' 'account_id' 'account' 'user_id' 'user' 'user_is_online' 'provider_id' 'provider' 'provider_class' 'providers_user' 'request' 'method' etc: see _operation().

    public function view()
    {
        $_is_options = $_method = $_request = $_operation_id = $_response = $_account_id = $_provider_id = $_method_id = $_user_id = $_user = $_account = $_provider = $_html_card_title = $_html_amount = $_html_status_title = $_html_datetime_start = $_html_datetime_update = $_html_datetime_finish = $_provider_class = $_provider_name = null;
        // check operation
        $operation = $this->_operation();
        // import options
        is_array($operation) && extract($operation, EXTR_PREFIX_ALL | EXTR_REFS, '');
        if (empty($_is_valid)) {
            return  $this->_user_message($operation);
        }
        // var
        $url = &$this->url;
        $html = _class('html');
        $payment_api = &$this->payment_api;
        $manage_lib = &$this->manage_payment_lib;
        // prepare view: request options
        $content = [];
        if ($_is_options) {
            foreach ($_method['option'] as $key => $title) {
                if ( ! empty($_request['options'][$key])) {
                    $content[$title] = $_request['options'][$key];
                }
            }
            $html_request_options = $html->simple_table($content, ['no_total' => true]);
            $html_request_options_csv = $html->a([
                'href' => $this->_url('csv_request', ['%operation_id' => $_operation_id]),
                'icon' => 'fa fa-file-excel-o',
                'title' => 'Request options: export to CSV file',
                'text' => 'CSV file',
                'target' => '_blank',
            ]);
        }
        // prepare view: response options
        $content = null;
        if ( ! empty($_response)) {
            $response = array_reverse($_response);
            $content = table($response, ['no_total' => true])
                ->text('datetime', 'Date')
                ->func('data', function ($value, $extra, $row) use ($_provider_name, $_providers_user__by_name) {
                    // message
                    $message = ($row['status_message'] ?? null) ?: ($row['data']['message'] ?? null);
                    $result = t(trim(trim($message), '.,:'));
                    // provider
                    $provider_name = ($row['provider_name'] ?? null);
                    if ($provider_name && $provider_name != $_provider_name) {
                        $provider_title = ($_providers_user__by_name[$provider_name]['title'] ?? null);
                        $result .= ' (' . $provider_title . ')';
                    }
                    return  $result;
                }, ['desc' => 'Message'])
                ->func('data', function ($value, $extra, $row) {
                    $result = ($row['state'] ?? null) ?: ($row['data']['state'] ?? null) ?: null;
                    return  $result;
                }, ['desc' => 'Status']);
            $response_last = reset($response);
            $response_last = $response_last['data'];
        }
        $html_response = $content;
        // prepare view: operations by method
        list($data, $count) = $payment_api->operation([
            'where' => 'account_id = ' . $_account_id
                . ' AND provider_id = ' . $_provider_id
                . ' AND operation_id != ' . $_operation_id
                . ' AND direction = "out"',
            'limit' => 50,
        ]);
        $html_operations_by_method = null;
        if (@count($data) > 0) {
            $content = [];
            foreach ($data as $item) {
                $request = &$item['options']['request'][0];
                // match method
                if (($request['options']['method_id'] ?? null) == $_method_id) {
                    $request_options = &$request['options'];
                    $account_number = ($request_options['account_number'] ?? null) ?:
                            ($request_options['account'] ?? null) ?:
                            ($request_options['card'] ?? null) ?:
                            ($request_options['to'] ?? null) ?:
                            ($request_options['purse'] ?? null) ?:
                            ($request_options['customer_purse'] ?? null) ?:
                            '-';
                    $content[$item['operation_id']] = [
                        'operation_id' => $item['operation_id'],
                        'account_number' => $account_number,
                        'amount' => $item['amount'],
                        'status_id' => $item['status_id'],
                        'date' => $item['datetime_update'],
                    ];
                }
            }
            $content && $html_operations_by_method = table($content, ['no_total' => true])
                ->text('operation_id', 'Operation')
                ->text('account_number', 'Account, card number, wallet')
                ->func('amount', function ($value, $extra, $row) use ($payment_api) {
                    $result = $payment_api->money_html($value);
                    return  $result;
                }, ['desc' => 'Amount'])
                ->func('status_id', function ($value, $extra, $row) use ($manage_lib, $_statuses) {
                    $status_name = $_statuses[$value]['name'];
                    $title = $_statuses[$value]['title'];
                    $css = $manage_lib->css_by_status([
                        'status_name' => $status_name,
                    ]);
                    $result = sprintf('<span class="%s">%s</span>', $css, $title);
                    return  $result;
                }, ['desc' => 'Status'])
                ->text('date', 'Date')
                ->btn('Payout', $url['view'], ['icon' => 'fa fa-sign-out', 'class_add' => 'btn-xs btn-primary', 'target' => '_blank']);
        }
        // prepare view: operation options
        $user_link = $html->a([
            'href' => $this->_url('user', ['%user_id' => $_user_id]),
            'icon' => 'fa fa-user',
            'title' => 'Profile',
            'text' => $_user['name'],
        ]);
        $balance_link = $html->a([
            'href' => $this->_url('balance', ['%user_id' => $_user_id, '%account_id' => $_account_id]),
            'title' => 'Balance',
            'text' => $payment_api->money_text($_account['balance']),
        ]);
        // compile
        $content = [
            'Operation' => $_operation_id,
            'User' => $user_link . $balance_link,
            'Provider' => $_provider['title'],
            'Method' => $_method['title'],
            'Card type' => $_html_card_title,
            'Amount' => $_html_amount,
            'Status' => $_html_status_title,
            'Created' => $_html_datetime_start,
            'Updated' => $_html_datetime_update,
            'Finished' => $_html_datetime_finish,
        ];
        if ( ! $_is_options) {
            unset($content['Method']);
            $content['Warning'] = '<span class="text-danger">payout details are missing</span>';
        }
        if ( ! @$_html_card_title) {
            unset($content['Card type']);
        }
        $html_operation_options = $html->simple_table($content, ['no_total' => true]);
        $url_view = $this->_url('view', ['%operation_id' => $_operation_id]);
        // manual mode
        $is_manual = false;
        $is_test = $_provider_class->is_test();
        switch ($_provider_name) {
            case 'ecommpay':
                $is_manual = true;
                $url_base = 'https://cliff.ecommpay.com/';
                $url_provider_operations = $url_base . 'operations/searchPayout';
                $url_provider_payouts = $url_base . 'payouts';
                if ($is_test) {
                    $url_base = 'https://cliff-sandbox.ecommpay.com/';
                    $url_provider_operations = $url_base . 'operations';
                    $url_provider_payouts = $url_provider_operations;
                }
                $url_provider_operation_detail = empty($response_last['transaction_id']) ? null : $url_base . 'operations/detail/' . $response_last['transaction_id'];
                break;
        }
        // render
        $replace = $operation + [
            'is_manual' => $is_manual,
            'header_data' => $html_operation_options,
            'request_data' => $html_request_options,
            'request_data_csv' => $html_request_options_csv,
            'response_data' => $html_response,
            'operations_by_method' => $html_operations_by_method,
            'url' => [
                'list' => $this->_url('list'),
                'recreate' => $this->_url('recreate', ['%operation_id' => $_operation_id]),
                'cancel' => $this->_url('cancel', ['%operation_id' => $_operation_id]),
                'expired' => $this->_url('expired', ['%operation_id' => $_operation_id]),
                'view' => $this->_url('view', ['%operation_id' => $_operation_id]),
                'request' => $this->_url('request', ['%operation_id' => $_operation_id]),
                'request_interkassa' => $this->_url('request_interkassa', ['%operation_id' => $_operation_id]),
                'check_interkassa' => $this->_url('check_interkassa', ['%operation_id' => $_operation_id]),
                'status_processing' => $this->_url('status_processing', ['%operation_id' => $_operation_id]),
                'status_success' => $this->_url('status_success', ['%operation_id' => $_operation_id]),
                'status_refused' => $this->_url('status_refused', ['%operation_id' => $_operation_id]),
                'csv' => $this->_url('csv', ['%operation_id' => $_operation_id]),
                'provider_operation_detail' => @$url_provider_operation_detail,
                'provider_operations' => @$url_provider_operations,
                'provider_payouts' => @$url_provider_payouts,
            ],
        ];
        $result = tpl()->parse('manage_payout/view', $replace);
        return  $result;
    }

Methods starting with _ hooks and helpers; not routable as page actions

_array2csv

public function _array2csv(array &$array, $delim = ';')
  • $array array
    public function _array2csv(array &$array, $delim = ';')
    {
        if (count((array) $array) == 0) {
            return null;
        }
        ob_start();
        $df = fopen('php://output', 'w');
        fputcsv($df, array_keys(reset($array)), ',', '"', '\\');
        foreach ($array as $row) {
            fputcsv($df, $row, $delim, '"', '\\');
        }
        fclose($df);
        return ob_get_clean();
    }
public function _check_all_interkassa()
    public function _check_all_interkassa()
    {
        // var
        $html = _class('html');
        $payment_api = &$this->payment_api;
        // update status only processing
        $object = $payment_api->get_status(['name' => 'processing']);
        list($status_id, $status) = $object;
        if (empty($status_id)) {
            return  $object;
        }
        // provider
        $providers_user = $payment_api->provider();
        $providers_id = implode(',', array_keys($providers_user));
        // fetch operations
        $db = db()->table('payment_operation')
            ->where('provider_id', 'in', $providers_id)
            ->where('status_id', '=', $status_id)
            ->where('direction', '=', 'out');
        $operations = $db->get_all();
        if (empty($operations)) {
            return  $operations;
        }
        // check operations
        $result = [];
        foreach ($operations as $item) {
            $operation_id = $item['operation_id'];
            $r = $this->_check_interkassa([
                'operation_id' => $operation_id,
            ]);
            $result[$operation_id] = $r;
            sleep(5);
        }
        return  $result;
    }
public function _check_all_interkassa_cli($options = null)
    public function _check_all_interkassa_cli($options = null)
    {
        $result = $this->_check_all_interkassa();
        if (empty($result)) {
            $status = -1;
            $message = 'no operations';
        } else {
            $status = 0;
            $content = [];
            foreach ($result as $operation_id => $item) {
                $content[] = $operation_id . ' - ' . (($item['status'] ?? null) ?: 'fail');

                $message = implode("\n", $content);
            }
        }
        echo  $message . PHP_EOL;
        exit($status);
    }
public function _check_interkassa($options = null)
    public function _check_interkassa($options = null)
    {
        $_is_processing_interkassa = $_response = $_operation_id = null;
        // check operation
        $operation = $this->_operation($options);
        // import options
        is_array($operation) && extract($operation, EXTR_PREFIX_ALL | EXTR_REFS, '');
        if (empty($_is_valid)) {
            return  $this->_user_message($operation);
        }
        if ( ! $_is_processing_interkassa) {
            $result = [
                'status_message' => 'This operation is not processed by Interkassa',
            ];
            return  $result;
        }
        // var
        $payment_api = &$this->payment_api;
        // response
        $response = @end($_response);
        if (empty($response)) {
            $result = [
                'status_message' => 'Transaction not found',
            ];
            return  $result;
        }
        $id = &$response['data']['id'];
        if ( ! ($id ?? null) || $id < 1) {
            $result = [
                'status_message' => 'Transaction ID not found',
            ];
            return  $result;
        }
        // provider interkassa
        $provider_class = $payment_api->provider_class([
            'provider_name' => 'interkassa',
        ]);
        if (empty($provider_class)) {
            $result = [
                'status_message' => 'Interkassa provider is not available',
            ];
            return  $result;
        }
        // check transaction
        $request_option = [
            'method_id' => 'withdraw-id',
            'id' => $id,
        ];
        list($status, $result) = $provider_class->api_request($request_option);
        if (empty($status)) {
            $result = [
                'status_message' => 'Unable to check the transaction',
            ];
            return  $result;
        }
        // check status
        $data = ($result['data'] ?? null);
        // check status
        $state = (int) $data['state'];
        $result = $this->status_interkassa([
            'operation_id' => $_operation_id,
            'state' => $state,
            'data' => $data,
        ]);
        return  $result;
    }
public function _confirmation_update_expired()
    public function _confirmation_update_expired()
    {
        // var
        $payment_api = _class('payment_api');
        // update status only in_progress
        $object = $payment_api->get_status(['name' => 'confirmation']);
        list($status_id, $status) = $object;
        if (empty($status_id)) {
            return  $object;
        }
        $object = $payment_api->get_status(['name' => 'expired']);
        list($new_status_id, $new_status) = $object;
        if (empty($new_status_id)) {
            return  $object;
        }
        // time expired
        $ts = strtotime($payment_api->CONFIRMATION_TIME);
        $sql_datetime_over = $payment_api->sql_datetime($ts);
        $sql_datetime = $payment_api->sql_datetime();
        $db = db()->table('payment_operation')->select('operation_id')
            ->where('status_id', '=', $status_id)
            ->where('direction', '=', 'out')
            ->where('datetime_update', '<', $sql_datetime_over)
            ->where_null('datetime_finish');
        // get items
        $items = @$db->get_all();
        $db_error = $db->db->_last_query_error;
        if ($items === false && is_array($db_error)) {
            return  null;
        }
        if ( ! $items) {
            return  true;
        }
        // processing
        $result = true;
        foreach ($items as $idx => $item) {
            $operation_id = (int) $item['operation_id'];
            $r = $payment_api->expired([
                'operation_id' => $operation_id,
            ]);
            $result &= ($r['status'] ?? null);
        }
        return  $result;
    }
public function _confirmation_update_expired_cli()
    public function _confirmation_update_expired_cli()
    {
        $result = $this->_confirmation_update_expired();
        if (empty($result)) {
            $status = 1;
            $message = 'Update is fail';
        } else {
            $status = 0;
            $message = 'Update is success';
        }
        echo  $message . PHP_EOL;
        exit($status);
    }
public function _filter_form_show($filter, $replace)
    public function _filter_form_show($filter, $replace)
    {
        $order_fields = [
            'o.operation_id' => 'Operation ID',
            'o.amount' => 'Amount',
            'o.balance' => 'Balance',
            'o.datetime_start' => 'Created',
            'o.datetime_update' => 'Updated',
        ];
        $payment_api = &$this->payment_api;
        $providers = $payment_api->provider();
        $providers__select_box = [];
        foreach ($providers as $id => $item) {
            $providers__select_box[$id] = $item['title'];
        }
        $payment_status = $payment_api->get_status();
        $payment_status__select_box = [];
        $payment_status__select_box[-1] = 'ALL STATUSES';
        foreach ($payment_status as $id => $item) {
            $payment_status__select_box[$id] = $item['title'];
        }
        $min_date = from('payment_operation')->one('UNIX_TIMESTAMP(MIN(datetime_start))');
        return form($replace, [
                'filter' => true,
                'selected' => $filter,
            ])
            ->daterange('datetime_start', [
                'format' => 'YYYY-MM-DD',
                'min_date' => date('Y-m-d', $min_date ?: (time() - 86400 * 30)),
                'max_date' => date('Y-m-d', time() + 86400),
                'autocomplete' => 'off',
                'desc' => 'Created',
                'no_label' => 1,
            ])
            ->daterange('datetime_update', [
                'format' => 'YYYY-MM-DD',
                'min_date' => date('Y-m-d', $min_date ?: (time() - 86400 * 30)),
                'max_date' => date('Y-m-d', time() + 86400),
                'autocomplete' => 'off',
                'desc' => 'Updated',
                'no_label' => 1,
            ])
            ->daterange('datetime_finish', [
                'format' => 'YYYY-MM-DD',
                'min_date' => date('Y-m-d', $min_date ?: (time() - 86400 * 30)),
                'max_date' => date('Y-m-d', time() + 86400),
                'autocomplete' => 'off',
                'desc' => 'Finished',
                'no_label' => 1,
            ])
            ->text('name', 'User name or ID', ['no_label' => 1])
            ->text('title', 'Operation title, ID or details', ['no_label' => 1])
            ->row_start()
                ->number('amount', 'Amount from')
                ->number('amount__and', 'Amount to')
            ->row_end()
            ->row_start()
                ->number('balance', 'Balance from')
                ->number('balance__and', 'Balance to')
            ->row_end()
            ->select_box('status_id', $payment_status__select_box, ['no_label' => 1, 'show_text' => '= Status =', 'desc' => 'Status'])
            ->select_box('provider_id', $providers__select_box, ['no_label' => 1, 'show_text' => '= Provider =', 'desc' => 'Provider'])
            ->row_start()
                ->select_box('order_by', $order_fields, ['show_text' => '= Sort by =', 'desc' => 'Sort by'])
                ->select_box('order_direction', ['asc' => '⇑', 'desc' => '⇓'])
            ->row_end()
            ->save_and_clear();
    }
public function _get_daily_data($days = null)
    public function _get_daily_data($days = null)
    {
        $time = time();
        $days = $days ?: 60;
        $min_time = $time - $days * 86400;
        $data = [];
        $sql = select('FROM_UNIXTIME(UNIX_TIMESTAMP(datetime_start), "%Y-%m-%d") AS day', 'COUNT(*) AS count')
            ->from('payment_operation')->where('datetime_start', '>', $min_time)
            ->group_by('FROM_UNIXTIME(UNIX_TIMESTAMP(datetime_start), "%Y-%m-%d")')
            ->where('direction', '=', 'out');
        foreach ((array) $sql->all() as $a) {
            $data[$a['day']] = $a['count'];
        }
        if ( ! $data) {
            return false;
        }
        $dates = [];
        foreach (range($days, 0) as $days_ago) {
            $date = date('Y-m-d', $time - $days_ago * 86400);
            $dates[$date] = $days_ago;
        }
        $out = [];
        $_data = null;
        foreach ($dates as $date => $days_ago) {
            $_data = $data[$date];
            // Trim empty values from left side
            if ( ! $out && ! $_data) {
                continue;
            }
            $out[$date] = $_data;
        }
        // Trim values from the right side too
        foreach (array_reverse($out, $preserve_keys = true) as $k => $v) {
            if ($v) {
                break;
            }
            unset($out[$k]);
        }
        return $out;
    }
public function _get_operation_id($options = null)
    public function _get_operation_id($options = null)
    {
        // import operation
        is_array($options) && extract($options, EXTR_PREFIX_ALL | EXTR_REFS, '');
        // var
        $result = ($_operation_id ?? null) ?: ($_GET['operation_id'] ?? null) ?: ($_POST['operation_id'] ?? null);
        if ( ! ($result ?? null)) {
            $result = [
                'status_message' => 'Invalid operation',
            ];
            return  $this->_user_message($result);
        }
        return  $result;
    }

_http_csv

public function _http_csv($options = null)
    public function _http_csv($options = null)
    {
        $_data = $_file_name = null;
        // import options
        is_array($options) && extract($options, EXTR_PREFIX_ALL | EXTR_REFS, '');
        if ( ! is_array($_data) || empty($_file_name)) {
            $result = [
                'status' => false,
                'status_header' => 'Export to CSV',
                'status_message' => 'No data',
            ];
            return  $result;
        }
        // start
        ob_start();
        $result = $this->_save_csv('php://output', $_data);
        $csv = ob_get_clean();
        if (empty($result['status'])) {
            return  $result;
        }
        if ( ! empty($_debug)) {
            return  $csv;
        }
        header('Content-type: text/csv');
        header('Content-disposition: attachment; filename=' . $_file_name);
        echo $csv;
        if (($_is_return ?? null) === false) {
            exit;
        }
        $result = [
            'csv' => $csv,
            'status' => true,
            'status_header' => 'Export to CSV',
            'status_message' => 'Done',
        ];
        return  $result;
    }

_init

public function _init()
    public function _init()
    {
        // class
        $this->payment_api = _class('payment_api');
        $this->manage_payment_lib = module('manage_payment_lib');
        // property
        $object = &$this->object;
        $action = &$this->action;
        $id = &$this->id;
        $filter_name = &$this->filter_name;
        $filter = &$this->filter;
        $url = &$this->url;
        // setup property
        $object = ($_GET['object'] ?? null);
        $action = ($_GET['action'] ?? null);
        $id = ($_GET['id'] ?? null);
        $filter_name = $object . '__' . $action;
        $filter = ($_SESSION[$filter_name] ?? null);
        // url
        $url = [
            'request' => url_admin([
                'object' => $object,
                'action' => 'request',
                'operation_id' => '%operation_id',
            ]),
            'request_interkassa' => url_admin([
                'object' => $object,
                'action' => 'request_interkassa',
                'operation_id' => '%operation_id',
            ]),
            'check_interkassa' => url_admin([
                'object' => $object,
                'action' => 'check_interkassa',
                'operation_id' => '%operation_id',
            ]),
            'check_all_interkassa' => url_admin([
                'object' => $object,
                'action' => 'check_all_interkassa',
            ]),
            'confirmation_update_expired' => url_admin([
                'object' => $object,
                'action' => 'confirmation_update_expired',
            ]),
            'expired' => url_admin([
                'object' => $object,
                'action' => 'expired',
                'operation_id' => '%operation_id',
            ]),
            'cancel' => url_admin([
                'object' => $object,
                'action' => 'cancel',
                'operation_id' => '%operation_id',
            ]),
            'recreate' => url_admin([
                'object' => $object,
                'action' => 'recreate',
                'operation_id' => '%operation_id',
            ]),
            'recreate_items' => url_admin([
                'object' => $object,
                'action' => 'recreate_items',
                'operation_id' => '%operation_id',
                'items' => '%items',
            ]),
            'status_processing' => url_admin([
                'object' => $object,
                'action' => 'status',
                'status' => 'processing',
                'operation_id' => '%operation_id',
            ]),
            'status_success' => url_admin([
                'object' => $object,
                'action' => 'status',
                'status' => 'success',
                'operation_id' => '%operation_id',
            ]),
            'status_refused' => url_admin([
                'object' => $object,
                'action' => 'status',
                'status' => 'refused',
                'operation_id' => '%operation_id',
            ]),
            'csv' => url_admin([
                'object' => $object,
                'action' => 'csv',
                'operation_id' => '%operation_id',
            ]),
            'csv_request' => url_admin([
                'object' => $object,
                'action' => 'csv_request',
                'operation_id' => '%operation_id',
            ]),
            'list' => url_admin([
                'object' => $object,
            ]),
            'view' => url_admin([
                'object' => $object,
                'action' => 'view',
                'operation_id' => '%operation_id',
            ]),
            'balance' => url_admin([
                'object' => 'manage_payment',
                'action' => 'balance',
                'user_id' => '%user_id',
                'account_id' => '%account_id',
            ]),
            'user' => url_admin([
                'object' => 'members',
                'action' => 'edit',
                'id' => '%user_id',
            ]),
        ];
    }

_mass_result

public function _mass_result($options = null)
    public function _mass_result($options = null)
    {
        // import operation
        is_array($options) && extract($options, EXTR_PREFIX_ALL | EXTR_REFS, '');
        // prepare
        $result = [
            'status_header' => &$_status_header,
            'status_message' => &$_status_message,
            'status' => &$_status,
            'operation_id' => -1,
        ];
        return  $this->_user_message($result);
    }

_operation

public function _operation($options = null)
    public function _operation($options = null)
    {
        $_operation_id = $o_account_id = $o_provider_id = $o_options = $o_status_id = $o_amount = $o_datetime_start = $o_datetime_update = $o_datetime_finish = null;
        // var
        $is_valid = true;
        $is_options = true;
        // import options
        is_array($options) && extract($options, EXTR_PREFIX_ALL | EXTR_REFS, '');
        // var
        $payment_api = &$this->payment_api;
        $manage_lib = &$this->manage_payment_lib;
        // check operation
        $operation_id = isset($_operation_id) ? $_operation_id : (int) ($_GET['operation_id'] ?? null);
        $operation = $payment_api->operation([
            'operation_id' => $operation_id,
        ]);
        if (empty($operation)) {
            $result = [
                'status' => false,
                'status_message' => 'Error: operation not found',
            ];
            return  $result;
        }
        // import operation
        is_array($operation) && extract($operation, EXTR_PREFIX_ALL | EXTR_REFS, 'o');
        // check account
        $account_result = $payment_api->get_account(['account_id' => $o_account_id]);
        if (empty($account_result)) {
            $result = [
                'status' => false,
                'status_message' => 'User account not found',
            ];
            return  $result;
        }
        list($account_id, $account) = $account_result;
        // check user
        $user_id = $account['user_id'];
        $user = user($user_id);
        if (empty($user)) {
            $result = [
                'status' => false,
                'status_message' => 'User not found: ' . $user_id,
            ];
            return  $result;
        }
        $online_users = _class('online_users', null, null, true);
        $user_is_online = $online_users->_is_online($user_id);
        // check provider
        $providers_user = $payment_api->provider();
        $payment_api->provider_options($providers_user, [
            'method_allow',
        ]);
        if (empty($providers_user[$o_provider_id])) {
            $result = [
                'status' => false,
                'status_message' => 'Unknown provider',
            ];
            return  $result;
        }
        $provider = &$providers_user[$o_provider_id];
        // providers by name
        $providers_user__by_name = [];
        foreach ($providers_user as &$item) {
            $provider_name = $item['name'];
            $providers_user__by_name[$provider_name] = &$item;
        }
        $provider_name = &$provider['name'];
        $provider_class = $payment_api->provider_class([
            'provider_name' => $provider_name,
        ]);
        if (empty($provider_class)) {
            $result = [
                'status_message' => 'Provider is not available: ' . $provider['title'],
            ];
            return  $result;
        }
        // check request
        if (
            empty($o_options['request'])
            || ! is_array($o_options['request'])
        ) {
            $is_options = false;
            $result = [
                'status_message' => 'Request parameters are missing',
            ];
            return  $result;
        }
        if ($is_options) {
            $request = reset($o_options['request']);
            // check method
            if (empty($request['options']['method_id'])) {
                $result = [
                    'status_message' => 'Payout method is missing',
                ];
                return  $result;
            }
            $method_id = $request['options']['method_id'];
            $method = $provider_class->api_method([
                'type' => 'payout',
                'method_id' => $method_id,
            ]);
            // detect card
            $card = ($request['options']['card'] ?? null);
            $result = $this->interkassa_detect_card([
                'card' => $card,
            ]);
            @list($card_method_id, $card_method) = $result;
            $html_card_title = null;
            if ($card_method_id) {
                $html_card_title = $card_method['title'];
            }
        }
        // check operation status
        $statuses = $payment_api->get_status();
        if (empty($statuses[$o_status_id])) {
            $result = [
                'status_message' => 'Unknown operation status: ' . $o_status_id,
            ];
            return  $result;
        }
        $o_status = $statuses[$o_status_id];
        // status css
        $status_name = $o_status['name'];
        $status_title = $o_status['title'];
        $css = $manage_lib->css_by_status([
            'status_name' => $status_name,
        ]);
        $html_status_title = $status_title;
        // is
        $is_refused = $o_status['name'] == 'refused';
        $is_expired = $o_status['name'] == 'expired';
        $is_progressed = $o_status['name'] == 'in_progress';
        $is_processing = $o_status['name'] == 'processing';
        $is_confirmation = $o_status['name'] == 'confirmation';
        $is_finish = ! ($is_progressed || $is_processing || $is_confirmation);
        $is_recreate = $is_refused || $is_expired;
        $is_payout_interkassa = (bool) $this->IS_PAYOUT_INTERKASSA && $card_method_id;
        // processing
        $processing = [];
        $is_processing_self = false;
        if (is_array($o_options['processing'])) {
            $processing_log = array_reverse($o_options['processing']);
            $processing = reset($processing_log);
            if (($processing['provider_name'] ?? null) && $processing['provider_name'] != $provider_name) {
                @list($processing_provider_id, $processing_provider) = $payment_api->get_provider([
                    'name' => $processing['provider_name'],
                ]);
                if ($is_processing && $processing_provider) {
                    $html_status_title = $status_title . ' (' . $processing_provider['title'] . ')';
                }
            } else {
                $is_processing_self = $is_processing;
            }
        }
        $is_confirmation && $is_processing_self = true;
        $html_status_title = sprintf('<span class="%s">%s</span>', $css, $html_status_title);
        $is_processing_interkassa = $is_processing && $processing['provider_name'] == 'interkassa';
        $is_processing_administration = $is_processing && $processing['provider_name'] == 'administration';
        // check response
        $response = null;
        if (
            ! empty($o_options['response'])
            && is_array($o_options['response'])
        ) {
            $response = $o_options['response'];
        }
        // misc
        $html_amount = $payment_api->money_html($o_amount);
        $html_datetime_start = $o_datetime_start;
        $html_datetime_update = $o_datetime_update;
        $html_datetime_finish = $o_datetime_finish;
        // result
        $result = [
            'is_valid' => &$is_valid,
            'is_options' => &$is_options,
            'operation_id' => &$operation_id,
            'operation' => &$operation,
            'processing_log' => &$processing_log,
            'processing' => &$processing,
            'statuses' => &$statuses,
            'status' => &$o_status,
            'status_id' => &$o_status_id,
            'status_name' => &$status_name,
            'status_title' => &$status_title,
            'html_status_title' => &$html_status_title,
            'account_id' => &$account_id,
            'account' => &$account,
            'user_id' => &$user_id,
            'user' => &$user,
            'user_is_online' => &$user_is_online,
            'provider_id' => &$o_provider_id,
            'provider' => &$provider,
            'provider_name' => &$provider_name,
            'provider_class' => &$provider_class,
            'providers_user' => &$providers_user,
            'providers_user__by_name' => &$providers_user__by_name,
            'request' => &$request,
            'method_id' => &$method_id,
            'method' => &$method,
            'card_method_id' => &$card_method_id,
            'card_method' => &$card_method,
            'html_card_title' => &$html_card_title,
            'response' => &$response,
            'is_refused' => &$is_refused,
            'is_expired' => &$is_expired,
            'is_progressed' => &$is_progressed,
            'is_processing' => &$is_processing,
            'is_confirmation' => &$is_confirmation,
            'is_processing_self' => &$is_processing_self,
            'is_processing_administration' => &$is_processing_administration,
            'is_processing_interkassa' => &$is_processing_interkassa,
            'is_payout_interkassa' => &$is_payout_interkassa,
            'is_finish' => &$is_finish,
            'is_recreate' => &$is_recreate,
            'html_amount' => &$html_amount,
            'html_datetime_start' => &$html_datetime_start,
            'html_datetime_update' => &$html_datetime_update,
            'html_datetime_finish' => &$html_datetime_finish,
        ];
        return  $result;
    }

_request

public function _request($options = null)
    public function _request($options = null)
    {
        $_request = $_operation_id = $_provider_class = null;
        // start
        db()->begin();
        // check operation
        $operation = $this->_operation($options);
        // import options
        is_array($operation) && extract($operation, EXTR_PREFIX_ALL | EXTR_REFS, '');
        if (empty($_is_valid)) {
            return  $operation;
        }
        // is_processing
        if ($_is_processing ?? null) {
            $result = [
                'status_message' => 'The operation is already being processed',
            ];
            return  $result;
        }
        // is_progressed
        if ( ! ($_is_progressed ?? null)) {
            $result = [
                'status_message' => 'The operation cannot be processed because its status has changed',
            ];
            return  $result;
        }
        // var
        $payment_api = &$this->payment_api;
        $data = $_request['options'] + [
            'operation_id' => $_operation_id,
        ];
        $result = $_provider_class->api_payout($data);
        $result['operation_id'] = $_operation_id;
        if (($result['status'] ?? null) == 'success') {
            // processing
            $_provider_class->_update_status([
                'operation_id' => $_operation_id,
                'name' => 'processing',
            ]);
        }
        // finish
        db()->commit();
        return  $result;
    }

_save_csv

public function _save_csv($file_name, $data = null)
    public function _save_csv($file_name, $data = null)
    {
        if (is_array($data) && ($file = fopen($file_name, 'w')) !== false) {
            foreach ($data as $id => $item) {
                if ( ! is_array($item)) {
                    fclose($file);
                    $result = [
                        'status' => false,
                        'status_header' => 'Export to CSV',
                        'status_message' => 'An array is required, invalid data: ' . $item,
                    ];
                    return  $result;
                }
                $_data = array_values($item);
                $result = fputcsv($file, $_data, ';', '"', '\\');
                if (false === $result) {
                    fclose($file);
                    $result = [
                        'status' => false,
                        'status_header' => 'Export to CSV',
                        'status_message' => 'Data conversion error: ' . $_data,
                    ];
                    return  $result;
                }
            }
            fclose($file);
            $result = [
                'status' => true,
            ];
        } else {
            $result = [
                'status' => false,
                'status_header' => 'Export to CSV',
                'status_message' => 'Error opening the data stream',
            ];
        }
        return  $result;
    }

_show_filter

public function _show_filter()
    public function _show_filter()
    {
        $object = &$this->object;
        $action = &$this->action;
        $filter_name = &$this->filter_name;
        $filter = &$this->filter;
        if ( ! in_array($action, ['show'])) {
            return  false;
        }
        // url
        $url_base = [
            'object' => $object,
            'action' => 'filter_save',
            'id' => $filter_name,
        ];
        $result = '';
        switch ($action) {
            case 'show':
                $url_filter = url_admin($url_base);
                $url_filter_clear = url_admin($url_base + [
                    'page' => 'clear',
                ]);
                $replace = [
                    'form_action' => $url_filter,
                    'clear_url' => $url_filter_clear,
                ];
                $result = $this->_filter_form_show($filter, $replace);
                break;
        }
        return  $result;
    }
public function _show_quick_filter()
    public function _show_quick_filter()
    {
        $a = [];
        $status_names = from('payment_status')->get_2d('status_id, title');
        $count_by_status = select(['status_id', 'COUNT(*) AS num'])->from('payment_operation')
            ->where('direction', '=', 'out')->group_by('status_id')->get_2d();
        $statuses_display = [
            1 => 'text-warning',
            2 => 'text-success',
            5 => 'text-warning',
            3 => 'text-danger',
            6 => 'text-danger',
            4 => 'text-muted',
            7 => 'text-info',
        ];
        foreach ((array) $statuses_display as $status_id => $css_class) {
            if ($count_by_status[$status_id]) {
                $name = $status_names[$status_id];
                $a[] = a('/@object/filter_save/clear/?filter=status_id:' . $status_id, $name, 'fa fa-filter', $name, $css_class, '');
            }
        }
        $a[] = a('/@object/filter_save/clear/', 'Clear filter', 'fa fa-close', '', '', '');
        return $a ? '<div class="pull-right">' . implode(PHP_EOL, $a) . '</div>' : '';
    }

_status

public function _status($options = null)
    public function _status($options = null)
    {
        $__status = $_provider_class = $_operation_id = $_user_id = $_operation = null;
        // check operation
        $operation = $this->_operation($options);
        // import options
        is_array($options) && extract($options, EXTR_PREFIX_ALL | EXTR_REFS, '_');
        is_array($operation) && extract($operation, EXTR_PREFIX_ALL | EXTR_REFS, '');
        if (empty($_is_valid)) {
            return  $this->_user_message($operation);
        }
        $status = ($_GET['status'] ?? null) ?: $__status;
        switch ($status) {
            case 'success':
                $result = $_provider_class->_payout_success([
                    'operation_id' => $_operation_id,
                ]);
                $mail_tpl = 'payout_success';
                break;
            case 'refused':
                $result = $_provider_class->_payout_refused([
                    'operation_id' => $_operation_id,
                ]);
                $mail_tpl = 'payout_refused';
                break;
            case 'processing':
                $result = $_provider_class->_payout_processing([
                    'operation_id' => $_operation_id,
                ]);
                break;
        }
        if (empty($result['status'])) {
            $result['operation_id'] = $_operation_id;
            return  $result;
        }
        // mail
        $payment_api = &$this->payment_api;
        ($mail_tpl ?? null) && $payment_api->mail([
            'tpl' => $mail_tpl,
            'user_id' => $_user_id,
            'admin' => true,
            'data' => [
                'operation_id' => $_operation_id,
                'amount' => $_operation['amount'],
            ],
        ]);
        $url_view = $this->_url('view', ['%operation_id' => $_operation_id]);
        if ( ! empty($__status_message)) {
            return  $options;
        }
        return  js_redirect($url_view, false);
    }

_url

public function _url($name, $replace = null)
    public function _url($name, $replace = null)
    {
        $url = &$this->url;
        $result = null;
        if (empty($url[$name])) {
            return  $result;
        }
        if ( ! is_array($replace)) {
            return  $url[$name];
        }
        $result = str_replace(array_keys($replace), array_values($replace), $url[$name]);
        return  $result;
    }