yf_payment_api__provider_ecommpay class

Fileplugins/payment/classes/yf_payment_api__provider_ecommpay.class.php
Use_class('payment_api__provider_ecommpay')
Not loaded by the docs: it runs code outside its class (_class on line 3). Read the source instead.

<?php

_class('payment_api__provider_remote');

class yf_payment_api__provider_ecommpay extends yf_payment_api__provider_remote
{
    public $URL = 'https://terminal.ecommpay.com/';
    public $URL_TEST = 'https://terminal-sandbox.ecommpay.com/';

    public $URL_API = 'https://gate.ecommpay.com/%method/json/';
    public $URL_API_TEST = 'https://gate-sandbox.ecommpay.com/%method/json/';
    public $URL_BIN_INFO = 'https://bins.payout.com/api/v1/bins/'; //get Bank info by first 6 digits card number, free request daily limit = 1000
    public $ALLOW_CARDS_COUNTRIES = [];//['UA'=>'Ukraine','US'=>'USA'] or empty array if every country allow

    public $method_allow = [
        'order' => [
            'payin' => [
                'ecommpay',
            ],
            'payout' => [
                'pay_card',
            ],
        ],
        'payin' => [
            'ecommpay' => [
                'title' => 'Visa, MasterCard',
                'icon' => 'ecommpay',
                'amount_min' => 100,
                'fee' => 0, // 0.1%
                'currency' => [
                    'USD' => [
                        'currency_id' => 'USD',
                        'active' => true,
                    ],
                ],
            ],
            'card' => [
                'title' => 'Visa, MasterCard',
                'icon' => 'visa-mastercard',
                'option' => [
                    // 'payment_group_id' => 1,
                    // 'followup'         => 0,
                ],
                'amount_min' => 100,
                'fee' => 0, // 0.1%
                'currency' => [
                    'USD' => [
                        'currency_id' => 'USD',
                        'active' => true,
                    ],
                ],
            ],
            'c24' => [
                'title' => 'C24',
                'icon' => 'c24',
                'option' => [
                    // 'payment_group_id' => 28,
                    // 'followup'         => 1,
                    // 'external_payment_type_id' => 'qw',
                ],
                'amount_min' => 100,
                'fee' => 0, // 0.1%
                'currency' => [
                    'USD' => [
                        'currency_id' => 'USD',
                        'active' => true,
                    ],
                ],
            ],
            'comepay' => [
                'title' => 'Comepay',
                'icon' => 'comepay',
                'option' => [
                    // 'payment_group_id' => 29,
                    // 'followup'         => 0,
                    // 'external_payment_type_id' => 'qw',
                ],
                'amount_min' => 100,
                'fee' => 0, // 0.1%
                'currency' => [
                    'USD' => [
                        'currency_id' => 'USD',
                        'active' => true,
                    ],
                ],
            ],
            'mobile_commerce' => [
                'title' => 'Mobile commerce',
                'icon' => 'mobile-commerce',
                'option' => [
                    // 'payment_group_id' => 7,
                    // 'followup'         => 0,
                    // 'phone' => '1231234567',
                ],
                'amount_min' => 100,
                'fee' => 0, // 0.1%
                'currency' => [
                    'USD' => [
                        'currency_id' => 'USD',
                        'active' => true,
                    ],
                ],
            ],
        ],
        'payout' => [
            'pay_card' => [
                'title' => 'Visa, MasterCard',
                'icon' => 'visa-mastercard',
                'uri' => [
                    '%method' => 'card',
                ],
                'action' => 'payout',
                'amount' => [
                    // 'min' => 10,
                    'max' => 150,
                ],
                // 'fee'        => 0, // 0.1%
                'currency' => [
                    'USD' => [
                        'currency_id' => 'USD',
                        'is_int' => true,
                        'active' => true,
                    ],
                ],
                'field' => [
                    'action',
                    'site_id',
                    'amount',
                    'currency',
                    'external_id',
                    // 'customer_ip',
                    'comment',
                    'card',
                    'sender_first_name',
                    'sender_last_name',
                    'sender_middle_name',
                    'sender_passport_number',
                    'sender_passport_issue_date',
                    'sender_passport_issued_by',
                    'sender_phone',
                    'sender_birthdate',
                    'sender_address',
                    'sender_city',
                    'sender_postindex',
                ],
                'order' => [
                    'card',
                    'sender_first_name',
                    'sender_last_name',
                    'sender_middle_name',
                    'sender_passport_number',
                    'sender_passport_issue_date',
                    'sender_passport_issued_by',
                    'sender_phone',
                    'sender_birthdate',
                    'sender_address',
                    'sender_city',
                    'sender_postindex',
                ],
                'option' => [
                    'card' => 'Card number',
                    'sender_first_name' => 'First name',
                    'sender_last_name' => 'Last name',
                    'sender_middle_name' => 'Middle name',
                    'sender_passport_number' => 'Passport series and number',
                    'sender_passport_issue_date' => 'Passport issue date',
                    'sender_passport_issued_by' => 'Passport issued by',
                    'sender_phone' => 'Contact phone',
                    'sender_birthdate' => 'Date of birth',
                    'sender_address' => 'Address',
                    'sender_city' => 'City',
                    'sender_postindex' => 'Postal code',
                ],
                'option_validation_js' => [
                    'card' => [
                        'type' => 'text',
                        'required' => true,
                        'minlength' => 13,
                        'maxlength' => 19,
                        'pattern' => '^\d+$',
                    ],
                    'sender_first_name' => [
                        'type' => 'text',
                        'required' => true,
                        'minlength' => 2,
                        'maxlength' => 256,
                        'pattern' => '^[a-zA-Zа-яА-Я\s\.\-]+$',
                    ],
                    'sender_last_name' => [
                        'type' => 'text',
                        'required' => true,
                        'minlength' => 2,
                        'maxlength' => 256,
                        'pattern' => '^[a-zA-Zа-яА-Я\s\.\-]+$',
                    ],
                    'sender_middle_name' => [
                        'type' => 'text',
                        'required' => true,
                        'minlength' => 2,
                        'maxlength' => 256,
                        'pattern' => '^[a-zA-Zа-яА-Я\s\.\-]+$',
                    ],
                    'sender_passport_number' => [ // only Russian
                        'type' => 'text',
                        'required' => true,
                        'minlength' => 8,
                        'maxlength' => 10,
                        'pattern' => '^[\wа-яА-Я]+$',
                    ],
                    'sender_passport_issue_date' => [
                        'type' => 'date',
                        'required' => true,
                        // 'pattern'   => '^\d{4}\-\d{1,2}\-\d{1,2}$',
                    ],
                    'sender_passport_issued_by' => [
                        'type' => 'text',
                        'required' => true,
                        'minlength' => 2,
                        'maxlength' => 256,
                        'pattern' => '^[\wа-яА-Я\s\,\.\-\\/\#№]+$',
                    ],
                    'sender_phone' => [ // only Russian
                        'type' => 'text',
                        'required' => true,
                        'minlength' => 11,
                        'maxlength' => 15,
                        'pattern' => '^\d+$',
                    ],
                    'sender_birthdate' => [
                        'type' => 'date',
                        'required' => true,
                        // 'pattern'   => '^\d{4}\-\d{1,2}\-\d{1,2}$',
                    ],
                    'sender_address' => [
                        'type' => 'text',
                        'required' => true,
                        'minlength' => 2,
                        'maxlength' => 256,
                        'pattern' => '^[\wа-яА-Я\s\,\.\-\\/\#№]+$',
                    ],
                    'sender_city' => [
                        'type' => 'text',
                        'required' => true,
                        'minlength' => 2,
                        'maxlength' => 256,
                        'pattern' => '^[a-zA-Zа-яА-Я\s\-\.]+$',
                    ],
                    'sender_postindex' => [
                        'type' => 'text',
                        'required' => true,
                        'minlength' => 5,
                        'maxlength' => 6,
                        'pattern' => '^[\d]+$',
                    ],
                ],
                'option_validation' => [
                    'card' => 'required|is_natural|length[13,19]',
                    'sender_first_name' => 'required|regex:~^[\pL\pM\s\.\-]+$~u|length[2,256]',
                    'sender_last_name' => 'required|regex:~^[\pL\pM\s\.\-]+$~u|length[2,256]',
                    'sender_middle_name' => 'required|regex:~^[\pL\pM\s\.\-]+$~u|length[2,256]',
                    'sender_passport_number' => 'required|regex:~^[\pL\pM\pN]+$~u|length[8,10]',
                    'sender_passport_issue_date' => 'required|valid_date_format[Y-m-d]',
                    'sender_passport_issued_by' => 'required|regex:~^[\pL\pM\pN\s\,\.\-\\/\#№]+$~u|length[2,256]',
                    'sender_phone' => 'required|is_natural|length[11,15]',
                    'sender_birthdate' => 'required|valid_date_format[Y-m-d]',
                    'sender_address' => 'required|regex:~^[\pL\pM\pN\s\,\.\-\\/\#]+$~u|length[2,256]',
                    'sender_city' => 'required|regex:~^[\pL\pM\s\-\.]+$~u|length[2,256]',
                    'sender_postindex' => 'required|is_natural|length[5,6]',
                ],
                'option_validation_message' => [
                    'card' => 'required, 13 to 19 digits',
                    'sender_first_name' => 'required, at least 2 characters',
                    'sender_last_name' => 'required, at least 2 characters',
                    'sender_middle_name' => 'required, at least 2 characters',
                    'sender_passport_number' => 'required, 8 to 10 characters',
                    'sender_passport_issue_date' => 'required, date "year-month-day" (example: 1999-01-22)',
                    'sender_passport_issued_by' => 'required, at least 2 characters',
                    'sender_phone' => 'required, at least 11 digits, without "+"',
                    'sender_birthdate' => 'required, date "year-month-day" (example: 1977-01-22)',
                    'sender_address' => 'required, at least 2 characters',
                    'sender_city' => 'required, at least 2 characters',
                    'sender_postindex' => 'required, at least 5 digits',
                ],
            ],
        ],
    ];

    public $_api_transform = [
        // '+' - required
        // '-' - not required
        // '?' - may be by conditions
        // 'amount'                 => 'amount',                     // + Numeric Payout amount in the site currency
        'currency' => 'currency',                   // - Enum Currency
        'operation_id' => 'external_id',                // + String Order id in the merchant's system
        'transaction_id' => 'transaction_id',             // ? Numeric ID of a successful payment with the bank card the payout goes to.
        'force_disable_callback' => 'force_disable_callback',     // - Enum Disable notifications (callback) about the operation. Values: 1 (yes, disable), 0 (no, send notifications)
        'first_callback_delay' => 'first_callback_delay',       // - Numeric Delay before the first notification
        'account' => 'card',                       // ? Numeric(13,19) Number of the bank card the payout goes to.
        'title' => 'comment',                    // + String(4096) Request comment example_comment
        'first_name' => 'sender_first_name',          // + String(255) User first name
        'last_name' => 'sender_last_name',           // + String(255) User last name
        'middle_name' => 'sender_middle_name',         // + String(255) User middle name
        'passport_number' => 'sender_passport_number',     // + String(255) User passport series and number
        'passport_issue_date' => 'sender_passport_issue_date', // + String(255) Passport issue date: 2002-01-31
        'passport_issued_by' => 'sender_passport_issued_by',  // + String(255) Passport issuing authority
        'phone' => 'sender_phone',               // + String(11) User contact phone.
        'birthdate' => 'sender_birthdate',           // + Date User date of birth: 1980-01-31
        'address' => 'sender_address',             // + String(255) User address
        'city' => 'sender_city',                // ? String(255) User city.
        'postindex' => 'sender_postindex',           // ? String(255) User postal code.
    ];

    public $_api_transform_reverse = [
        'external_id' => 'operation_id',
        'code' => 'state',
        'card' => 'account',
    ];

    public $_options_transform = [
        'title' => 'description',
        'operation_id' => 'external_id',
        'public_key' => 'site_id',
        'key_public' => 'site_id',
        'test' => 'test_mode',
    ];

    public $_options_transform_reverse = [
        'description' => 'title',
        'external_id' => 'operation_id',
        'site_id' => 'key_public',
    ];

    public $_status_response = [
        '1' => 'success',
        '2' => 'refused',
    ];
    public $_status_server = [
        '1' => 'processing', // initiated
        '2' => 'processing', // external processing
        '3' => 'processing', // awaiting confirmation
        '4' => 'success',    // success
        '5' => 'refused',    // void
        '6' => 'refused',    // processor decline
        '7' => 'refused',    // fraudstop decline
        '8' => 'refused',    // mpi decline
        '9' => 'refused',
        '10' => 'refused',    // system failure
        '11' => 'refused',    // unsupported protocol operation
        '12' => 'refused',    // protocol configuration error
        '13' => 'expired',    // transaction is expired
        '14' => 'refused',    // transaction rejected by user
        '15' => 'refused',    // internal decline
    ];

    public $_type_server = [
        // deposition         payout
        // 1 (authorization)  4  (void)
        // 3 (purchase)       5  (refund)
        // 6 (rebill)         11 (payout)
        '1' => 'deposition',     // authorization Authorization
        // '2'  => 'payment', // confirm Authorization confirmation
        '3' => 'deposition',     // purchase Direct charge
        '4' => 'payment', // void Authorization cancellation
        '5' => 'payment', // refund Refund
        '6' => 'deposition',     // rebill Recurring payment
        // '7'  => 'payment', // chargeback Payment dispute
        // '8'  => 'payment', // complete3ds 3DS payment completion
        // '9'  => 'payment',
        // '10' => 'payment',
        '11' => 'payment', // payout Payout
    ];

    public $currency_default = 'USD';
    public $currency_allow = [
        'USD' => [
            'currency_id' => 'USD',
            'active' => true,
        ],
        /* 'EUR' => array(
            'currency_id' => 'EUR',
            'active'      => true,
        ),
        'UAH' => array(
            'currency_id' => 'UAH',
            'active'      => true,
        ), */
    ];

    // public $fee = 5; // 5%

    public $service_allow = [
        'Visa, MasterCard',
    ];

    public $ip_filter = [
        '78.140.183.154' => true,
        '204.26.61.98' => true,
    ];

    public $url_result = null;
    public $url_server = null;

    public function _init()
    {
        if ( ! $this->ENABLE) {
            return  null;
        }
        // load api
        require_once __DIR__ . '/payment_provider/ecommpay/EcommPay.php';
        $this->api = new EcommPay($this->KEY_PUBLIC, $this->KEY_PRIVATE);
        $this->url_result = url_user('/api/payment/provider?name=ecommpay&operation=response');
        $this->url_server = url_user('/api/payment/provider?name=ecommpay&operation=response&server=true');
        // translation
        $strs = &$this->method_allow['payout']['pay_card']['option'];
        foreach ($strs as $key => &$str) {
            $str = t($str);
        }
        // payout default option
        $user_id = main()->USER_ID;
        if ($user_id) {
            $user = user($user_id);
            $option_default = &$this->method_allow['payout']['pay_card']['option_default'];
            $option_default = [
                'card' => $user['card'],
                'sender_first_name' => $user['first_name'],
                'sender_last_name' => $user['last_name'],
                'sender_middle_name' => ($user['middle_name'] ?? null) ?: $user['patronymic'],
                'sender_passport_number' => $user['passport_num'],
                'sender_passport_issue_date' => $user['passport_issue_date'],
                'sender_passport_issued_by' => ($user['passport_issued_by'] ?? null) ?: $user['passport_released'],
                'sender_phone' => @str_replace([' ', '-', '+'], '', $user['phone']),
                'sender_birthdate' => ($user['birthdate'] ?? null) ?: $user['birth_date'],
                'sender_address' => ($user['address'] ?? null) ?: $user['address2'],
                'sender_city' => ($user['city'] ?? null) ?: $user['city2'],
                'sender_postindex' => ($user['zip_code'] ?? null) ?: $user['zip_code2'],
            ];
        }


        if (count((array) $this->ALLOW_CARDS_COUNTRIES)) {
            $countries = [];
            foreach ($this->ALLOW_CARDS_COUNTRIES as $country) {
                $countries[] = t($country);
            }
            $countries_validation_message = 'card must be issued in ' . implode(', ', $countries);
            $this->method_allow['payout']['pay_card']['option_validation_message']['card'] .= ', ' . $countries_validation_message;
            $this->method_allow['payout']['pay_card']['option_validation']['card'] = [
                '__before__' => 'required|is_natural|length[13,19]',
                '__after__' => function ($in, $tmp1 = null, $tmp2 = null, &$error_msg = null) use ($countries_validation_message) {
                    $bin = substr($in, 0, 6);
                    $url = $this->URL_BIN_INFO . $bin;
                    $request_result = common()->get_remote_page($url);

                    $request_data = ! empty($request_result) ? @json_decode($request_result, true) : false;
                    if ($request_data && isset($request_data['country_code'])) {
                        if (isset($this->ALLOW_CARDS_COUNTRIES[$request_data['country_code']])) {
                            return true;
                        }

                        $error_msg = $countries_validation_message;
                        return false;
                    }
                    return true;
                }, ];
        }

        // parent
        parent::_init();
    }

    public function key($name = 'public', $value = null)
    {
        if ( ! $this->ENABLE) {
            return  null;
        }
        $value = $this->api->key($name, $value);
        return  $value;
    }

    public function key_reset()
    {
        if ( ! $this->ENABLE) {
            return  null;
        }
        $this->key('public', $this->KEY_PUBLIC);
        $this->key('private', $this->KEY_PRIVATE);
    }

    public function signature($options, $request = true)
    {
        if ( ! $this->ENABLE) {
            return  null;
        }
        $result = $this->api->signature($options, $request);
        return  $result;
    }

    public function _amount($amount, $currency, $is_request = true)
    {
        if ( ! $this->ENABLE) {
            return  null;
        }
        $currency_id = $this->get_currency([
            'currency_id' => $currency,
        ]);
        if ($currency_id != $currency) {
            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;
        }
        $units = pow(10, $currency['minor_units']);
        if ($is_request) {
            $result = (int) ($amount * $units);
        } else {
            $result = (float) $amount / $units;
        }
        return  $result;
    }

    public function _form_options($options)
    {
        if ( ! $this->ENABLE) {
            return  null;
        }
        $_ = $options;
        // transform
        foreach ((array) $this->_options_transform as $from => $to) {
            if (isset($_[$from]) && $from != $to) {
                $_[$to] = $_[$from];
                unset($_[$from]);
            }
        }
        // url
        if ( ! empty($_['url_result'])
            || empty($_['success_url'])
        ) {
            $url = $this->_url($options);
            $_['success_url'] = $url . '&status=success';
            $_['callback_method'] = 4;
            unset($_['url_result']);
        }
        if ( ! empty($_['url_result'])
            || empty($_['decline_url'])
        ) {
            $url = $this->_url($options);
            $_['decline_url'] = $url . '&status=fail';
            $_['callback_method'] = 4;
            unset($_['url_result']);
        }
        unset($_['url_server']);
        // default
        empty($_['language']) && $_['language'] = 'en';
        // amount
        $_['amount'] = $this->_amount($_['amount'], $_['currency'], $is_request = true);
        // site id
        empty($_['site_id']) && $_['site_id'] = $this->KEY_PUBLIC;
        if (empty($_['amount']) || empty($_['site_id'])) {
            $_ = null;
        }
        return  $_;
    }

    public function _url($options, $is_server = false)
    {
        $_url_server = $_url_result = $_operation_id = null;
        if ( ! $this->ENABLE) {
            return  null;
        }
        is_array($options) && extract($options, EXTR_PREFIX_ALL | EXTR_REFS, '');
        if ($is_server) {
            $url = $_url_server ?: $this->url_server;
        } else {
            $url = $_url_result ?: $this->url_result;
        }
        $result = $url . '&operation_id=' . $_operation_id;
        return  $result;
    }

    public function _form($data, $options = null)
    {
        if ( ! $this->ENABLE) {
            return  null;
        }
        $_ = &$options;
        // START DUMP
        $payment_api = $this->payment_api;
        $payment_api->dump(['name' => 'EcommPay', 'operation_id' => @(int) $_['data']['operation_id']]);
        if (empty($data)) {
            return  null;
        }
        $is_array = (bool) $_['is_array'];
        $form_options = $this->_form_options($data);
        // DUMP
        $payment_api->dump(['var' => $form_options]);
        if (empty($form_options)) {
            return  null;
        }
        $signature = $this->api->signature($form_options);
        if (empty($signature)) {
            return  null;
        }
        $form_options['signature'] = $signature;
        if ( ! empty($this->TEST_MODE) || ! empty($_['test_mode'])) {
            $url = &$this->URL_TEST;
        } else {
            $url = &$this->URL;
        }
        $result = [];
        if ($is_array) {
            $result['url'] = $url;
        } else {
            $result[] = '<form id="_js_provider_ecommpay_form" method="post" accept-charset="utf-8" action="' . $url . '" class="display: none;">';
        }
        foreach ((array) $form_options as $key => $value) {
            if ($is_array) {
                $result['data'][$key] = $value;
            } else {
                $result[] = sprintf('<input type="hidden" name="%s" value="%s" />', $key, $value);
            }
        }
        if ( ! $is_array) {
            $result[] = '</form>';
            $result = implode(PHP_EOL, $result);
        }
        return  $result;
    }

    public function _api_response()
    {
        if ( ! $this->ENABLE) {
            return  null;
        }
        $payment_api = $this->payment_api;
        $test_mode = &$this->TEST_MODE;
        $is_server = ! empty($_GET['server']);
        $result = null;
        // check operation
        $operation_id = @(int) ($_GET['operation_id'] ?? null);
        // START DUMP
        $payment_api->dump(['name' => 'EcommPay', 'operation_id' => $operation_id]);
        // response
        $response = $_POST;
        // check signature
        isset($response['signature']) && $signature = $response['signature'];
        // check signature
        if (empty($signature)) {
            $result = [
                'status' => false,
                'status_message' => 'Empty signature',
            ];
            // DUMP
            $payment_api->dump(['var' => $result]);
            return  $result;
        }
        $signature_options = $response;
        $_signature = $this->signature($signature_options);
        if ($signature != $_signature) {
            $result = [
                'status' => false,
                'status_message' => 'Invalid signature',
            ];
            // DUMP
            $payment_api->dump(['var' => $result]);
            return  $result;
        }
        // user success or fail
        if ( ! $is_server) {
            // check status
            $state = isset($response['type']) && $response['type'] == '1' ? true : false;
            $status = isset($_GET['status'])
                && ($_GET['status'] ?? null) == 'success'
                && $state
                ? true : false;
            $status_message = $status ? 'Operation completed successfully' : 'Operation failed';
            $result = [
                'status' => $status,
                'status_message' => $status_message,
            ];
            return  $result;
        }
        // server notification
        // check ip
        $ip_allow = $this->_check_ip();
        if ($ip_allow === false) {
            // DUMP
            $payment_api->dump(['var' => 'ip not allow']);
            return  null;
        }
        // update operation
        $_response = $this->_response_parse($response);
        // check public key (site_id)
        $key_public = $_response['key_public'];
        $_key_public = $this->key('public');
        if ($key_public != $_key_public) {
            $result = [
                'status' => false,
                'status_message' => 'Invalid key (site_id)',
            ];
            // DUMP
            $payment_api->dump(['var' => $result]);
            return  $result;
        }
        // check operation_id
        if ($operation_id && $operation_id != $_response['operation_id']) {
            $result = [
                'status' => false,
                'status_message' => 'Operation ID does not match',
            ];
            // DUMP
            $payment_api->dump(['var' => $result]);
            return  $result;
        }
        // START NEW DUMP
        $operation_id = @(int) $_response['operation_id'];
        $payment_api->dump(['is_new' => true, 'name' => 'EcommPay', 'operation_id' => $operation_id]);
        // deposition or payout
        $state = $_response['type_id'];
        $status = $this->_type_server;
        list($payment_type) = $this->_state($state, $status);
        if (empty($payment_type)) {
            // DUMP
            $payment_api->dump(['var' => 'type: ' . $state]);
            return  null;
        }
        // check status
        $state = $_response['status_id'];
        $status = $this->_status_server;
        list($status_name, $status_message) = $this->_state($state, $status);
        // update account, operation data
        $operation_data = [
            'operation_id' => $operation_id,
            'provider_name' => 'ecommpay',
            'state' => $state,
            'status_name' => $status_name,
            'status_message' => $status_message,
            'payment_type' => $payment_type,
            'response' => $_response,
        ];
        // DUMP
        $payment_api->dump(['var' => ['payment_type' => $payment_type, 'update operation' => $operation_data]]);
        $result = $this->{ '_api_' . $payment_type }($operation_data);
        // DUMP
        $payment_api->dump(['var' => ['update result' => $result]]);
        return  $result;
    }

    public function _response_parse($response)
    {
        if ( ! $this->ENABLE) {
            return  null;
        }
        $_ = $response;
        // transform
        foreach ((array) $this->_options_transform_reverse as $from => $to) {
            if (isset($_[$from])) {
                $_[$to] = $_[$from];
                unset($_[$from]);
            }
        }
        return  $_;
    }

    public function api_payout($options = null)
    {
        $_method_id = $_amount = $_currency_id = $_external_id = $_sender_phone = null;
        if ( ! $this->ENABLE) {
            return  null;
        }
        // import options
        is_array($options) && extract($options, EXTR_PREFIX_ALL | EXTR_REFS, '');
        // method
        $method = $this->api_method([
            'type' => 'payout',
            'method_id' => $_method_id,
        ]);
        if (empty($method)) {
            $result = [
                'status' => false,
                'status_message' => 'Request method not found',
            ];
            return  $result;
        }
        $payment_api = &$this->payment_api;
        // operation_id
        $_operation_id = (int) $_operation_id;
        $operation_id = $_operation_id;
        if (empty($_operation_id)) {
            $result = [
                'status' => false,
                'status_message' => 'Operation ID is not defined',
            ];
            return  $result;
        }
        // currency_id
        $currency_id = $this->get_currency_payout($options);
        if (empty($currency_id)) {
            $result = [
                'status' => false,
                'status_message' => 'Unknown currency',
            ];
            return  $result;
        }
        // amount min/max
        $result = $this->amount_limit([
            'amount' => $_amount,
            'currency_id' => $currency_id,
            'method' => $method,
        ]);
        if ( ! ($result['status'] ?? null)) {
            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' => 'Unable to convert currencies',
            ];
            return  $result;
        }
        // fee
        $fee = $this->get_fee_payout($options);
        $amount_currency_total = $payment_api->fee($amount_currency, $fee);
        // transform
        foreach ($this->_api_transform as $from => $to) {
            $_from = '_' . $from;
            $_to = '_' . $to;
            $f = &${ $_from };
            $t = &${ $_to   };
            if (isset($f) && $_from != $_to) {
                $t = $f;
                unset(${ $_from });
            }
        }
        $_amount = $this->_amount_payout($_amount, $_currency_id, $method, $is_request = true);
        ! isset($_site_id) && $_site_id = $this->key('public');
        ! isset($_comment) && $_comment = t('Payout (id: ' . $_external_id . ')');
        ! isset($_action) && $_action = $method['action'];
        is_string($_sender_phone) && $_sender_phone = str_replace([' ', '-', '+'], '', $_sender_phone);
        // check required
        $request = [];
        foreach ($method['field'] as $key) {
            $value = @${ '_' . $key };
            if ( ! isset($value)) {
                $result = [
                    'status' => false,
                    'status_message' => 'Request data is missing: ' . $key,
                ];
                continue;
            }
            $request[$key] = &${ '_' . $key };
        }
        // signature
        $signature = $this->api->signature($request);
        if (empty($signature)) {
            $result = [
                'status' => false,
                'status_message' => 'Signature generation error',
            ];
            return  $result;
        }
        $request['signature'] = $signature;
        // START DUMP
        $payment_api->dump(['name' => 'EcommPay', 'operation_id' => $operation_id,
            'var' => ['request' => $request],
        ]);
        // url
        $object = $this->api_url($method, $options);
        if (is_array($object) && $object['status'] === false) {
            return  $object;
        }
        $url = $object;
        // request
        $request_options = [];
        ($_is_debug ?? null) && $request_options['is_debug'] = true;
        $result = $this->_api_request($url, $request, $request_options);
        // DUMP
        $payment_api->dump(['var' => ['response' => $result]]);
        if (empty($result)) {
            $result = [
                'status' => false,
                'status_message' => 'Unable to send the request',
            ];
            return  $result;
        }
        list($status, $response) = $result;
        if ($response === null) {
            $result = [
                'status' => false,
                'status_message' => 'Unable to decode the response: ' . var_export($response, true),
            ];
            return  $result;
        }
        // transform reverse
        foreach ($this->_api_transform_reverse as $from => $to) {
            if ($from != $to && isset($response[$from])) {
                $response[$to] = $response[$from];
                unset($response[$from]);
            }
        }
        // result
        $result = [
            'status' => &$status,
            'status_message' => &$status_message,
        ];
        $status = 'in_progress';
        $status_message = null;
        $state = (int) $response['state'];
        switch ($state) {
            // success
            case 0:
                if ($response['amount'] == $_amount
                    && $response['operation_id'] == $operation_id
                ) {
                    $status = 'success';
                    $status_message = 'Done';
                } else {
                    $status = 'processing';
                    $status_message = 'Done, but the amount or operation ID does not match';
                }
                break;
                // processing
            case 50:
                $status = 'processing';
                $status_message = 'In progress';
                break;
                // fails...
            case 2:
                $status_message = 'Access denied';
                break;
            case 101:
                $status_message = 'Invalid card number ' . $request['card'];
                break;
            case 121:
                $status_message = 'Unable to process the order with this id: ' . $operation_id;
                break;
            case 126:
                $status_message = 'Invalid phone number ' . $request['sender_phone'];
                break;
            case 421:
                $status_message = 'Invalid request data';
                break;
            case 113:
                $status_message = 'Payouts are disabled';
                break;
            case 908:
                $status_message = 'Payout has already been made';
                break;
            default:
                $status_message = 'Error: ' . $response['message'] . ' (' . $state . ')';
                break;
        }
        ($status_message ?? null) && $response['message'] = $status_message;
        // save response
        $sql_datetime = $payment_api->sql_datetime();
        $provider_name = 'ecommpay';
        $operation_options = [
            'processing' => [[
                'provider_name' => $provider_name,
                'datetime' => $sql_datetime,
            ]],
            'response' => [[
                'datetime' => $sql_datetime,
                'provider_name' => $provider_name,
                'state' => $state,
                'status_name' => $status,
                'status_message' => $status_message,
                'data' => $response,
            ]],
        ];
        $operation_update_data = [
            'operation_id' => $operation_id,
            'datetime_update' => $sql_datetime,
            'options' => $operation_options,
        ];
        $payment_api->operation_update($operation_update_data);
        return  $result;
    }
}