<?php

namespace App\Libs\Pay\Merchants;

use Exception;
use GuzzleHttp\Client;
use Log;

class SandPay
{
    const API_HOST = 'https://cashier.sandpay.com.cn/gateway/api/';
    const API_TEST_HOST = 'http://61.129.71.103:8003/gateway/';
    const NOTIFY_HOST = 'http://47.97.120.133:8094/';
    const NOTIFY_TEST_HOST = 'http://managepre.aizhuishu.com/';

    private $notify_url;
    private $front_url;
    private $pay_url;
    private $query_order_url;
    private $private_key;
    private $public_key;
    private $client;
    private $mer_id;
    private $app_id;

    public function __construct(array $config)
    {
        $this->pay_url =  (self::API_HOST) . 'order/pay';
        $this->query_order_url =  (self::API_HOST) . 'order/query';
        $this->mer_id = $config['mer_id'];
        $this->app_id = $config['app_id'];
        $this->private_key = $this->loadPk12Cert(storage_path($config['private_key_path']), $config['cert_pwd']);
        $this->public_key = $this->loadX509Cert(storage_path($config['public_key_path']));
        $this->notify_url =   env('SANDPAY_CALL_BACK_URL');
        //$this->notify_url =   (env('APP_ENV') == 'online' ? self::NOTIFY_HOST : self::NOTIFY_TEST_HOST) . 'pay/sandpay_back';
        $this->front_url =   (env('APP_ENV') == 'online' ? self::NOTIFY_HOST : self::NOTIFY_TEST_HOST) . 'pay/sandpay_back';
        $this->client = new Client();
    }

    public function notify(array $params)
    {
        $sign = $params['sign']; //签名
        $signType = $params['signType']; //签名方式
        $data = stripslashes($params['data']); //支付数据
        $result = json_decode($data, true); //data数据

        if ($this->verify($data, $sign, $this->public_key)) {
            //签名验证成功
            return $result;
        } else {
            //签名验证失败
            myLog('sand_pay')->error('sign error: ' . json_encode($params));
        }
    }

    public function send(array $data)
    {
        $params = [
            'head' => [
                'version' => '1.0',
                'method' => 'sandpay.trade.pay',
                'productId' => '00002020',
                'accessType' => '1',
                'mid' => $this->mer_id,
                'channelType' => '07',
                'reqTime' => date('YmdHis', time())
            ],
            'body' => [
                'orderCode' => $data['trade_no'],
                'totalAmount' => $this->amountFormat($data['price']/100),
                'subject' => $data['body'],
                'body' => $data['body'],
                'payMode' => 'sand_wx',
                'payExtra' => json_encode([
                    'subAppid' => $this->app_id,
                    'userId' => $data['openid'],
                ]),
                'clientIp' => _getIp(),
                'notifyUrl' => $this->notify_url,
                'frontUrl' => $this->front_url,
                'extend' => ''
            ]
        ];
        $result = '';
        try{
            $result = $this->sendPost($this->pay_url, $params);
        }catch (\Exception $e){
            Log::error('SandPay $request param is :');
            Log::error($params);
            Log::error('SandPay  error return  '.' pay_url： response '.$result);
        }

        $data = json_decode($result['data'], true);
        if ($data['head']['respCode'] == "000000") {
            $credential = json_decode($data['body']['credential'],true);
            return json_decode($credential['params'],true);
        } else {
            myLog('sand_pay')->error('唤起支付失败: ' );
            myLog('sand_pay')->error($result );
            myLog('sand_pay')->error($params );
            return [];
        }
    }
    public function refund(array $data){
        $params = [
            'head'=>[
                'version'=>'1.0',
                'method'=>'sandpay.trade.refund',
                'productId'=>'00002020',
                'accessType'=>'1',
                'mid'=>$this->mer_id,
                'channelType' => '07',
                'reqTime' => date('YmdHis')
            ],
            'body' => [
                'orderCode' => $data['refund_no'],
                'refundAmount' => $this->amountFormat($data['price']/100),
                'oriOrderCode'=>$data['trade_no'],
                'notifyUrl' => $data['callback'],
                'refundReason' => '退货',
                'extend' => isset($data['attach'])?$data['attach']:''
            ]
        ];
        $url = 'https://cashier.sandpay.com.cn/gateway/api/order/refund';
        $result = '';
        try{
            $result = $this->sendPost($url, $params);
        }catch (\Exception $e){
            Log::error('SandPay $request param is :');
            Log::error($params);
            Log::error('SandPay  error return  '.' pay_url： response '.$result);
        }
        return $result;
    }

    public function query(array $data){
        $params = [
            'head'=>[
                'version'=>'1.0',
                'method'=>'sandpay.trade.query',
                'productId'=>'00002020',
                'accessType'=>'1',
                'mid'=>$this->mer_id,
                'channelType' => '07',
                'reqTime' => date('YmdHis')
            ],
            'body' => [
                'orderCode' => $data['trade_no'],
                'extend' => ''
            ]
        ];
        $result = '';
        $url = 'https://cashier.sandpay.com.cn/gateway/api/order/query';
        try{
            $result = $this->sendPost($url, $params);
        }catch (\Exception $e){
        }
        return $result;
    }

    /**
     * 格式金额
     */
    private function amountFormat(float $amount)
    {
        $amount = (string) (int) ($amount * 100);
        $length = strlen($amount);
        for ($i = 0; $i < 12 - $length; $i++) {
            $amount = '0' . $amount;
        }
        return $amount;
    }

    /**
     * 请求接口
     * @param array $data
     * @return array
     */
    private function sendPost(string $url, array $data)
    {
        // step1: 私钥签名
        $sign = $this->sign($data, $this->private_key);

        // step2: 拼接post数据
        $post = array(
            'charset' => 'utf-8',
            'signType' => '01',
            'data' => json_encode($data),
            'sign' => $sign
        );

        // step3: post请求
        $result = $this->http_post_json($url, $post);
        $arr = $this->parse_result($result);

        try {
            //step4: 公钥验签
            $this->verify($arr['data'], $arr['sign'], $this->public_key);
            return $arr;
        } catch (Exception $e) {
            echo $e->getMessage();
            return [];
        }
    }

    private function parse_result($result)
    {
        $arr = array();
        $response = urldecode($result);
        $arrStr = explode('&', $response);
        foreach ($arrStr as $str) {
            $p = strpos($str, "=");
            $key = substr($str, 0, $p);
            $value = substr($str, $p + 1);
            $arr[$key] = $value;
        }

        return $arr;
    }

    /**
     * 发送请求
     * @param $url
     * @param $param
     * @return array|null
     * @throws Exception
     */
    private function http_post_json(string $url, array $param)
    {
        try {
            $response = $this->client->post($url, ['form_params' => $param, 'Content-Type' => 'application/x-www-form-urlencoded']);
            $content = $response->getBody()->getContents();
            return $content;
            /*$content = urldecode($content);
            parse_str($content, $result);
            return $result;*/
        } catch (Exception $e) {
            throw $e;
        }
    }

    /**
     * 获取公钥
     * @param $path
     * @return mixed
     * @throws Exception
     */
    private function loadX509Cert($path)
    {
        try {
            $file = file_get_contents($path);
            if (!$file) {
                throw new Exception('loadx509Cert::file_get_contents ERROR');
            }

            $cert = chunk_split(base64_encode($file), 64, "\n");
            $cert = "-----BEGIN CERTIFICATE-----\n" . $cert . "-----END CERTIFICATE-----\n";

            $res = openssl_pkey_get_public($cert);
            $detail = openssl_pkey_get_details($res);
            openssl_free_key($res);

            if (!$detail) {
                throw new Exception('loadX509Cert::openssl_pkey_get_details ERROR');
            }

            return $detail['key'];
        } catch (Exception $e) {
            throw $e;
        }
    }

    /**
     * 获取私钥
     * @param $path
     * @param $pwd
     * @return mixed
     * @throws Exception
     */
    private function loadPk12Cert($path, $pwd)
    {
        try {
            $file = file_get_contents($path);
            if (!$file) {
                throw new Exception('loadPk12Cert::file
					_get_contents');
            }

            if (!openssl_pkcs12_read($file, $cert, $pwd)) {
                throw new Exception('loadPk12Cert::openssl_pkcs12_read ERROR');
            }
            return $cert['pkey'];
        } catch (Exception $e) {
            throw $e;
        }
    }

    /**
     * 私钥签名
     * @param $plainText
     * @param $path
     * @return string
     * @throws Exception
     */
    function sign($plainText, $path)
    {
        $plainText = json_encode($plainText);
        try {
            $resource = openssl_pkey_get_private($path);
            $result = openssl_sign($plainText, $sign, $resource);
            openssl_free_key($resource);

            if (!$result) {
                throw new Exception('签名出错' . $plainText);
            }

            return base64_encode($sign);
        } catch (Exception $e) {
            throw $e;
        }
    }

    /**
     * 公钥验签
     * @param $plainText
     * @param $sign
     * @param $path
     * @return int
     * @throws Exception
     */
    function verify($plainText, $sign, $path)
    {
        $resource = openssl_pkey_get_public($path);
        $result = openssl_verify($plainText, base64_decode($sign), $resource);
        openssl_free_key($resource);

        if (!$result) {
            throw new Exception('签名验证未通过,plainText:' . $plainText . '。sign:' . $sign, '02002');
        }

        return $result;
    }

    /**
     * 公钥加密AESKey
     * @param $plainText
     * @param $puk
     * @return string
     * @throws Exception
     */
    function RSAEncryptByPub($plainText, $puk)
    {
        if (!openssl_public_encrypt($plainText, $cipherText, $puk, OPENSSL_PKCS1_PADDING)) {
            throw new Exception('AESKey 加密错误');
        }

        return base64_encode($cipherText);
    }

    /**
     * 私钥解密AESKey
     * @param $cipherText
     * @param $prk
     * @return string
     * @throws Exception
     */
    function RSADecryptByPri($cipherText, $prk)
    {
        if (!openssl_private_decrypt(base64_decode($cipherText), $plainText, $prk, OPENSSL_PKCS1_PADDING)) {
            throw new Exception('AESKey 解密错误');
        }

        return (string) $plainText;
    }

    /**
     * AES加密
     * @param $plainText
     * @param $key
     * @return string
     * @throws Exception
     */
    function AESEncrypt($plainText, $key)
    {
        $plainText = json_encode($plainText);
        $result = openssl_encrypt($plainText, 'AES-128-ECB', $key, 1);

        if (!$result) {
            throw new Exception('报文加密错误');
        }

        return base64_encode($result);
    }

    /**
     * AES解密
     * @param $cipherText
     * @param $key
     * @return string
     * @throws Exception
     */
    function AESDecrypt($cipherText, $key)
    {
        $result = openssl_decrypt(base64_decode($cipherText), 'AES-128-ECB', $key, 1);

        if (!$result) {
            throw new Exception('报文解密错误', 2003);
        }

        return $result;
    }

    /**
     * 生成AESKey
     * @param $size
     * @return string
     */
    function aes_generate($size)
    {
        $str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
        $arr = array();
        for ($i = 0; $i < $size; $i++) {
            $arr[] = $str[mt_rand(0, 61)];
        }
        return implode('', $arr);
    }
}
