<?php
class QIWI {
    
    # Публичные переменные класса :
    public $iQiwiAccount, $aBalances = array( 'USD' => 0, 'RUB' => 0, 'EUR' => 0, 'KZT' => 0 );
    
    # Приватные переменные класса :
    private $sCookieFile, $sProxy, $sResponse, $aResponse;
   
    # Метод : конструктор.
    # Принимает : Qiwi.кошелек, пароль, абсолютный путь к файлу cookie, данные прокси.
    public function __construct( $iQiwiAccount, $sPassword, $sCookieFile, $sProxy = null ) {
        
        # Если файл cookie не существует :
        if( !is_file( $sCookieFile ) ) {
            
            # Если директория недоступна для записи :
            if( !is_writeable( dirname( $sCookieFile ) ) ) {
                
                # Выбрасываем исключение :
                throw new Exception( 'please, set chmod 777 for directory '.dirname( $sCookieFile ) );
            }
            
            # Создание файла :
            file_put_contents( $sCookieFile, '' );
        }
        
        # Если файл cookie недоступен для записи :
        if( !is_writable( $sCookieFile ) ) {
            
            # Выбрасываем исключение :
            throw new Exception( 'please, set chmod 777 for file '.$sCookieFile );
        }
 
        # Инициализация данных класса :
        $this->sCookieFile = $sCookieFile;
        $this->sProxy = $sProxy;

        # Запрос к серверу :
        $this->curl( 'person/state.action' );
        
        # Проверка авторизации кошельком :
        if( isset( $this->aResponse['data'] ) && isset( $this->aResponse['data']['person'] ) && isset( $this->aResponse['data']['balances'] ) && $this->aResponse['data']['person'] == $iQiwiAccount ) {
            
            # Инициализация переменных класса :
            $this->iQiwiAccount = $this->aResponse['data']['person'];
            
            # Перебираем информацию о балансах :
            foreach( $this->aResponse['data']['balances'] as $sEquivalent => $dBalance ) {
                
                # Добавляем данные в массив :
                $this->aBalances[$sEquivalent] = $dBalance;
            }
            return;
        }
        
        # Запрос к серверу :
        $this->curl( 'https://auth.qiwi.com/cas/tgts', json_encode( array( 'login' => '+'.$iQiwiAccount, 'password' => $sPassword ) ) );
        
        # Если в ответе есть ошибка :
        if( isset( $this->aResponse['entity'] ) && isset( $this->aResponse['entity']['error'] ) && isset( $this->aResponse['entity']['error']['message'] ) )
            throw new Exception( mb_strtolower( $this->aResponse['entity']['error']['message'] ) );
        
        # Если в ответе нет токена :
        if( !isset( $this->aResponse['entity'] ) || !isset( $this->aResponse['entity']['ticket'] ) )
            throw new Exception( 'ticket не найден - '.$this->sResponse );
        
        # Тут у нас TGT токен
        $sTGTToken = $this->aResponse['entity']['ticket'];
        
        # Запрос к серверу :
        $this->curl( 'https://auth.qiwi.com/cas/sts', json_encode( array( 'service' => 'https://qiwi.com/j_spring_cas_security_check', 'ticket' => $sTGTToken ) ) );
        
        # Если в ответе есть ошибка :
        if( isset( $this->aResponse['entity'] ) && isset( $this->aResponse['entity']['error'] ) && isset( $this->aResponse['entity']['error']['message'] ) )
            throw new Exception( mb_strtolower( $this->aResponse['entity']['error']['message'] ) );
        
        # Если в ответе нет токена :
        if( !isset( $this->aResponse['entity'] ) || !isset( $this->aResponse['entity']['ticket'] ) )
            throw new Exception( 'ticket не найден - '.$this->sResponse );
        
        # Запрос к серверу :
        $this->curl( 'https://qiwi.com/j_spring_cas_security_check?ticket='.$this->aResponse['entity']['ticket'] );
        
        # Если в ответе есть ошибка :
        if( isset( $this->aResponse['message'] ) && $this->aResponse['message'] != '' )
            throw new Exception( $this->aResponse['message'] );
        
        # Если авторизация не успешная :
        if( !isset( $this->aResponse['code'] ) || !isset( $this->aResponse['code']['_name'] ) || $this->aResponse['code']['_name'] != 'NORMAL' )
            throw new Exception( 'error authorize - '.$this->sResponse );
        
        # Получение информации об qiwi.кошельке :
        $this->curl( 'person/state.action' );

        # Если возникла ошибка:
        if( !isset( $this->aResponse['data'] ) || !is_array( $this->aResponse['data'] ) || !isset( $this->aResponse['data']['person'] ) || !isset( $this->aResponse['data']['balances'] ) )
            throw new Exception( var_export( $this->aResponse, true ) );
        
        # Инициализация переменных класса :
        $this->iQiwiAccount = $this->aResponse['data']['person'];
        
        # Перебираем информацию о балансах :
        foreach( $this->aResponse['data']['balances'] as $sEquivalent => $dBalance ) {
            
            # Инициализация переменных класса :
            $this->aBalances[$sEquivalent] = $dBalance;
        }
    }
    
    # Метод : очистка cookie.
    public function clearCookie() {
        
        # Очистка содержимого файла :
        file_put_contents( $this->sCookieFile, '' );
    }
    
    # Метод : перевод средств на другой Qiwi.кошелек.
    # Принимает : кошелек, сумма, валюта, примечание.
    # Возвращает : уникальный номер транзакции.
    public function transfer( $iQiwiAccount, $dAmount, $sCurrency, $sComment ) {
        return $this->payment( null, array( 'account' => '+'.$iQiwiAccount, 'comment' => $sComment ), $dAmount, $sCurrency, $sCurrency );
    }
    
    # Метод : получение информации из истории транзакций.
    # Принимает : дату начала, дату конца.
    public function getHistory( $sStartDate, $sFinishDate ) {

        # Запрос к серверу :
        $this->curl( 'user/report/list.action?daterange=true&start='.$sStartDate.'&finish='.$sFinishDate );
 
        # Результирующий массив :
        $aTransactions = array();
        
        # Перебираем строки :
        foreach( explode( '</div><div class="reportsLine ', str_replace( '> <', '><', preg_replace( '!\s+!u', ' ', $this->sResponse ) ) ) as $iKey => $sValue ) {
            
            # Пропускаем первую строку :
            if( $iKey == 0 )
                continue;
            
            # Инициализация массива :
            $aData = array();

            # Получение суммы счета :
            $aData['iID'] = explode( '<span class="value">', $sValue );
            if( count( $aData['iID'] ) < 2 )
                continue;
            $aData['iID'] = explode( '</', $aData['iID'][1] );
            $aData['iID'] = trim( $aData['iID'][0] );
            
            # Получение даты и время :
            $aData['sDate'] = explode( 'class="date">', $sValue );
            $aData['sDate'] = explode( '</', $aData['sDate'][1] );
            $aData['sDate'] = trim( $aData['sDate'][0] );
            $aData['sTime'] = explode( 'class="time">', $sValue );
            $aData['sTime'] = explode( '</', $aData['sTime'][1] );
            $aData['sTime'] = trim( $aData['sTime'][0] );
            
            # Получение суммы :
            $aData['sAmount'] = explode( 'class="originalExpense"><span>', $sValue );
            $aData['sAmount'] = explode( '</', $aData['sAmount'][1] );
            $aData['sAmount'] = trim( $aData['sAmount'][0] );
            $aData['dAmount'] = preg_replace( '/[^0-9\.]+/', '', str_replace( ',', '.', $aData['sAmount'] ) ) - 0;
            
            # Получение валюты счета :
            $aData['sCurrency'] = mb_strpos( $aData['sAmount'], 'руб.' ) !== false ? 'RUB' : (mb_strpos( $aData['sAmount'], 'долл.' ) !== false ? 'USD' : (mb_strpos( $aData['sAmount'], 'тенге.' ) !== false ? 'KZT' : 'NAN'));
            
            # Получение суммы с учетом комиссии :
            $aData['sWithExpend'] = explode( 'WithExpend', $sValue );
            $aData['sWithExpend'] = explode( '</div>', $aData['sWithExpend'][1] );
            $aData['sWithExpend'] = explode( '<div class="cash">', $aData['sWithExpend'][0] );
            $aData['sWithExpend'] = trim( $aData['sWithExpend'][1] );
            $aData['dWithExpend'] = preg_replace( '/[^0-9\.]+/', '', str_replace( ',', '.', $aData['sWithExpend'] ) ) - 0;
            
            # Получение номера телефона корреспондента :
            $aData['iOpponentPhone'] = explode( 'class="opNumber">', $sValue );
            $aData['iOpponentPhone'] = explode( '</', $aData['iOpponentPhone'][1] );
            $aData['iOpponentPhone'] = trim( str_replace( '+', '', $aData['iOpponentPhone'][0] ) );
            
            # Получение примечания :
            $aData['sComment'] = explode( 'class="comment">', $sValue );
            $aData['sComment'] = explode( '</', $aData['sComment'][1] );
            $aData['sComment'] = html_entity_decode( trim( $aData['sComment'][0] ), ENT_QUOTES, 'UTF-8' );
            
            # Получаем информацию о провайдере :
            $aData['sProvider'] = explode( '<div class="provider"><span>', $sValue );
            $aData['sProvider'] = explode( '</span>', $aData['sProvider'][1] );
            $aData['sProvider'] = trim( $aData['sProvider'][0] );
            
            # Прибыль или расход ?
            $aData['sType'] = mb_strpos( $sValue, 'IncomeWithExpend expenditure' ) !== false ? 'EXPENDITURE' : (mb_strpos( $sValue, 'IncomeWithExpend income' ) !== false ? 'INCOME' : 'NAN');
            
            # Получение статуса транзакции :
            $aData['sStatus'] = explode( '"', $sValue );
            $aData['sStatus'] = str_replace( 'status_', '', trim( $aData['sStatus'][0] ) );
            
            # Получаем  информацию о ошибке если она есть :
            if( $aData['sStatus'] == 'ERROR' ) {
                $aData['sError'] = explode( '{"message":"', $sValue );
                $aData['sError'] = explode( '"', $aData['sError'][1] );
                $aData['sError'] = trim( $aData['sError'][0] );
            }
            
            # Дополнительные проверки :
            if( $aData['iID'] == false ) {
                $aData['iID'] = explode( '{"txn":', $sValue );
                $aData['iID'] = explode( '}', $aData['iID'][1] );
                $aData['iID'] = $aData['iID'][0];
            }
            
            $aTransactions['ID-'.$aData['iID']] = $aData;
        }
        
        return $aTransactions;
    }
    
    # Метод : получение информации о балансах.
    # Возвращает : ассоциативный массив, ключ - валюта, значение - баланс.
    public function getBalances() {
        
        # Запрос к серверу :
        $this->curl( 'person/state.action' );

        # Если не существует нужных данных :
        if( !isset( $this->aResponse['data'] ) || !isset( $this->aResponse['data']['person'] ) || !isset( $this->aResponse['data']['balances'] ) )
            throw new Exception( 'bad server answer' );
        
        # Инициализация переменных :
        $this->iQiwiAccount = $this->aResponse['data']['person'];
        
        # Перебираем балансы :
        foreach( $this->aResponse['data']['balances'] as $sEquivalent => $dBalance ) {
            
            # Добавление данных в массив :
            $this->aBalances[$sEquivalent] = $dBalance;
        }
        
        return $this->aBalances;
    }

    # Метод : оплата услуги.
    private function payment( $iProvider, array $aExtra, $dAmount, $sCurrency = 'RUB', $sPayCurrency = 'RUB' ) {
        
        # Распознавание суммы платежа :
        $dAmount = intval( str_replace( ',', '.', $dAmount ) * 100 ) / 100;
        $aAmount = explode( '.', $dAmount );
        if( !isset( $aAmount[1] ) )
            $aAmount[1] = '00';
        else if( strlen( $aAmount[1] ) != 2 )
            $aAmount[1] .= '0';
        
        # Преобразовуем массив aExtra :
        foreach( $aExtra as $sKey => $sValue ) {
            $aExtra["extra['".$sKey."']"] = $sValue;
            unset( $aExtra[$sKey] );
        }
        
        # Запрос на получение формы платежа :
        $this->curl( is_null( $iProvider ) ? 'payment/transfer/form.action' : 'payment/form.action?provider='.$iProvider );
        
        # Запрос на получение токена платежа :
        $this->curl( 
            'user/payment/form/state.action?'.http_build_query( 
                array_merge( array(
                    'amountInteger' => $aAmount[0],
                    'amountFraction' => $aAmount[1],
                    'arg_num' => '',
                    'currency' => $sCurrency,
                    'protected' => 'true',
                    'source' => 'qiwi_'.$sPayCurrency,
                    'state' => 'CONFIRM' 
                ), $aExtra )
            )
        );
        
        # Проверка статуса платежа :
        if( !isset( $this->aResponse['data'] ) || !isset( $this->aResponse['data']['token'] ) )
            throw new Exception( isset( $this->aResponse['message'] ) ? $this->aResponse['message'] : 'internal error, step 2' );
            
        # Проведения платежа в системе :
        $this->curl(
            'user/payment/form/state.action?'.http_build_query( 
                array_merge( array(
                    'amountInteger' => $aAmount[0],
                    'amountFraction' => $aAmount[1],
                    'arg_num' => '',
                    'currency' => $sCurrency,
                    'protected' => 'true',
                    'source' => 'qiwi_'.$sPayCurrency,
                    'state' => 'CONFIRM',
                    'token' => $this->aResponse['data']['token'],
                ), $aExtra )
            )
        );

        # Запрос на получение токена подтверждения платежа :
        $this->curl( 'payment/form/state.action?state=PAY' );
        
        # Проверка статуса платежа :
        if( !isset( $this->aResponse['data'] ) || !isset( $this->aResponse['data']['token'] ) )
            throw new Exception( isset( $this->aResponse['message'] ) ? $this->aResponse['message'] : 'internal error, step 4' );

        # Подтвеждение проведения платежа :
        $this->curl(
            'payment/form/state.action',
            array(
                'token' => $this->aResponse['data']['token'],
                'state' => 'PAY'
            )
        );

        # Если платеж был не успешен :
        if( mb_strpos( $this->sResponse, 'transaction":"' ) === false ) {
            if( mb_strpos( $this->sResponse, 'class="errorElement"' ) !== false ) {
                $aExplode = explode( 'class="errorElement">', $this->sResponse );
                $aExplode = explode( '</', $aExplode[1] );
                throw new Exception( trim( $aExplode[0] ) );
            }
            # Иначе, если требуется SMS подтверждение операций :
            else if( mb_strpos( $this->sResponse, 'confirmPage' ) !== false )
                return false;
            else {
                if( count( $sMessage = explode( '<p>', $this->sResponse ) ) < 2 )
                    throw new Exception( 'unknown error' );
                $sMessage = explode( '</p>', $sMessage[1] );
                throw new Exception( $sMessage[0] );
            }
        }
        
        # Если провайдер - Qiwi Яйца :
        if( $iProvider == 22496 ) {
            if( count( $aExplode = explode( 'Код ваучера:', $this->sResponse ) ) < 2 )
                throw new Exception( 'error parse egg' );
            $aExplode = explode( '</', $aExplode[1] );
            return trim( $aExplode[0] );
        }
            
        # Получение истории переводов :
        $aHistory = $this->getHistory( date( 'd.m.Y', strtotime( '-1 day' ) ), date( 'd.m.Y', strtotime( '+1 day' ) ) );
        $aTransfer = array_shift( $aHistory );
        if( $aTransfer === false || $aTransfer['dAmount'] != $dAmount || $aTransfer['sCurrency'] != $sCurrency )
            throw new Exception( 'transfer not found in history' );
        return $aTransfer['iID'];
    }
    
    # Метод : проверка активности SMS подтверждения.
    # Возвращает : true - SMS включено, false - SMS выключено.
    public function isSMSActive() {
        
        # Отправка запроса :
        $this->curl( 'options/security.action' );

        # Если не удалось проверить :
        if( count( $aExplode = explode( 'SMS_CONFIRMATION', $this->sResponse ) ) < 3 )
            throw new Exception( 'error check sms confirmation' );
        
        return strpos( $aExplode[1], 'display' ) === false;
    }
    
    # Метод : генерация QIWI Ваучера.
    # Принимает : сумма, комментарий.
    # Возвращает : код ваучера.
    public function createEgg( $dAmount, $sComment ) {
        return $this->payment( 22496, array( 'account' => '708', 'comment' => $sComment, 'to_account' => '', 'to_account_type' => 'undefind' ), $dAmount );
    }
    
    # Метод : активация QIWI Ваучера.
    # Принимает : код ваучера.
    # Возвращает ассоциативный массив из : dAmount - сумма, sComment - комментарий.
    public function activateEgg( $sCode ) {
        
        # Если код не указан :
        if( $sCode == '' )
            throw new Exception( 'код ваучера не указан' );
        
        # Инициализация переменных :
        $aResult = array(); # результирующий массив
        
        # Загрузка страницы :
        $this->curl( 'user/eggs/activate/content/form.action', array( 'code' => $sCode ) );
        
        # Если нет нужного ответа :
        if( !isset( $this->aResponse['data'] ) || !isset( $this->aResponse['data']['token'] ) )
            throw new Exception( 'error in step 1' );

        # Загрузка страницы :
        $this->curl( 'user/eggs/activate/content/form.action', array( 'code' => $sCode, 'token' => $this->aResponse['data']['token'] ) );

        # Если зачисления не произошло :
        if( mb_substr_count( $this->sResponse, $sCode ) != 2 ) {
            if( count( $aExplode = explode( '<p>', $this->sResponse ) ) < 3 )
                throw new Exception( 'undefined error' );
            $aExplode = explode( '</', $aExplode[2] );
            throw new Exception( trim( strip_tags( $aExplode[0] ) ) );
        }
        
        # Парсинг суммы :
        if( count( $aExplode = explode( 'на сумму', $this->sResponse ) ) < 2 )
            throw new Exception( 'error parse amount' );
        $aExplode = explode( 'руб', $aExplode[1] );
        $aResult['dAmount'] = preg_replace( '/[^0-9\.]+/', '', str_replace( ',', '.', trim( $aExplode[0] ) ) ) - 0;
        
        # Парсинг примечания :
        if( count( $aExplode = explode( 'Комментарий к переводу', $this->sResponse ) ) < 2 )
            throw new Exception( 'error parse comment' );
        $aExplode = explode( '</p>', $aExplode[1] );
        $aResult['sComment'] = trim( strip_tags( $aExplode[0] ) );

        # Запрос к серверу :
        $this->curl( 'user/eggs/activate/content/activate.action', array( 'code' => $sCode ) );
        
        # Проверка подтверждения :
        if( !isset( $this->aResponse['code'] ) || !isset( $this->aResponse['code']['value'] ) || !isset( $this->aResponse['code']['_name'] ) || $this->aResponse['code']['value'] != '0' || $this->aResponse['code']['_name'] != 'NORMAL' )
            throw new Exception( 'bad server answer: '.var_export( $this->aResponse, true ) );

        return $aResult;
    }
    
    public function requestChangePassword() {
        
        # Загружаем страницу :
        $this->curl( 'options/password.action' );
        
        # Загружаем страницу :
        $this->curl( 'user/person/change/password.action' );
        
        # Инициализация переменных :
        $iIdentifier = isset( $this->aResponse['identifier'] ) ? $this->aResponse['identifier'] - 0 : 0;
        
        # Если поле <= 0 :
        if( $iIdentifier <= 0 )
            throw new Exception( 'field identifier not found' );
        
        # Загружаем страницу :
        $this->curl( 'user/confirmation/form.action', array(
            'identifier' => $iIdentifier,
            'type' => 'PASSWORD_CHANGE'
        ) );
        
        return $iIdentifier;
    }

    public function progressChangePassword( $iIdentifier, $sOldPassword, $sNewPassword, $iCode ) {
        
        # Загружаем страницу :
        $this->curl( 'user/confirmation/confirm.action', array(
            'code' => $iCode,
            "data['newPassword']" => $sNewPassword,
            "data['oldPassword']" => $sOldPassword,
            "data['period']" => 4,
            'identifier' => $iIdentifier,
            'type' => 'PASSWORD_CHANGE'
        ) );
        
        # Проверка на наличие ошибок :
        if( !isset( $this->aResponse['code'] ) || !isset( $this->aResponse['code']['value'] ) || $this->aResponse['code']['value'] != 0 )
            throw new Exception( isset( $this->aResponse['message'] ) ? $this->aResponse['message'] : json_encode( $this->aResponse ) );
    }

    public function requestConfirmPayments( $bOff = true ) {
        
        # Загрузка страницы :
        $this->curl( 'settings/options/security.action' );
        
        # Загрузка страницы :
        $this->curl( 'user/person/change/security.action', array(
            'type' => 'SMS_CONFIRMATION',
            'value' => $bOff ? 'false' : 'true' 
        ) );
        
        # Проверка ответа :
        if( !isset( $this->aResponse['code'] ) || !isset( $this->aResponse['code']['value'] ) || $this->aResponse['code']['value'] != 7 || !isset( $this->aResponse['data'] ) || !isset( $this->aResponse['data']['token'] ) )
            throw new Exception( json_encode( $this->aResponse ) );
        
        # Инициализация переменных :
        $sToken = $this->aResponse['data']['token'];
        
        # Загрузка страницы :
        $this->curl( 'user/person/change/security.action', array(
            'token' => $sToken,
            'type' => 'SMS_CONFIRMATION',
            'value' => $bOff ? 'false' : 'true'
        ) );
        
        # Проверка ответа :
        if( isset( $this->aResponse['code'] ) && isset( $this->aResponse['code']['value'] ) && $this->aResponse['code']['value'] == 0 )
            return true;
        
        # Проверка ответа :
        if( !isset( $this->aResponse['code'] ) || !isset( $this->aResponse['code']['value'] ) || $this->aResponse['code']['value'] != 4 || !isset( $this->aResponse['identifier'] ) || $this->aResponse['identifier'] <= 0 )
            throw new Exception( json_encode( $this->aResponse ) );
        
        # Инициализация переменных :
        $iIdentifier = $this->aResponse['identifier'];
        
        # Загрузка страницы :
        $this->curl( 'user/confirmation/form.action', array(
            'identifier' => $iIdentifier,
            'token' => $sToken,
            'type' => 'SMS_CONFIRMATION',
            'value' => $bOff ? 'false' : 'true'
        ) );
        
        return $iIdentifier;
    }

    public function progressConfirmPayments( $iIdentifier, $iCode ) {
        
        # Загрузка страницы :
        $this->curl( 'user/confirmation/confirm.action', array(
            'code' => $iCode,
            'identifier' => $iIdentifier,
            'type' => 'SMS_CONFIRMATION'
        ) );
        
        # Проверка на наличие ошибок :
        if( !isset( $this->aResponse['code'] ) || !isset( $this->aResponse['code']['value'] ) || $this->aResponse['code']['value'] != 0 )
            throw new Exception( isset( $this->aResponse['message'] ) ? $this->aResponse['message'] : json_encode( $this->aResponse ) );
    }
    
    public function paymentSMSConfirm( $iCode ) {
        
        # Загрузка страницы :
        $this->curl( 'user/payment/form/state.action', array(
            'confirmationCode' => $iCode,
            'protected' => 'true',
            'state' => 'PAY'
        ) );
        
        # Загрузка страницы :
        $this->curl( 'user/payment/form/state.action', array(
            'confirmationCode' => $iCode,
            'protected' => 'true',
            'state' => 'PAY',
            'token' => $this->aResponse['data']['token']
        ) );

        # Если платеж был не успешен :
        if( mb_strpos( $this->sResponse, 'transaction":"' ) === false ) {
            if( mb_strpos( $this->sResponse, 'class="errorElement"' ) !== false ) {
                $aExplode = explode( 'class="errorElement">', $this->sResponse );
                $aExplode = explode( '</', $aExplode[1] );
                throw new Exception( trim( $aExplode[0] ) );
            }
            if( count( $sMessage = explode( '<p>', $this->sResponse ) ) < 2 )
                throw new Exception( 'unknown error' );
            $sMessage = explode( '</p>', $sMessage[1] );
            throw new Exception( $sMessage[0] );
        }
        
        # Если это яйцо :
        if( count( $aExplode = explode( 'Код ваучера:', $this->sResponse ) ) > 1 ) {
            $aExplode = explode( '<', $aExplode[1] );
            return trim( $aExplode[0] );
        }
        
        # Парсинг № транзакции :
        $aExplode = explode( 'transaction":"', $this->sResponse );
        $aExplode = explode( '"', $aExplode[1] );
        return $aExplode[0];
    }
	
    # Метод : запрос к серверу.
    private function curl( $sPath, $mPOST = null, array $aOptions = null ) {
        
        # Инициализация статических переменных :
        static $sReferer = null;
        
        # Инициализация переменных :
        $oCurl = curl_init( mb_substr( $sPath, 0, 4 ) == 'http' ? $sPath : 'https://qiwi.com/'.$sPath );
        
        # Настройки cURL :
        curl_setopt_array( $oCurl, array(
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_COOKIEJAR => $this->sCookieFile,
            CURLOPT_COOKIEFILE => $this->sCookieFile,
            CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:39.0) Gecko/20100101 Firefox/39.0',
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_HTTPHEADER => mb_substr( $sPath, 0, 4 ) == 'http' ? (is_null( $mPOST ) ? array( 'Accept: application/json, text/javascript, */*; q=0.01', 'X-Requested-With: XMLHttpRequest' ) : array( 'Content-Type: application/json; charset=UTF-8' )) : array( 'Accept: application/json, text/javascript, */*; q=0.01', 'X-Requested-With: XMLHttpRequest' ),
        ) );
        
        # Если требуется отправить POST - запрос :
        if( is_array( $mPOST ) || $mPOST != '' || mb_substr( $sPath, 0, 4 ) != 'http' ) {
            
            # Настройки Curl подключения :
            curl_setopt_array( $oCurl, array(
                CURLOPT_POST => true,
                CURLOPT_POSTFIELDS => is_array( $mPOST ) ? http_build_query( $mPOST ) : $mPOST,
            ) );
        }
        
        # Если существует реферер :
        if( !is_null( $sReferer ) )
            curl_setopt( $oCurl, CURLOPT_REFERER, $sReferer );
        
        # Если требуется указать дополнительные настройки :
        if( is_array( $aOptions ) && count( $aOptions ) )
            curl_setopt_array( $oCurl, $aOptions );
        
        # Если требуется работать через Proxy :
        if( $this->sProxy != '' ) {
            
            # Разбиваем строку по двоеточию в массив :
            $aExplode = explode( ':', $this->sProxy );
            
            # Если размер массива > 2 :
            if( count( $aExplode ) > 2 ) {
                
                # Подключение proxy к curl:
                curl_setopt_array( $oCurl, array(
                    CURLOPT_PROXY => $aExplode[0].':'.$aExplode[1],
                    CURLOPT_HTTPPROXYTUNNEL => true,
                    CURLOPT_TIMEOUT => 10,
                    CURLOPT_PROXYTYPE => $aExplode[2] == 'socks5' ? CURLPROXY_SOCKS5 : ($aExplode[2] == 'socks4' ? CURLPROXY_SOCKS4 : CURLPROXY_HTTP)
                ) );
                
                # Если размер массива больше 4 :
                if( count( $aExplode ) > 4 ) {
                    
                    # Авторизация в proxy к curl:
                    curl_setopt( $oCurl, CURLOPT_PROXYUSERPWD, $aExplode[3].':'.$aExplode[4] );
                }
            }          
        }

        # Получение ответа :
        $this->sResponse = curl_exec( $oCurl );
        
        # Если произошла ошибка :
        if( curl_errno( $oCurl ) )
            throw new Exception( curl_errno( $oCurl ).' - '.curl_error( $oCurl ) );
        
        # Закрываем соединение :
        curl_close( $oCurl );
        
        # Сохраняем страницу referer :
        $sReferer = mb_substr( $sPath, 0, 4 ) == 'http' ? $sPath : 'https://qiwi.com/'.$sPath;
        
        # Преобразование ответа в массив :
        $this->aResponse = json_decode( $this->sResponse, true );
        if( json_last_error() != JSON_ERROR_NONE )
            $this->aResponse = array();
        
        return $this->sResponse;
    }
}